diff --git a/app/code/Magento/Analytics/Test/Mftf/Data/UserData.xml b/app/code/Magento/Analytics/Test/Mftf/Data/UserData.xml index f6e5b955816e2..83f27def4b4e8 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Data/UserData.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Data/UserData.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> noreport No @@ -18,7 +18,7 @@ en_US true 123123q - + restrictedWebUser restricted diff --git a/app/code/Magento/Analytics/Test/Mftf/Section/AdminConfigSection.xml b/app/code/Magento/Analytics/Test/Mftf/Section/AdminConfigSection.xml new file mode 100644 index 0000000000000..f8554a4ea115b --- /dev/null +++ b/app/code/Magento/Analytics/Test/Mftf/Section/AdminConfigSection.xml @@ -0,0 +1,22 @@ + + + +
+ + + + + + + + + + + +
+
\ No newline at end of file diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml index d9617209dcdff..1706383fc7866 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml @@ -16,9 +16,6 @@ - - - diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml index 2d5594a43b1a7..dcfdca9e8edd7 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml @@ -17,9 +17,6 @@ - - - diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml index d8aed1250d82a..5414e9c2a5c18 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml @@ -17,9 +17,6 @@ - - - diff --git a/app/code/Magento/AsynchronousOperations/Api/Data/BulkSummaryInterface.php b/app/code/Magento/AsynchronousOperations/Api/Data/BulkSummaryInterface.php index f5dd5bb13eabb..a433ec0953a83 100644 --- a/app/code/Magento/AsynchronousOperations/Api/Data/BulkSummaryInterface.php +++ b/app/code/Magento/AsynchronousOperations/Api/Data/BulkSummaryInterface.php @@ -13,6 +13,8 @@ */ interface BulkSummaryInterface extends \Magento\Framework\Bulk\BulkSummaryInterface { + const USER_TYPE = 'user_type'; + /** * Retrieve existing extension attributes object. * @@ -31,4 +33,19 @@ public function getExtensionAttributes(); public function setExtensionAttributes( \Magento\AsynchronousOperations\Api\Data\BulkSummaryExtensionInterface $extensionAttributes ); + + /** + * Get user type + * + * @return int + */ + public function getUserType(); + + /** + * Set user type + * + * @param int $userType + * @return $this + */ + public function setUserType($userType); } diff --git a/app/code/Magento/AsynchronousOperations/Model/BulkManagement.php b/app/code/Magento/AsynchronousOperations/Model/BulkManagement.php index 4f086ce8ac2ca..faf01921e5737 100644 --- a/app/code/Magento/AsynchronousOperations/Model/BulkManagement.php +++ b/app/code/Magento/AsynchronousOperations/Model/BulkManagement.php @@ -5,6 +5,7 @@ */ namespace Magento\AsynchronousOperations\Model; +use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ResourceConnection; use Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface; use Magento\AsynchronousOperations\Api\Data\BulkSummaryInterfaceFactory; @@ -13,6 +14,7 @@ use Magento\Framework\EntityManager\EntityManager; use Magento\Framework\EntityManager\MetadataPool; use Magento\AsynchronousOperations\Model\ResourceModel\Operation\CollectionFactory; +use Magento\Authorization\Model\UserContextInterface; /** * Class BulkManagement @@ -51,6 +53,11 @@ class BulkManagement implements \Magento\Framework\Bulk\BulkManagementInterface */ private $resourceConnection; + /** + * @var \Magento\Authorization\Model\UserContextInterface + */ + private $userContext; + /** * @var \Psr\Log\LoggerInterface */ @@ -65,6 +72,7 @@ class BulkManagement implements \Magento\Framework\Bulk\BulkManagementInterface * @param MetadataPool $metadataPool * @param ResourceConnection $resourceConnection * @param \Psr\Log\LoggerInterface $logger + * @param UserContextInterface $userContext */ public function __construct( EntityManager $entityManager, @@ -73,7 +81,8 @@ public function __construct( BulkPublisherInterface $publisher, MetadataPool $metadataPool, ResourceConnection $resourceConnection, - \Psr\Log\LoggerInterface $logger + \Psr\Log\LoggerInterface $logger, + UserContextInterface $userContext = null ) { $this->entityManager = $entityManager; $this->bulkSummaryFactory= $bulkSummaryFactory; @@ -82,6 +91,7 @@ public function __construct( $this->resourceConnection = $resourceConnection; $this->publisher = $publisher; $this->logger = $logger; + $this->userContext = $userContext ?: ObjectManager::getInstance()->get(UserContextInterface::class); } /** @@ -93,6 +103,10 @@ public function scheduleBulk($bulkUuid, array $operations, $description, $userId $connection = $this->resourceConnection->getConnectionByName($metadata->getEntityConnectionName()); // save bulk summary and related operations $connection->beginTransaction(); + $userType = $this->userContext->getUserType(); + if ($userType === null) { + $userType = UserContextInterface::USER_TYPE_ADMIN; + } try { /** @var \Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface $bulkSummary */ $bulkSummary = $this->bulkSummaryFactory->create(); @@ -100,6 +114,7 @@ public function scheduleBulk($bulkUuid, array $operations, $description, $userId $bulkSummary->setBulkId($bulkUuid); $bulkSummary->setDescription($description); $bulkSummary->setUserId($userId); + $bulkSummary->setUserType($userType); $bulkSummary->setOperationCount((int)$bulkSummary->getOperationCount() + count($operations)); $this->entityManager->save($bulkSummary); diff --git a/app/code/Magento/AsynchronousOperations/Model/BulkSummary.php b/app/code/Magento/AsynchronousOperations/Model/BulkSummary.php index e99233d076957..1d834ad10b2e7 100644 --- a/app/code/Magento/AsynchronousOperations/Model/BulkSummary.php +++ b/app/code/Magento/AsynchronousOperations/Model/BulkSummary.php @@ -78,6 +78,22 @@ public function setUserId($userId) return $this->setData(self::USER_ID, $userId); } + /** + * @inheritDoc + */ + public function getUserType() + { + return $this->getData(self::USER_TYPE); + } + + /** + * @inheritDoc + */ + public function setUserType($userType) + { + return $this->setData(self::USER_TYPE, $userType); + } + /** * @inheritDoc */ diff --git a/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php b/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php index 2d516e82f4016..eae92e1663fc8 100644 --- a/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php +++ b/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php @@ -8,6 +8,7 @@ namespace Magento\AsynchronousOperations\Model; +use Magento\Framework\App\ObjectManager; use Magento\Framework\DataObject\IdentityGeneratorInterface; use Magento\Framework\Exception\LocalizedException; use Magento\AsynchronousOperations\Api\Data\ItemStatusInterfaceFactory; @@ -18,9 +19,12 @@ use Magento\Framework\Exception\BulkException; use Psr\Log\LoggerInterface; use Magento\AsynchronousOperations\Model\ResourceModel\Operation\OperationRepository; +use Magento\Authorization\Model\UserContextInterface; /** * Class MassSchedule used for adding multiple entities as Operations to Bulk Management with the status tracking + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) Suppressed without refactoring to not introduce BiC */ class MassSchedule { @@ -54,6 +58,11 @@ class MassSchedule */ private $operationRepository; + /** + * @var \Magento\Authorization\Model\UserContextInterface + */ + private $userContext; + /** * Initialize dependencies. * @@ -63,6 +72,7 @@ class MassSchedule * @param BulkManagementInterface $bulkManagement * @param LoggerInterface $logger * @param OperationRepository $operationRepository + * @param UserContextInterface $userContext */ public function __construct( IdentityGeneratorInterface $identityService, @@ -70,7 +80,8 @@ public function __construct( AsyncResponseInterfaceFactory $asyncResponseFactory, BulkManagementInterface $bulkManagement, LoggerInterface $logger, - OperationRepository $operationRepository + OperationRepository $operationRepository, + UserContextInterface $userContext = null ) { $this->identityService = $identityService; $this->itemStatusInterfaceFactory = $itemStatusInterfaceFactory; @@ -78,15 +89,16 @@ public function __construct( $this->bulkManagement = $bulkManagement; $this->logger = $logger; $this->operationRepository = $operationRepository; + $this->userContext = $userContext ?: ObjectManager::getInstance()->get(UserContextInterface::class); } /** * Schedule new bulk operation based on the list of entities * - * @param $topicName - * @param $entitiesArray - * @param null $groupId - * @param null $userId + * @param string $topicName + * @param array $entitiesArray + * @param string $groupId + * @param string $userId * @return AsyncResponseInterface * @throws BulkException * @throws LocalizedException @@ -95,6 +107,10 @@ public function publishMass($topicName, array $entitiesArray, $groupId = null, $ { $bulkDescription = __('Topic %1', $topicName); + if ($userId == null) { + $userId = $this->userContext->getUserId(); + } + if ($groupId == null) { $groupId = $this->identityService->generateId(); diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkManagementTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkManagementTest.php index 3a45c34df17f8..e5951fb129e7e 100644 --- a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkManagementTest.php +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkManagementTest.php @@ -108,6 +108,7 @@ public function testScheduleBulk() $bulkUuid = 'bulk-001'; $description = 'Bulk summary description...'; $userId = 1; + $userType = \Magento\Authorization\Model\UserContextInterface::USER_TYPE_ADMIN; $connectionName = 'default'; $topicNames = ['topic.name.0', 'topic.name.1']; $operation = $this->getMockBuilder(\Magento\AsynchronousOperations\Api\Data\OperationInterface::class) @@ -131,6 +132,7 @@ public function testScheduleBulk() $bulkSummary->expects($this->once())->method('setBulkId')->with($bulkUuid)->willReturnSelf(); $bulkSummary->expects($this->once())->method('setDescription')->with($description)->willReturnSelf(); $bulkSummary->expects($this->once())->method('setUserId')->with($userId)->willReturnSelf(); + $bulkSummary->expects($this->once())->method('setUserType')->with($userType)->willReturnSelf(); $bulkSummary->expects($this->once())->method('getOperationCount')->willReturn(1); $bulkSummary->expects($this->once())->method('setOperationCount')->with(3)->willReturnSelf(); $this->entityManager->expects($this->once())->method('save')->with($bulkSummary)->willReturn($bulkSummary); diff --git a/app/code/Magento/AsynchronousOperations/composer.json b/app/code/Magento/AsynchronousOperations/composer.json index 7d5a097eeea3f..18927b5f4ecca 100644 --- a/app/code/Magento/AsynchronousOperations/composer.json +++ b/app/code/Magento/AsynchronousOperations/composer.json @@ -10,7 +10,6 @@ "magento/module-authorization": "*", "magento/module-backend": "*", "magento/module-ui": "*", - "magento/module-user": "*", "php": "~7.1.3||~7.2.0" }, "suggest": { diff --git a/app/code/Magento/AsynchronousOperations/etc/db_schema.xml b/app/code/Magento/AsynchronousOperations/etc/db_schema.xml index 1b99ce9a2805f..342326e6666f1 100644 --- a/app/code/Magento/AsynchronousOperations/etc/db_schema.xml +++ b/app/code/Magento/AsynchronousOperations/etc/db_schema.xml @@ -14,7 +14,8 @@ + comment="ID of the WebAPI user that performed an action"/> + @@ -23,11 +24,12 @@ - + + + routeConfig->getRouteByFrontName($match[1]); return \Magento\Backend\Model\UrlInterface::SECRET_KEY_PARAM_NAME . '/' . $this->_url->getSecretKey( - $routeId, + $routeId ?: $match[1], $match[2], $match[3] ); @@ -354,7 +356,7 @@ protected function _columnBrake($items, $limit) * @param \Magento\Backend\Model\Menu\Item $menuItem * @param int $level * @param int $limit - * @param $id int + * @param int|null $id * @return string HTML code */ protected function _addSubMenu($menuItem, $level, $limit, $id = null) diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminMenuSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminMenuSection.xml index 9d3182b6236a4..9e4a6d9219526 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminMenuSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminMenuSection.xml @@ -7,9 +7,10 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
+ diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminMenuNavigationWithSecretKeysTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminMenuNavigationWithSecretKeysTest.xml new file mode 100644 index 0000000000000..c9a3b8089cc1d --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminMenuNavigationWithSecretKeysTest.xml @@ -0,0 +1,50 @@ + + + + + + + + + + <description value="Admin should be able to navigate between menu options with secret url keys enabled"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-95349"/> + <group value="menu"/> + </annotations> + <before> + <magentoCLI command="config:set admin/security/use_form_key 1" stepKey="enableUrlSecretKeys"/> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches1"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <magentoCLI command="config:set admin/security/use_form_key 0" stepKey="disableUrlSecretKeys"/> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches2"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <click selector="{{AdminMenuSection.stores}}" stepKey="clickStoresMenuOption1"/> + <waitForLoadingMaskToDisappear stepKey="waitForStoresMenu1" /> + <click selector="{{AdminMenuSection.configuration}}" stepKey="clickStoresConfigurationMenuOption1"/> + <waitForPageLoad stepKey="waitForConfigurationPageLoad1"/> + <seeCurrentUrlMatches regex="~\/admin\/system_config\/~" stepKey="seeCurrentUrlMatchesConfigPath1"/> + + <click selector="{{AdminMenuSection.catalog}}" stepKey="clickCatalogMenuOption"/> + <waitForLoadingMaskToDisappear stepKey="waitForCatalogMenu1" /> + <click selector="{{AdminMenuSection.catalogProducts}}" stepKey="clickCatalogProductsMenuOption"/> + <waitForPageLoad stepKey="waitForProductsPageLoad"/> + <seeCurrentUrlMatches regex="~\/catalog\/product\/~" stepKey="seeCurrentUrlMatchesProductsPath"/> + + <click selector="{{AdminMenuSection.stores}}" stepKey="clickStoresMenuOption2"/> + <waitForLoadingMaskToDisappear stepKey="waitForStoresMenu2" /> + <click selector="{{AdminMenuSection.configuration}}" stepKey="clickStoresConfigurationMenuOption2"/> + <waitForPageLoad stepKey="waitForConfigurationPageLoad2"/> + <seeCurrentUrlMatches regex="~\/admin\/system_config\/~" stepKey="seeCurrentUrlMatchesConfigPath2"/> + </test> +</tests> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfigurationPaymentSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfigurationPaymentSection.xml index d8e1b837a1d1b..d9f2b14a40e2f 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfigurationPaymentSection.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfigurationPaymentSection.xml @@ -10,6 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <section name="BraintreeConfigurationPaymentSection"> <element name="creditCart" type="radio" selector="#braintree"/> + <element name="paymentMethodContainer" type="block" selector=".payment-method-braintree"/> <element name="paymentMethod" type="radio" selector="//div[@class='payment-group']//input[contains(@id, 'braintree_cc_vault_')]"/> <element name="cartFrame" type="iframe" selector="braintree-hosted-field-number"/> <element name="monthFrame" type="iframe" selector="braintree-hosted-field-expirationMonth"/> diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml b/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml index 7f6d862ada085..114c79189101a 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml @@ -61,10 +61,12 @@ <!--Fill cart data--> <click selector="{{BraintreeConfigurationPaymentSection.creditCart}}" stepKey="SelectBraintreePaymentMethod"/> <waitForPageLoad stepKey="waitForPageLoad3"/> + <scrollTo selector="{{BraintreeConfigurationPaymentSection.creditCart}}" stepKey="ScrollToCreditCardSection"/> <actionGroup ref="StorefrontFillCartDataActionGroup" stepKey="StorefrontFillCartDataActionGroup"/> <waitForPageLoad stepKey="waitForPageLoad4"/> <!--Place order--> - <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="PlaceOrder"/> + <click selector="{{BraintreeConfigurationPaymentSection.paymentMethodContainer}} {{CheckoutPaymentSection.placeOrder}}" + stepKey="PlaceOrder"/> <waitForPageLoad stepKey="waitForPageLoad5"/> <!--Add product to cart again--> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml index 0fb8a7b88250c..5b2b771434b73 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml @@ -17,9 +17,6 @@ <severity value="CRITICAL"/> <testCaseId value="MC-217"/> <group value="Bundle"/> - <skip> - <issueId value="MAGETWO-92898"/> - </skip> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml index 71a876bbbcdbf..beb2c2bffa135 100644 --- a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml @@ -13,7 +13,7 @@ <waitForPageLoad stepKey="waitForStoresLoaded"/> <click selector="{{CaptchaFormsDisplayingSection.config}}" stepKey="ClickToGoConfiguration"/> <waitForPageLoad stepKey="waitForConfigurationsLoaded"/> - <scrollTo selector="{{CaptchaFormsDisplayingSection.customer}}" stepKey="ScrollToCustomers"/> + <scrollTo selector="{{CaptchaFormsDisplayingSection.customer}}" x="0" y="-80" stepKey="ScrollToCustomers"/> <click selector="{{CaptchaFormsDisplayingSection.customer}}" stepKey="ClickToCustomers"/> <waitForPageLoad stepKey="waitForCustomerConfigurationsLoaded"/> <click selector="{{CaptchaFormsDisplayingSection.customerConfig}}" stepKey="ClickToGoCustomerConfiguration"/> diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php index 84ad6d2116726..985d812fc742b 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php @@ -14,6 +14,7 @@ use Magento\Catalog\Controller\Adminhtml\Product\Attribute; use Magento\Catalog\Helper\Product; use Magento\Catalog\Model\Product\Attribute\Frontend\Inputtype\Presentation; +use Magento\Framework\Serialize\Serializer\FormData; use Magento\Catalog\Model\Product\AttributeSet\BuildFactory; use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory; use Magento\Eav\Model\Adminhtml\System\Config\Source\Inputtype\Validator; @@ -32,6 +33,8 @@ use Magento\Framework\View\Result\PageFactory; /** + * Product attribute save controller. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Save extends Attribute implements HttpPostActionInterface @@ -76,6 +79,11 @@ class Save extends Attribute implements HttpPostActionInterface */ private $presentation; + /** + * @var FormData|null + */ + private $formDataSerializer; + /** * @param Context $context * @param FrontendInterface $attributeLabelCache @@ -89,6 +97,7 @@ class Save extends Attribute implements HttpPostActionInterface * @param Product $productHelper * @param LayoutFactory $layoutFactory * @param Presentation|null $presentation + * @param FormData|null $formDataSerializer * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -103,7 +112,8 @@ public function __construct( FilterManager $filterManager, Product $productHelper, LayoutFactory $layoutFactory, - Presentation $presentation = null + Presentation $presentation = null, + FormData $formDataSerializer = null ) { parent::__construct($context, $attributeLabelCache, $coreRegistry, $resultPageFactory); $this->buildFactory = $buildFactory; @@ -114,19 +124,38 @@ public function __construct( $this->groupCollectionFactory = $groupCollectionFactory; $this->layoutFactory = $layoutFactory; $this->presentation = $presentation ?: ObjectManager::getInstance()->get(Presentation::class); + $this->formDataSerializer = $formDataSerializer + ?: ObjectManager::getInstance()->get(FormData::class); } /** + * @inheritdoc + * * @return Redirect * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @throws \Zend_Validate_Exception */ public function execute() { + try { + $optionData = $this->formDataSerializer + ->unserialize($this->getRequest()->getParam('serialized_options', '[]')); + } catch (\InvalidArgumentException $e) { + $message = __("The attribute couldn't be saved due to an error. Verify your information and try again. " + . "If the error persists, please try again later."); + $this->messageManager->addErrorMessage($message); + return $this->returnResult('catalog/*/edit', ['_current' => true], ['error' => true]); + } + $data = $this->getRequest()->getPostValue(); + $data = array_replace_recursive( + $data, + $optionData + ); + if ($data) { - $this->preprocessOptionsData($data); $setId = $this->getRequest()->getParam('set'); $attributeSet = null; @@ -135,7 +164,7 @@ public function execute() $name = trim($name); try { - /** @var $attributeSet Set */ + /** @var Set $attributeSet */ $attributeSet = $this->buildFactory->create() ->setEntityTypeId($this->_entityTypeId) ->setSkeletonId($setId) @@ -157,7 +186,7 @@ public function execute() $attributeId = $this->getRequest()->getParam('attribute_id'); - /** @var $model ProductAttributeInterface */ + /** @var ProductAttributeInterface $model */ $model = $this->attributeFactory->create(); if ($attributeId) { $model->load($attributeId); @@ -189,7 +218,7 @@ public function execute() //validate frontend_input if (isset($data['frontend_input'])) { - /** @var $inputType Validator */ + /** @var Validator $inputType */ $inputType = $this->validatorFactory->create(); if (!$inputType->isValid($data['frontend_input'])) { foreach ($inputType->getMessages() as $message) { @@ -317,28 +346,8 @@ public function execute() } /** - * Extract options data from serialized options field and append to data array. + * Provides an initialized Result object. * - * This logic is required to overcome max_input_vars php limit - * that may vary and/or be inaccessible to change on different instances. - * - * @param array $data - * @return void - */ - private function preprocessOptionsData(&$data) - { - if (isset($data['serialized_options'])) { - $serializedOptions = json_decode($data['serialized_options'], JSON_OBJECT_AS_ARRAY); - foreach ($serializedOptions as $serializedOption) { - $option = []; - parse_str($serializedOption, $option); - $data = array_replace_recursive($data, $option); - } - } - unset($data['serialized_options']); - } - - /** * @param string $path * @param array $params * @param array $response diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php index dac92f9b2eb9e..8a9d0c9b612cb 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php @@ -7,11 +7,18 @@ namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute; +use Magento\Framework\Serialize\Serializer\FormData; use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\DataObject; use Magento\Catalog\Controller\Adminhtml\Product\Attribute as AttributeAction; +/** + * Product attribute validate controller. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class Validate extends AttributeAction implements HttpGetActionInterface, HttpPostActionInterface { const DEFAULT_MESSAGE_KEY = 'message'; @@ -31,6 +38,11 @@ class Validate extends AttributeAction implements HttpGetActionInterface, HttpPo */ private $multipleAttributeList; + /** + * @var FormData|null + */ + private $formDataSerializer; + /** * Constructor * @@ -41,6 +53,7 @@ class Validate extends AttributeAction implements HttpGetActionInterface, HttpPo * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory * @param \Magento\Framework\View\LayoutFactory $layoutFactory * @param array $multipleAttributeList + * @param FormData|null $formDataSerializer */ public function __construct( \Magento\Backend\App\Action\Context $context, @@ -49,15 +62,20 @@ public function __construct( \Magento\Framework\View\Result\PageFactory $resultPageFactory, \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory, \Magento\Framework\View\LayoutFactory $layoutFactory, - array $multipleAttributeList = [] + array $multipleAttributeList = [], + FormData $formDataSerializer = null ) { parent::__construct($context, $attributeLabelCache, $coreRegistry, $resultPageFactory); $this->resultJsonFactory = $resultJsonFactory; $this->layoutFactory = $layoutFactory; $this->multipleAttributeList = $multipleAttributeList; + $this->formDataSerializer = $formDataSerializer ?: ObjectManager::getInstance() + ->get(FormData::class); } /** + * @inheritdoc + * * @return \Magento\Framework\Controller\ResultInterface * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -66,6 +84,15 @@ public function execute() { $response = new DataObject(); $response->setError(false); + try { + $optionsData = $this->formDataSerializer + ->unserialize($this->getRequest()->getParam('serialized_options', '[]')); + } catch (\InvalidArgumentException $e) { + $message = __("The attribute couldn't be validated due to an error. Verify your information and try again. " + . "If the error persists, please try again later."); + $this->setMessageToResponse($response, [$message]); + $response->setError(true); + } $attributeCode = $this->getRequest()->getParam('attribute_code'); $frontendLabel = $this->getRequest()->getParam('frontend_label'); @@ -105,10 +132,10 @@ public function execute() } $multipleOption = $this->getRequest()->getParam("frontend_input"); - $multipleOption = null == $multipleOption ? 'select' : $multipleOption; + $multipleOption = (null === $multipleOption) ? 'select' : $multipleOption; - if (isset($this->multipleAttributeList[$multipleOption]) && !(null == ($multipleOption))) { - $options = $this->getRequest()->getParam($this->multipleAttributeList[$multipleOption]); + if (isset($this->multipleAttributeList[$multipleOption])) { + $options = $optionsData[$this->multipleAttributeList[$multipleOption]] ?? null; $this->checkUniqueOption( $response, $options @@ -126,7 +153,8 @@ public function execute() } /** - * Throws Exception if not unique values into options + * Throws Exception if not unique values into options. + * * @param array $optionsValues * @param array $deletedOptions * @return bool @@ -160,6 +188,8 @@ private function setMessageToResponse($response, $messages) } /** + * Performs checking the uniqueness of the attribute options. + * * @param DataObject $response * @param array|null $options * @return $this diff --git a/app/code/Magento/Catalog/Model/Product/Media/Config.php b/app/code/Magento/Catalog/Model/Product/Media/Config.php index 72936d317399c..33af93db13b4c 100644 --- a/app/code/Magento/Catalog/Model/Product/Media/Config.php +++ b/app/code/Magento/Catalog/Model/Product/Media/Config.php @@ -10,11 +10,9 @@ use Magento\Store\Model\StoreManagerInterface; /** - * Catalog product media config + * Catalog product media config. * * @api - * - * @author Magento Core Team <core@magentocommerce.com> * @since 100.0.2 */ class Config implements ConfigInterface @@ -31,6 +29,11 @@ class Config implements ConfigInterface */ private $attributeHelper; + /** + * @var string[] + */ + private $mediaAttributeCodes; + /** * @param StoreManagerInterface $storeManager */ @@ -40,8 +43,7 @@ public function __construct(StoreManagerInterface $storeManager) } /** - * Filesystem directory path of product images - * relatively to media folder + * Get filesystem directory path for product images relative to the media directory. * * @return string */ @@ -51,8 +53,7 @@ public function getBaseMediaPathAddition() } /** - * Web-based directory path of product images - * relatively to media folder + * Get web-based directory path for product images relative to the media directory. * * @return string */ @@ -62,7 +63,7 @@ public function getBaseMediaUrlAddition() } /** - * @return string + * @inheritdoc */ public function getBaseMediaPath() { @@ -70,7 +71,7 @@ public function getBaseMediaPath() } /** - * @return string + * @inheritdoc */ public function getBaseMediaUrl() { @@ -79,8 +80,7 @@ public function getBaseMediaUrl() } /** - * Filesystem directory path of temporary product images - * relatively to media folder + * Filesystem directory path of temporary product images relative to the media directory. * * @return string */ @@ -90,6 +90,8 @@ public function getBaseTmpMediaPath() } /** + * Get temporary base media URL. + * * @return string */ public function getBaseTmpMediaUrl() @@ -100,8 +102,7 @@ public function getBaseTmpMediaUrl() } /** - * @param string $file - * @return string + * @inheritdoc */ public function getMediaUrl($file) { @@ -109,8 +110,7 @@ public function getMediaUrl($file) } /** - * @param string $file - * @return string + * @inheritdoc */ public function getMediaPath($file) { @@ -118,6 +118,8 @@ public function getMediaPath($file) } /** + * Get temporary media URL. + * * @param string $file * @return string */ @@ -127,8 +129,7 @@ public function getTmpMediaUrl($file) } /** - * Part of URL of temporary product images - * relatively to media folder + * Part of URL of temporary product images relative to the media directory. * * @param string $file * @return string @@ -139,7 +140,7 @@ public function getTmpMediaShortUrl($file) } /** - * Part of URL of product images relatively to media folder + * Part of URL of product images relatively to media folder. * * @param string $file * @return string @@ -150,6 +151,8 @@ public function getMediaShortUrl($file) } /** + * Get path to the temporary media. + * * @param string $file * @return string */ @@ -159,6 +162,8 @@ public function getTmpMediaPath($file) } /** + * Process file path. + * * @param string $file * @return string */ @@ -168,15 +173,23 @@ protected function _prepareFile($file) } /** + * Get codes of media attribute. + * * @return array * @since 100.0.4 */ public function getMediaAttributeCodes() { - return $this->getAttributeHelper()->getAttributeCodesByFrontendType('media_image'); + if (!isset($this->mediaAttributeCodes)) { + // the in-memory object-level caching allows to prevent unnecessary calls to the DB + $this->mediaAttributeCodes = $this->getAttributeHelper()->getAttributeCodesByFrontendType('media_image'); + } + return $this->mediaAttributeCodes; } /** + * Get attribute helper. + * * @return Attribute */ private function getAttributeHelper() diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml index fd80838692065..23687484f1012 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml @@ -14,7 +14,10 @@ </arguments> <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="navigateToProductAttributeGrid"/> <waitForPageLoad stepKey="waitForPageLoad1"/> - <click selector="{{AdminProductAttributeGridSection.AttributeCode(ProductAttribute.attribute_code)}}" stepKey="navigateToAttributeEditPage1" /> + <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" + userInput="{{ProductAttribute.attribute_code}}" stepKey="setAttributeCode"/> + <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="searchForAttributeFromTheGrid"/> + <click selector="{{AdminProductAttributeGridSection.FirstRow}}" stepKey="clickOnAttributeRow"/> <waitForPageLoad stepKey="waitForPageLoad2" /> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml index 9ae551b69d388..7c0cc03186c6e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -18,6 +18,7 @@ <data key="urlKey" unique="suffix">testurlkey</data> <data key="status">1</data> <data key="quantity">100</data> + <data key="weight">1</data> <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> </entity> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Framework/Metadata/image_content-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/image_content-meta.xml similarity index 100% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Framework/Metadata/image_content-meta.xml rename to app/code/Magento/Catalog/Test/Mftf/Metadata/image_content-meta.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml index b83676c2e1033..17778c76da9b9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml @@ -17,12 +17,14 @@ <element name="DefaultValue" type="input" selector="#default_value_text"/> <element name="Scope" type="select" selector="#is_global"/> <element name="Save" type="button" selector="#save" timeout="30"/> + <element name="DeleteAttribute" type="button" selector="#delete" timeout="30"/> <element name="SaveAndEdit" type="button" selector="#save_and_edit_button" timeout="30"/> <element name="TinyMCE4" type="button" selector="//span[text()='Default Value']/parent::label/following-sibling::div//div[@class='mce-branding-powered-by']"/> <element name="checkIfTabOpen" selector="//div[@id='advanced_fieldset-wrapper' and not(contains(@class,'opened'))]" type="button"/> <element name="useInLayeredNavigation" type="select" selector="#is_filterable"/> </section> <section name="StorefrontPropertiesSection"> + <element name="PageTitle" type="text" selector="//span[text()='Storefront Properties']" /> <element name="StoreFrontPropertiesTab" selector="#product_attribute_tabs_front" type="button"/> <element name="EnableWYSIWYG" type="select" selector="#enabled"/> <element name="useForPromoRuleConditions" type="select" selector="#is_used_for_promo_rules"/> @@ -63,4 +65,12 @@ <element name="SpecialCharacter" type="button" selector=".mce-i-charmap" /> <element name="TextArea" type="input" selector="#default_value_textarea" /> </section> + <section name="AdvancedAttributePropertiesSection"> + <element name="AdvancedAttributePropertiesSectionToggle" + type="button" selector="#advanced_fieldset-wrapper"/> + <element name="AttributeCode" type="text" selector="#attribute_code"/> + <element name="Scope" type="select" selector="#is_global"/> + <element name="AddToColumnOptions" type="select" selector="#is_used_in_grid"/> + <element name="UseInFilterOptions" type="select" selector="#is_filterable_in_grid"/> + </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFiltersSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFiltersSection.xml index ed08c84cdb6f8..7a9de9670f216 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFiltersSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFiltersSection.xml @@ -15,7 +15,7 @@ <element name="apply" type="button" selector="button[data-action=grid-filter-apply]" timeout="30"/> <element name="filter" type="button" selector="//div[@class='data-grid-filters-action-wrap']/button" timeout="30"/> <element name="typeDropDown" type="multiselect" selector="//select[@name='type_id']" timeout="30"/> - <element name="bundleOption" type="multiselect" selector="//select[@name='type_id']/option[4]" timeout="30"/> + <element name="bundleOption" type="multiselect" selector="//select[@name='type_id']/option[@value='bundle']" timeout="30"/> <element name="applyFilters" type="button" selector="//button[@class='action-secondary']" timeout="30"/> <element name="allCheckbox" type="checkbox" selector="//div[@data-role='grid-wrapper']//label[@data-bind='attr: {for: ko.uid}']" timeout="30"/> <element name="actions" type="button" selector="//div[@class='action-select-wrap']/button" timeout="30"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml index 51199300206a9..03df9d2a88107 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml @@ -106,24 +106,24 @@ <element name="Numlist" type="button" selector="//div[@id='editorproduct_form_description']//i[@class='mce-ico mce-i-bullist']" /> <element name="Bullet" type="button" selector="//div[@id='editorproduct_form_description']//i[@class='mce-ico mce-i-numlist']" /> <element name="InsertLink" type="button" selector="//div[@id='editorproduct_form_description']//i[@class='mce-ico mce-i-link']" /> - <element name="InsertImageIcon" type="button" selector="//div[@id='editorproduct_form_description']//i[@class='mce-ico mce-i-image']" /> + <element name="InsertImageIcon" type="button" selector="//div[@id='editorproduct_form_description']//i[@class='mce-ico mce-i-image']" timeout="30"/> <element name="InsertTable" type="button" selector="//div[@id='editorproduct_form_description']//i[@class='mce-ico mce-i-table']" /> <element name="SpecialCharacter" type="button" selector="//div[@id='editorproduct_form_description']//i[@class='mce-ico mce-i-charmap']" /> - <element name="Browse" type="button" selector=".mce-i-browse"/> - <element name="BrowseUploadImage" type="file" selector=".fileupload" /> + <element name="Browse" type="button" selector=".mce-i-browse" timeout="30"/> + <element name="BrowseUploadImage" type="file" selector=".fileupload" timeout="30"/> <element name="image" type="text" selector="//small[text()='{{var1}}']" parameterized="true"/> <element name="imageSelected" type="text" selector="//small[text()='{{var1}}']/parent::*[@class='filecnt selected']" parameterized="true"/> <element name="ImageSource" type="input" selector=".mce-combobox.mce-abs-layout-item.mce-last.mce-has-open" /> <element name="ImageDescription" type="input" selector=".mce-textbox.mce-abs-layout-item.mce-last" /> <element name="Height" type="input" selector=".mce-textbox.mce-abs-layout-item.mce-first" /> <element name="UploadImage" type="file" selector=".fileupload" /> - <element name="OkBtn" type="button" selector="//span[text()='Ok']"/> + <element name="OkBtn" type="button" selector="//button//span[text()='Ok']"/> <element name="InsertFile" type="text" selector="#insert_files" timeout="30"/> - <element name="CreateFolder" type="button" selector="#new_folder" /> - <element name="DeleteSelectedBtn" type="text" selector="#delete_files"/> - <element name="CancelBtn" type="button" selector="#cancel" /> + <element name="CreateFolder" type="button" selector="#new_folder" timeout="30"/> + <element name="DeleteSelectedBtn" type="text" selector="#delete_files" timeout="30"/> + <element name="CancelBtn" type="button" selector=".page-actions #cancel" /> <element name="FolderName" type="button" selector="input[data-role='promptField']" /> - <element name="AcceptFolderName" type="button" selector=".action-primary.action-accept" /> + <element name="AcceptFolderName" type="button" selector=".action-primary.action-accept" timeout="30"/> <element name="StorageRootArrow" type="button" selector="#root > .jstree-icon" /> <element name="checkIfArrowExpand" type="button" selector="//li[@id='root' and contains(@class,'jstree-closed')]" /> <element name="WysiwygArrow" type="button" selector="#d3lzaXd5Zw-- > .jstree-icon" /> @@ -144,21 +144,21 @@ <element name="Numlist" type="button" selector="//div[@id='editorproduct_form_short_description']//i[@class='mce-ico mce-i-bullist']" /> <element name="Bullet" type="button" selector="//div[@id='editorproduct_form_short_description']//i[@class='mce-ico mce-i-numlist']" /> <element name="InsertLink" type="button" selector="//div[@id='editorproduct_form_short_description']//i[@class='mce-ico mce-i-link']" /> - <element name="InsertImageIcon" type="button" selector="//div[@id='editorproduct_form_short_description']//i[@class='mce-ico mce-i-image']" /> + <element name="InsertImageIcon" type="button" selector="//div[@id='editorproduct_form_short_description']//i[@class='mce-ico mce-i-image']" timeout="30"/> <element name="InsertTable" type="button" selector="//div[@id='editorproduct_form_short_description']//i[@class='mce-ico mce-i-table']" /> <element name="SpecialCharacter" type="button" selector="//div[@id='editorproduct_form_short_description']//i[@class='mce-ico mce-i-charmap']"/> <element name="Browse" type="button" selector=".mce-i-browse"/> - <element name="BrowseUploadImage" type="file" selector=".fileupload" /> + <element name="BrowseUploadImage" type="file" selector=".fileupload" timeout="30" /> <element name="image" type="text" selector="//small[text()='{{var1}}']" parameterized="true"/> <element name="imageSelected" type="text" selector="//small[text()='{{var1}}']/parent::*[@class='filecnt selected']" parameterized="true"/> <element name="ImageSource" type="input" selector=".mce-combobox.mce-abs-layout-item.mce-last.mce-has-open" /> <element name="ImageDescription" type="input" selector=".mce-textbox.mce-abs-layout-item.mce-last" /> <element name="Height" type="input" selector=".mce-textbox.mce-abs-layout-item.mce-first" /> <element name="UploadImage" type="file" selector=".fileupload" /> - <element name="OkBtn" type="button" selector="//span[text()='Ok']"/> + <element name="OkBtn" type="button" selector="//span[text()='Ok']" timeout="30"/> <element name="InsertFile" type="text" selector="#insert_files"/> <element name="CreateFolder" type="button" selector="#new_folder" /> - <element name="DeleteSelectedBtn" type="text" selector="#delete_files"/> + <element name="DeleteSelectedBtn" type="text" selector="#delete_files" timeout="30"/> <element name="CancelBtn" type="button" selector="#cancel" /> <element name="FolderName" type="button" selector="input[data-role='promptField']" /> <element name="AcceptFolderName" type="button" selector=".action-primary.action-accept" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml index bd1c057133d83..d8f6bc3e4a749 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml @@ -25,73 +25,69 @@ <actionGroup ref="EnabledWYSIWYG" stepKey="enableWYSIWYG"/> <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> </before> + <after> + <actionGroup ref="DisabledWYSIWYG" stepKey="disableWYSIWYG"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <amOnPage url="{{AdminProductCreatePage.url(AddToDefaultSet.attributeSetId, 'simple')}}" stepKey="navigateToNewProduct"/> - <waitForPageLoad stepKey="waitForPageLoad"/> - <fillField userInput="{{_defaultProduct.name}}" selector="{{AdminProductFormSection.productName}}" stepKey="fillName"/> - <fillField userInput="{{_defaultProduct.price}}" selector="{{AdminProductFormSection.productPrice}}" stepKey="fillPrice"/> - <fillField userInput="{{_defaultProduct.sku}}" selector="{{AdminProductFormSection.productSku}}" stepKey="fillSKU"/> - <fillField userInput="{{_defaultProduct.quantity}}" selector="{{AdminProductFormSection.productQuantity}}" stepKey="fillQuantity"/> - <scrollTo selector="{{AdminProductFormSection.productQuantity}}" stepKey="scrollToQty" /> + <waitForPageLoad stepKey="waitForPageLoadProductCreatePage"/> + <actionGroup ref="fillMainProductForm" stepKey="fillBasicProductInfo" /> + <click selector="{{AdminProductFormSection.contentTab}}" stepKey="clickContentTab" /> <waitForElementVisible selector="{{ProductDescriptionWYSIWYGToolbarSection.TinyMCE4}}" stepKey="waitForDescription" /> <click selector="{{ProductDescriptionWYSIWYGToolbarSection.InsertImageIcon}}" stepKey="clickInsertImageIcon1" /> - <waitForPageLoad stepKey="waitForPageLoad1" /> <click selector="{{ProductDescriptionWYSIWYGToolbarSection.Browse}}" stepKey="clickBrowse1" /> - <waitForPageLoad stepKey="waitForPageLoad2" /> - <waitForLoadingMaskToDisappear stepKey="waitForLoading1" /> - <waitForLoadingMaskToDisappear stepKey="waitForLoading2" /> + <waitForLoadingMaskToDisappear stepKey="waitForBrowseModal" /> <see selector="{{ProductDescriptionWYSIWYGToolbarSection.CancelBtn}}" userInput="Cancel" stepKey="seeCancelBtn1" /> <see selector="{{ProductDescriptionWYSIWYGToolbarSection.CreateFolder}}" userInput="Create Folder" stepKey="seeCreateFolderBtn1" /> - <click selector="{{ProductDescriptionWYSIWYGToolbarSection.CreateFolder}}" stepKey="createFolder1"/> - <waitForElementVisible selector="{{ProductDescriptionWYSIWYGToolbarSection.FolderName}}" stepKey="waitForPopUp1" /> + <dontSeeElement selector="{{ProductDescriptionWYSIWYGToolbarSection.InsertFile}}" stepKey="dontSeeAddSelectedBtn1" /> + <click selector="{{ProductDescriptionWYSIWYGToolbarSection.CreateFolder}}" stepKey="createFolder1" /> + <waitForElement selector="{{ProductDescriptionWYSIWYGToolbarSection.FolderName}}" stepKey="waitForPopUp1" /> <fillField selector="{{ProductDescriptionWYSIWYGToolbarSection.FolderName}}" userInput="{{ImageFolder.name}}" stepKey="fillFolderName1" /> <click selector="{{ProductDescriptionWYSIWYGToolbarSection.AcceptFolderName}}" stepKey="acceptFolderName11" /> - <waitForLoadingMaskToDisappear stepKey="waitForLoading3" /> <conditionalClick selector="{{ProductDescriptionWYSIWYGToolbarSection.StorageRootArrow}}" dependentSelector="{{ProductDescriptionWYSIWYGToolbarSection.checkIfArrowExpand}}" stepKey="clickStorageRootArrowIfClosed" visible="true"/> <conditionalClick selector="{{ProductDescriptionWYSIWYGToolbarSection.WysiwygArrow}}" dependentSelector="{{ProductDescriptionWYSIWYGToolbarSection.checkIfWysiwygArrowExpand}}" stepKey="clickWysiwygArrowIfClosed" visible="true"/> <waitForText userInput="{{ImageFolder.name}}" stepKey="waitForNewFolder1" /> <click userInput="{{ImageFolder.name}}" stepKey="clickOnCreatedFolder1" /> <waitForLoadingMaskToDisappear stepKey="waitForLoading4" /> <attachFile selector="{{ProductDescriptionWYSIWYGToolbarSection.BrowseUploadImage}}" userInput="{{ImageUpload1.value}}" stepKey="uploadImage1"/> - <waitForLoadingMaskToDisappear stepKey="waitForLoading5" /> + <waitForLoadingMaskToDisappear stepKey="waitForFileUpload1" /> <waitForElementVisible selector="{{ProductDescriptionWYSIWYGToolbarSection.image(ImageUpload1.value)}}" stepKey="waitForUploadImage1" /> <seeElement selector="{{ProductDescriptionWYSIWYGToolbarSection.imageSelected(ImageUpload1.value)}}" stepKey="seeImageSelected1" /> <see selector="{{ProductDescriptionWYSIWYGToolbarSection.DeleteSelectedBtn}}" userInput="Delete Selected" stepKey="seeDeleteBtn1"/> <click selector="{{ProductDescriptionWYSIWYGToolbarSection.DeleteSelectedBtn}}" stepKey="clickDeleteSelected1" /> - <waitForText userInput="OK" stepKey="waitForConfirm1" /> - <click selector="{{ProductDescriptionWYSIWYGToolbarSection.confirmDelete}}" stepKey="confirmDelete1" /> + <waitForElementVisible selector="{{AdminConfirmationModalSection.ok}}" stepKey="waitForConfirmDelete1"/> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmDelete1" /> <waitForElementNotVisible selector="{{ProductDescriptionWYSIWYGToolbarSection.image(ImageUpload1.value)}}" stepKey="waitForImageDeleted1" /> <dontSeeElement selector="{{ProductDescriptionWYSIWYGToolbarSection.image(ImageUpload1.value)}}" stepKey="dontSeeImage1" /> + <dontSeeElement selector="{{ProductDescriptionWYSIWYGToolbarSection.InsertFile}}" stepKey="dontSeeAddSelectedBtn2" /> <attachFile selector="{{ProductDescriptionWYSIWYGToolbarSection.BrowseUploadImage}}" userInput="{{ImageUpload1.value}}" stepKey="uploadImage2"/> - <waitForLoadingMaskToDisappear stepKey="waitForLoading6" /> + <waitForLoadingMaskToDisappear stepKey="waitForFileUpload2" /> <waitForElementVisible selector="{{ProductDescriptionWYSIWYGToolbarSection.image(ImageUpload1.value)}}" stepKey="waitForUploadImage2" /> <click selector="{{ProductDescriptionWYSIWYGToolbarSection.InsertFile}}" stepKey="clickInsertBtn1" /> - <waitForLoadingMaskToDisappear stepKey="waitForLoading7" /> - <waitForElementVisible selector="{{ProductDescriptionWYSIWYGToolbarSection.OkBtn}}" stepKey="waitForOkBtn1" /> + <waitForElementVisible selector="{{ProductDescriptionWYSIWYGToolbarSection.ImageDescription}}" stepKey="waitForImageDescriptionButton1" /> <fillField selector="{{ProductDescriptionWYSIWYGToolbarSection.ImageDescription}}" userInput="{{ImageUpload1.content}}" stepKey="fillImageDescription1" /> <fillField selector="{{ProductDescriptionWYSIWYGToolbarSection.Height}}" userInput="{{ImageUpload1.height}}" stepKey="fillImageHeight1" /> <click selector="{{ProductDescriptionWYSIWYGToolbarSection.OkBtn}}" stepKey="clickOkBtn1" /> - <waitForPageLoad stepKey="waitForPageLoad3"/> <scrollTo selector="{{ProductDescriptionWYSIWYGToolbarSection.TinyMCE4}}" stepKey="scrollToTinyMCE4" /> <click selector="{{ProductShortDescriptionWYSIWYGToolbarSection.InsertImageIcon}}" stepKey="clickInsertImageIcon2" /> - <waitForPageLoad stepKey="waitForPageLoad4" /> <click selector="{{ProductShortDescriptionWYSIWYGToolbarSection.Browse}}" stepKey="clickBrowse2" /> - <waitForPageLoad stepKey="waitForPageLoad5" /> - <waitForLoadingMaskToDisappear stepKey="waitForLoading8" /> + <waitForElementVisible selector="{{ProductDescriptionWYSIWYGToolbarSection.CancelBtn}}" stepKey="waitForCancelButton2"/> <see selector="{{ProductShortDescriptionWYSIWYGToolbarSection.CancelBtn}}" userInput="Cancel" stepKey="seeCancelBtn2" /> <see selector="{{ProductShortDescriptionWYSIWYGToolbarSection.CreateFolder}}" userInput="Create Folder" stepKey="seeCreateFolderBtn2" /> - <dontSeeElement selector="{{ProductShortDescriptionWYSIWYGToolbarSection.InsertFile}}" stepKey="dontSeeAddSelectedBtn2" /> + <dontSeeElement selector="{{ProductShortDescriptionWYSIWYGToolbarSection.InsertFile}}" stepKey="dontSeeAddSelectedBtn3" /> <attachFile selector="{{ProductShortDescriptionWYSIWYGToolbarSection.BrowseUploadImage}}" userInput="{{ImageUpload3.value}}" stepKey="uploadImage3"/> + <waitForLoadingMaskToDisappear stepKey="waitForFileUpload3" /> <waitForElementVisible selector="{{ProductShortDescriptionWYSIWYGToolbarSection.image(ImageUpload3.value)}}" stepKey="waitForUploadImage3" /> - <waitForLoadingMaskToDisappear stepKey="waitForLoading9" /> - <wait time="3" stepKey="waitMore" /> <waitForElement selector="{{ProductShortDescriptionWYSIWYGToolbarSection.DeleteSelectedBtn}}" stepKey="waitForDeletebtn" /> <see selector="{{ProductShortDescriptionWYSIWYGToolbarSection.DeleteSelectedBtn}}" userInput="Delete Selected" stepKey="seeDeleteBtn2"/> <click selector="{{ProductShortDescriptionWYSIWYGToolbarSection.DeleteSelectedBtn}}" stepKey="clickDeleteSelected2" /> - <waitForText userInput="OK" stepKey="waitForConfirm3" /> - <click selector="{{ProductShortDescriptionWYSIWYGToolbarSection.confirmDelete}}" stepKey="confirmDelete2" /> + <waitForElementVisible selector="{{AdminConfirmationModalSection.ok}}" stepKey="waitForConfirm3"/> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmDelete2" /> + <dontSeeElement selector="{{ProductDescriptionWYSIWYGToolbarSection.InsertFile}}" stepKey="dontSeeAddSelectedBtn4" /> <attachFile selector="{{ProductShortDescriptionWYSIWYGToolbarSection.BrowseUploadImage}}" userInput="{{ImageUpload3.value}}" stepKey="uploadImage4"/> - <waitForLoadingMaskToDisappear stepKey="waitForLoading10" /> + <waitForLoadingMaskToDisappear stepKey="waitForFileUpload4" /> <waitForElementVisible selector="{{ProductShortDescriptionWYSIWYGToolbarSection.image(ImageUpload3.value)}}" stepKey="waitForUploadImage4" /> <click selector="{{ProductShortDescriptionWYSIWYGToolbarSection.InsertFile}}" stepKey="clickInsertBtn" /> <waitForLoadingMaskToDisappear stepKey="waitForLoading11" /> @@ -107,9 +103,5 @@ <seeElement selector="{{StorefrontProductInfoMainSection.mediaDescription}}" stepKey="assertMediaDescription"/> <seeElementInDOM selector="{{StorefrontCategoryMainSection.imageSource(ImageUpload3.fileName)}}" stepKey="assertMediaSource3"/> <seeElementInDOM selector="{{StorefrontCategoryMainSection.imageSource(ImageUpload1.fileName)}}" stepKey="assertMediaSource1"/> - <after> - <actionGroup ref="DisabledWYSIWYG" stepKey="disableWYSIWYG"/> - <actionGroup ref="logout" stepKey="logout"/> - </after> </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml index 7603400ba8dcd..e914b8c96d03e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml @@ -26,10 +26,9 @@ <requiredEntity createDataKey="myProductAttributeCreation"/> </createData> </before> - <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="navigateToProductAttributeGrid1"/> - <waitForPageLoad stepKey="waitForPageLoad1"/> - <click selector="{{AdminProductAttributeGridSection.AttributeCode($$myProductAttributeCreation.attribute_code$$)}}" stepKey="navigateToAttributeEditPage1" /> - <waitForPageLoad stepKey="waitForPageLoad2" /> + <actionGroup ref="navigateToCreatedProductAttribute" stepKey="navigateToAttribute"> + <argument name="ProductAttribute" value="productAttributeWysiwyg"/> + </actionGroup> <seeOptionIsSelected selector="{{AttributePropertiesSection.InputType}}" userInput="Text Editor" stepKey="seeTextEditorSelected" /> <see selector="{{AttributePropertiesSection.InputType}}" userInput="Text Area" stepKey="seeTextArea1" /> <selectOption selector="{{AttributePropertiesSection.InputType}}" userInput="Text Area" stepKey="selectTextArea" /> diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/SaveTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/SaveTest.php index f493cbc88f18e..ced65b2d2e15d 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/SaveTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/SaveTest.php @@ -5,7 +5,9 @@ */ namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\Attribute; +use Magento\Catalog\Api\Data\ProductAttributeInterface; use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save; +use Magento\Framework\Serialize\Serializer\FormData; use Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\AttributeTest; use Magento\Catalog\Model\Product\AttributeSet\BuildFactory; use Magento\Catalog\Model\Product\AttributeSet\Build; @@ -13,11 +15,14 @@ use Magento\Eav\Api\Data\AttributeSetInterface; use Magento\Eav\Model\Adminhtml\System\Config\Source\Inputtype\ValidatorFactory; use Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\CollectionFactory; +use Magento\Framework\Controller\ResultFactory; use Magento\Framework\Filter\FilterManager; use Magento\Catalog\Helper\Product as ProductHelper; +use Magento\Framework\View\Element\Messages; use Magento\Framework\View\LayoutFactory; use Magento\Backend\Model\View\Result\Redirect as ResultRedirect; use Magento\Eav\Model\Adminhtml\System\Config\Source\Inputtype\Validator as InputTypeValidator; +use Magento\Framework\View\LayoutInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -79,6 +84,16 @@ class SaveTest extends AttributeTest */ protected $inputTypeValidatorMock; + /** + * @var FormData|\PHPUnit_Framework_MockObject_MockObject + */ + private $formDataSerializerMock; + + /** + * @var ProductAttributeInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $productAttributeMock; + protected function setUp() { parent::setUp(); @@ -108,6 +123,7 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); $this->redirectMock = $this->getMockBuilder(ResultRedirect::class) + ->setMethods(['setData', 'setPath']) ->disableOriginalConstructor() ->getMock(); $this->attributeSetMock = $this->getMockBuilder(AttributeSetInterface::class) @@ -119,6 +135,12 @@ protected function setUp() $this->inputTypeValidatorMock = $this->getMockBuilder(InputTypeValidator::class) ->disableOriginalConstructor() ->getMock(); + $this->formDataSerializerMock = $this->getMockBuilder(FormData::class) + ->disableOriginalConstructor() + ->getMock(); + $this->productAttributeMock = $this->getMockBuilder(ProductAttributeInterface::class) + ->setMethods(['getId', 'get']) + ->getMockForAbstractClass(); $this->buildFactoryMock->expects($this->any()) ->method('create') @@ -126,6 +148,9 @@ protected function setUp() $this->validatorFactoryMock->expects($this->any()) ->method('create') ->willReturn($this->inputTypeValidatorMock); + $this->attributeFactoryMock + ->method('create') + ->willReturn($this->productAttributeMock); } /** @@ -145,11 +170,23 @@ protected function getModel() 'validatorFactory' => $this->validatorFactoryMock, 'groupCollectionFactory' => $this->groupCollectionFactoryMock, 'layoutFactory' => $this->layoutFactoryMock, + 'formDataSerializer' => $this->formDataSerializerMock, ]); } public function testExecuteWithEmptyData() { + $this->requestMock->expects($this->any()) + ->method('getParam') + ->willReturnMap([ + ['isAjax', null, null], + ['serialized_options', '[]', ''], + ]); + $this->formDataSerializerMock + ->expects($this->once()) + ->method('unserialize') + ->with('') + ->willReturn([]); $this->requestMock->expects($this->once()) ->method('getPostValue') ->willReturn([]); @@ -170,6 +207,23 @@ public function testExecute() 'frontend_input' => 'test_frontend_input', ]; + $this->requestMock->expects($this->any()) + ->method('getParam') + ->willReturnMap([ + ['isAjax', null, null], + ['serialized_options', '[]', ''], + ]); + $this->formDataSerializerMock + ->expects($this->once()) + ->method('unserialize') + ->with('') + ->willReturn([]); + $this->productAttributeMock + ->method('getId') + ->willReturn(1); + $this->productAttributeMock + ->method('getAttributeCode') + ->willReturn('test_code'); $this->requestMock->expects($this->once()) ->method('getPostValue') ->willReturn($data); @@ -203,4 +257,80 @@ public function testExecute() $this->assertInstanceOf(ResultRedirect::class, $this->getModel()->execute()); } + + /** + * @throws \Magento\Framework\Exception\NotFoundException + */ + public function testExecuteWithOptionsDataError() + { + $serializedOptions = '{"key":"value"}'; + $message = "The attribute couldn't be saved due to an error. Verify your information and try again. " + . "If the error persists, please try again later."; + + $this->requestMock->expects($this->any()) + ->method('getParam') + ->willReturnMap([ + ['isAjax', null, true], + ['serialized_options', '[]', $serializedOptions], + ]); + $this->formDataSerializerMock + ->expects($this->once()) + ->method('unserialize') + ->with($serializedOptions) + ->willThrowException(new \InvalidArgumentException('Some exception')); + $this->messageManager + ->expects($this->once()) + ->method('addErrorMessage') + ->with($message); + $this->addReturnResultConditions('catalog/*/edit', ['_current' => true], ['error' => true]); + + $this->getModel()->execute(); + } + + /** + * @param string $path + * @param array $params + * @param array $response + * @return mixed + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + private function addReturnResultConditions(string $path = '', array $params = [], array $response = []) + { + $layoutMock = $this->getMockBuilder(LayoutInterface::class) + ->setMethods(['initMessages', 'getMessagesBlock']) + ->getMockForAbstractClass(); + $this->layoutFactoryMock + ->expects($this->once()) + ->method('create') + ->with() + ->willReturn($layoutMock); + $layoutMock + ->method('initMessages') + ->with(); + $messageBlockMock = $this->getMockBuilder(Messages::class) + ->disableOriginalConstructor() + ->getMock(); + $layoutMock + ->expects($this->once()) + ->method('getMessagesBlock') + ->willReturn($messageBlockMock); + $messageBlockMock + ->expects($this->once()) + ->method('getGroupedHtml') + ->willReturn('message1'); + $this->resultFactoryMock + ->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_JSON) + ->willReturn($this->redirectMock); + $response = array_merge($response, [ + 'messages' => ['message1'], + 'params' => $params, + ]); + $this->redirectMock + ->expects($this->once()) + ->method('setData') + ->with($response) + ->willReturnSelf(); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php index 9c747393cc72a..c6210f93e1290 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php @@ -6,6 +6,7 @@ namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\Attribute; use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Validate; +use Magento\Framework\Serialize\Serializer\FormData; use Magento\Catalog\Model\ResourceModel\Eav\Attribute; use Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\AttributeTest; use Magento\Eav\Model\Entity\Attribute\Set as AttributeSet; @@ -61,6 +62,11 @@ class ValidateTest extends AttributeTest */ protected $layoutMock; + /** + * @var FormData|\PHPUnit_Framework_MockObject_MockObject + */ + private $formDataSerializerMock; + protected function setUp() { parent::setUp(); @@ -86,6 +92,9 @@ protected function setUp() ->getMock(); $this->layoutMock = $this->getMockBuilder(LayoutInterface::class) ->getMockForAbstractClass(); + $this->formDataSerializerMock = $this->getMockBuilder(FormData::class) + ->disableOriginalConstructor() + ->getMock(); $this->contextMock->expects($this->any()) ->method('getObjectManager') @@ -100,25 +109,28 @@ protected function getModel() return $this->objectManager->getObject( Validate::class, [ - 'context' => $this->contextMock, - 'attributeLabelCache' => $this->attributeLabelCacheMock, - 'coreRegistry' => $this->coreRegistryMock, - 'resultPageFactory' => $this->resultPageFactoryMock, - 'resultJsonFactory' => $this->resultJsonFactoryMock, - 'layoutFactory' => $this->layoutFactoryMock, - 'multipleAttributeList' => ['select' => 'option'] + 'context' => $this->contextMock, + 'attributeLabelCache' => $this->attributeLabelCacheMock, + 'coreRegistry' => $this->coreRegistryMock, + 'resultPageFactory' => $this->resultPageFactoryMock, + 'resultJsonFactory' => $this->resultJsonFactoryMock, + 'layoutFactory' => $this->layoutFactoryMock, + 'multipleAttributeList' => ['select' => 'option'], + 'formDataSerializer' => $this->formDataSerializerMock, ] ); } public function testExecute() { + $serializedOptions = '{"key":"value"}'; $this->requestMock->expects($this->any()) ->method('getParam') ->willReturnMap([ ['frontend_label', null, 'test_frontend_label'], ['attribute_code', null, 'test_attribute_code'], ['new_attribute_set_name', null, 'test_attribute_set_name'], + ['serialized_options', '[]', $serializedOptions], ]); $this->objectManagerMock->expects($this->exactly(2)) ->method('create') @@ -160,6 +172,7 @@ public function testExecute() */ public function testUniqueValidation(array $options, $isError) { + $serializedOptions = '{"key":"value"}'; $countFunctionCalls = ($isError) ? 6 : 5; $this->requestMock->expects($this->exactly($countFunctionCalls)) ->method('getParam') @@ -167,10 +180,16 @@ public function testUniqueValidation(array $options, $isError) ['frontend_label', null, null], ['attribute_code', null, "test_attribute_code"], ['new_attribute_set_name', null, 'test_attribute_set_name'], - ['option', null, $options], - ['message_key', null, Validate::DEFAULT_MESSAGE_KEY] + ['message_key', null, Validate::DEFAULT_MESSAGE_KEY], + ['serialized_options', '[]', $serializedOptions], ]); + $this->formDataSerializerMock + ->expects($this->once()) + ->method('unserialize') + ->with($serializedOptions) + ->willReturn($options); + $this->objectManagerMock->expects($this->once()) ->method('create') ->willReturn($this->attributeMock); @@ -203,67 +222,77 @@ public function provideUniqueData() return [ 'no values' => [ [ - 'delete' => [ - "option_0" => "", - "option_1" => "", - "option_2" => "", - ] + 'option' => [ + 'delete' => [ + "option_0" => "", + "option_1" => "", + "option_2" => "", + ], + ], ], false ], 'valid options' => [ [ - 'value' => [ - "option_0" => [1, 0], - "option_1" => [2, 0], - "option_2" => [3, 0], + 'option' => [ + 'value' => [ + "option_0" => [1, 0], + "option_1" => [2, 0], + "option_2" => [3, 0], + ], + 'delete' => [ + "option_0" => "", + "option_1" => "", + "option_2" => "", + ], ], - 'delete' => [ - "option_0" => "", - "option_1" => "", - "option_2" => "", - ] ], false ], 'duplicate options' => [ [ - 'value' => [ - "option_0" => [1, 0], - "option_1" => [1, 0], - "option_2" => [3, 0], + 'option' => [ + 'value' => [ + "option_0" => [1, 0], + "option_1" => [1, 0], + "option_2" => [3, 0], + ], + 'delete' => [ + "option_0" => "", + "option_1" => "", + "option_2" => "", + ], ], - 'delete' => [ - "option_0" => "", - "option_1" => "", - "option_2" => "", - ] ], true ], 'duplicate and deleted' => [ [ - 'value' => [ - "option_0" => [1, 0], - "option_1" => [1, 0], - "option_2" => [3, 0], + 'option' => [ + 'value' => [ + "option_0" => [1, 0], + "option_1" => [1, 0], + "option_2" => [3, 0], + ], + 'delete' => [ + "option_0" => "", + "option_1" => "1", + "option_2" => "", + ], ], - 'delete' => [ - "option_0" => "", - "option_1" => "1", - "option_2" => "", - ] ], false ], 'empty and deleted' => [ [ - 'value' => [ - "option_0" => [1, 0], - "option_1" => [2, 0], - "option_2" => ["", ""], + 'option' => [ + 'value' => [ + "option_0" => [1, 0], + "option_1" => [2, 0], + "option_2" => ["", ""], + ], + 'delete' => [ + "option_0" => "", + "option_1" => "", + "option_2" => "1", + ], ], - 'delete' => [ - "option_0" => "", - "option_1" => "", - "option_2" => "1", - ] ], false ], ]; @@ -278,6 +307,7 @@ public function provideUniqueData() */ public function testEmptyOption(array $options, $result) { + $serializedOptions = '{"key":"value"}'; $this->requestMock->expects($this->any()) ->method('getParam') ->willReturnMap([ @@ -285,10 +315,16 @@ public function testEmptyOption(array $options, $result) ['frontend_input', 'select', 'multipleselect'], ['attribute_code', null, "test_attribute_code"], ['new_attribute_set_name', null, 'test_attribute_set_name'], - ['option', null, $options], ['message_key', Validate::DEFAULT_MESSAGE_KEY, 'message'], + ['serialized_options', '[]', $serializedOptions], ]); + $this->formDataSerializerMock + ->expects($this->once()) + ->method('unserialize') + ->with($serializedOptions) + ->willReturn($options); + $this->objectManagerMock->expects($this->once()) ->method('create') ->willReturn($this->attributeMock); @@ -320,8 +356,10 @@ public function provideEmptyOption() return [ 'empty admin scope options' => [ [ - 'value' => [ - "option_0" => [''], + 'option' => [ + 'value' => [ + "option_0" => [''], + ], ], ], (object) [ @@ -331,8 +369,10 @@ public function provideEmptyOption() ], 'not empty admin scope options' => [ [ - 'value' => [ - "option_0" => ['asdads'], + 'option' => [ + 'value' => [ + "option_0" => ['asdads'], + ], ], ], (object) [ @@ -341,11 +381,13 @@ public function provideEmptyOption() ], 'empty admin scope options and deleted' => [ [ - 'value' => [ - "option_0" => [''], - ], - 'delete' => [ - 'option_0' => '1', + 'option' => [ + 'value' => [ + "option_0" => [''], + ], + 'delete' => [ + 'option_0' => '1', + ], ], ], (object) [ @@ -354,11 +396,13 @@ public function provideEmptyOption() ], 'empty admin scope options and not deleted' => [ [ - 'value' => [ - "option_0" => [''], - ], - 'delete' => [ - 'option_0' => '0', + 'option' => [ + 'value' => [ + "option_0" => [''], + ], + 'delete' => [ + 'option_0' => '0', + ], ], ], (object) [ @@ -368,4 +412,55 @@ public function provideEmptyOption() ], ]; } + + /** + * @throws \Magento\Framework\Exception\NotFoundException + */ + public function testExecuteWithOptionsDataError() + { + $serializedOptions = '{"key":"value"}'; + $message = "The attribute couldn't be validated due to an error. Verify your information and try again. " + . "If the error persists, please try again later."; + $this->requestMock->expects($this->any()) + ->method('getParam') + ->willReturnMap([ + ['frontend_label', null, 'test_frontend_label'], + ['attribute_code', null, 'test_attribute_code'], + ['new_attribute_set_name', null, 'test_attribute_set_name'], + ['message_key', Validate::DEFAULT_MESSAGE_KEY, 'message'], + ['serialized_options', '[]', $serializedOptions], + ]); + + $this->formDataSerializerMock + ->expects($this->once()) + ->method('unserialize') + ->with($serializedOptions) + ->willThrowException(new \InvalidArgumentException('Some exception')); + + $this->objectManagerMock + ->method('create') + ->willReturnMap([ + [\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class, [], $this->attributeMock], + [\Magento\Eav\Model\Entity\Attribute\Set::class, [], $this->attributeSetMock] + ]); + + $this->attributeMock + ->method('loadByCode') + ->willReturnSelf(); + $this->attributeSetMock + ->method('setEntityTypeId') + ->willReturnSelf(); + $this->resultJsonFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->resultJson); + $this->resultJson->expects($this->once()) + ->method('setJsonData') + ->with(json_encode([ + 'error' => true, + 'message' => $message + ])) + ->willReturnSelf(); + + $this->getModel()->execute(); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/AttributeTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/AttributeTest.php index b85b03852b621..2a75773754fca 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/AttributeTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/AttributeTest.php @@ -9,8 +9,10 @@ use Magento\Catalog\Controller\Adminhtml\Product\Attribute; use Magento\Framework\App\RequestInterface; use Magento\Framework\Cache\FrontendInterface; +use Magento\Framework\Message\ManagerInterface; +use Magento\Framework\ObjectManager\ObjectManager; use Magento\Framework\Registry; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Framework\View\Result\PageFactory; use Magento\Framework\Controller\ResultFactory; @@ -20,7 +22,7 @@ class AttributeTest extends \PHPUnit\Framework\TestCase { /** - * @var ObjectManager + * @var ObjectManagerHelper */ protected $objectManager; @@ -54,9 +56,14 @@ class AttributeTest extends \PHPUnit\Framework\TestCase */ protected $resultFactoryMock; + /** + * @var ManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $messageManager; + protected function setUp() { - $this->objectManager = new ObjectManager($this); + $this->objectManager = new ObjectManagerHelper($this); $this->contextMock = $this->getMockBuilder(Context::class) ->disableOriginalConstructor() ->getMock(); @@ -74,6 +81,9 @@ protected function setUp() $this->resultFactoryMock = $this->getMockBuilder(ResultFactory::class) ->disableOriginalConstructor() ->getMock(); + $this->messageManager = $this->getMockBuilder(ManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); $this->contextMock->expects($this->any()) ->method('getRequest') @@ -81,6 +91,9 @@ protected function setUp() $this->contextMock->expects($this->any()) ->method('getResultFactory') ->willReturn($this->resultFactoryMock); + $this->contextMock + ->method('getMessageManager') + ->willReturn($this->messageManager); } /** diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/Price/CompositeProductRowSizeEstimatorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/Price/CompositeProductRowSizeEstimatorTest.php index 1c47644338143..728044b89cafe 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/Price/CompositeProductRowSizeEstimatorTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/Price/CompositeProductRowSizeEstimatorTest.php @@ -49,7 +49,6 @@ protected function setUp() public function testEstimateRowSize() { - $this->markTestSkipped('Unskip after MAGETWO-89738'); $expectedResult = 40000000; $maxRelatedProductCount = 10; diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml index 3cbfa0f29d74f..07a801f42a786 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml @@ -40,13 +40,16 @@ function getFrontTab() { function checkOptionsPanelVisibility(){ if($('manage-options-panel')){ - var panel = $('manage-options-panel').up('.fieldset'); + var panel = $('manage-options-panel').up('.fieldset'), + activePanelClass = 'selected-type-options'; if($('frontend_input') && ($('frontend_input').value=='select' || $('frontend_input').value=='multiselect')){ panel.show(); + panel.addClass(activePanelClass); } else { panel.hide(); + panel.removeClass(activePanelClass); } } } diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/options.js b/app/code/Magento/Catalog/view/adminhtml/web/js/options.js index 6ea005915763c..1ec47f14fee88 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/options.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/options.js @@ -20,7 +20,6 @@ define([ return function (config) { var optionPanel = jQuery('#manage-options-panel'), - optionsValues = [], editForm = jQuery('#edit_form'), attributeOption = { table: $('attribute-options-table'), @@ -145,7 +144,9 @@ define([ return optionDefaultInputType; } - }; + }, + tableBody = jQuery(), + activePanelClass = 'selected-type-options'; if ($('add_new_option_button')) { Event.observe('add_new_option_button', 'click', attributeOption.add.bind(attributeOption, {}, true)); @@ -180,30 +181,40 @@ define([ }); }); } - editForm.on('submit', function () { - optionPanel.find('input') - .each(function () { - if (this.disabled) { - return; - } + editForm.on('beforeSubmit', function () { + var optionsValues = [], + optionContainer = optionPanel.find('table tbody'); + + if (optionPanel.hasClass(activePanelClass)) { + optionContainer.find('input') + .each(function () { + if (this.disabled) { + return; + } - if (this.type === 'checkbox' || this.type === 'radio') { - if (this.checked) { + if (this.type === 'checkbox' || this.type === 'radio') { + if (this.checked) { + optionsValues.push(this.name + '=' + jQuery(this).val()); + } + } else { optionsValues.push(this.name + '=' + jQuery(this).val()); } - } else { - optionsValues.push(this.name + '=' + jQuery(this).val()); - } - }); - jQuery('<input>') - .attr({ - type: 'hidden', - name: 'serialized_options' - }) - .val(JSON.stringify(optionsValues)) - .prependTo(editForm); - optionPanel.find('table') - .replaceWith(jQuery('<div>').text(jQuery.mage.__('Sending attribute values as package.'))); + }); + jQuery('<input>') + .attr({ + type: 'hidden', + name: 'serialized_options' + }) + .val(JSON.stringify(optionsValues)) + .prependTo(editForm); + } + tableBody = optionContainer.detach(); + }); + editForm.on('afterValidate.error highlight.validate', function () { + if (optionPanel.hasClass(activePanelClass)) { + optionPanel.find('table').append(tableBody); + jQuery('input[name="serialized_options"]').remove(); + } }); window.attributeOption = attributeOption; window.optionDefaultInputType = attributeOption.getOptionInputType(); diff --git a/app/code/Magento/Catalog/view/frontend/web/js/product/storage/data-storage.js b/app/code/Magento/Catalog/view/frontend/web/js/product/storage/data-storage.js index ab566a70a756d..3dc9f3e844516 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/product/storage/data-storage.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/product/storage/data-storage.js @@ -34,6 +34,21 @@ define([ }; } + /** + * Set data to localStorage with support check. + * + * @param {String} namespace + * @param {Object} data + */ + function setLocalStorageItem(namespace, data) { + try { + window.localStorage.setItem(namespace, JSON.stringify(data)); + } catch (e) { + console.warn('localStorage is unavailable - skipping local caching of product data'); + console.error(e); + } + } + return { /** @@ -118,7 +133,7 @@ define([ if (_.isEmpty(data)) { this.localStorage.removeAll(); } else { - this.localStorage.set(data); + setLocalStorageItem(this.namespace, data); } }, diff --git a/app/code/Magento/Catalog/view/frontend/web/js/product/storage/ids-storage.js b/app/code/Magento/Catalog/view/frontend/web/js/product/storage/ids-storage.js index 7eafbad8299d8..ec07c19a2c1b1 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/product/storage/ids-storage.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/product/storage/ids-storage.js @@ -11,6 +11,21 @@ define([ ], function ($, _, ko, utils) { 'use strict'; + /** + * Set data to localStorage with support check. + * + * @param {String} namespace + * @param {Object} data + */ + function setLocalStorageItem(namespace, data) { + try { + window.localStorage.setItem(namespace, JSON.stringify(data)); + } catch (e) { + console.warn('localStorage is unavailable - skipping local caching of product data'); + console.error(e); + } + } + return { /** @@ -94,11 +109,7 @@ define([ * Initializes handler to "data" property update */ internalDataHandler: function (data) { - var localStorage = this.localStorage.get(); - - if (!utils.compare(data, localStorage).equal) { - this.localStorage.set(data); - } + setLocalStorageItem(this.namespace, data); }, /** diff --git a/app/code/Magento/Catalog/view/frontend/web/js/product/storage/storage-service.js b/app/code/Magento/Catalog/view/frontend/web/js/product/storage/storage-service.js index e571baa23e497..b35ab867bb0e7 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/product/storage/storage-service.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/product/storage/storage-service.js @@ -47,7 +47,7 @@ define([ * @param {*} data */ add: function (data) { - if (!_.isEmpty(data) && !utils.compare(data, this.data()).equal) { + if (!_.isEmpty(data)) { this.data(_.extend(utils.copy(this.data()), data)); } }, diff --git a/app/code/Magento/CatalogGraphQl/Model/AttributesJoiner.php b/app/code/Magento/CatalogGraphQl/Model/AttributesJoiner.php index ebd8671de02fb..d57154c429920 100644 --- a/app/code/Magento/CatalogGraphQl/Model/AttributesJoiner.php +++ b/app/code/Magento/CatalogGraphQl/Model/AttributesJoiner.php @@ -15,6 +15,11 @@ */ class AttributesJoiner { + /** + * @var array + */ + private $queryFields = []; + /** * Join fields attached to field node to collection's select. * @@ -24,17 +29,33 @@ class AttributesJoiner */ public function join(FieldNode $fieldNode, AbstractCollection $collection) : void { - $query = $fieldNode->selectionSet->selections; - - /** @var FieldNode $field */ - foreach ($query as $field) { - if ($field->kind === 'InlineFragment') { - continue; + foreach ($this->getQueryFields($fieldNode) as $field) { + if (!$collection->isAttributeAdded($field)) { + $collection->addAttributeToSelect($field); } + } + } - if (!$collection->isAttributeAdded($field->name->value)) { - $collection->addAttributeToSelect($field->name->value); + /** + * Get an array of queried fields. + * + * @param FieldNode $fieldNode + * @return string[] + */ + public function getQueryFields(FieldNode $fieldNode) + { + if (!isset($this->queryFields[$fieldNode->name->value])) { + $this->queryFields[$fieldNode->name->value] = []; + $query = $fieldNode->selectionSet->selections; + /** @var FieldNode $field */ + foreach ($query as $field) { + if ($field->kind === 'InlineFragment') { + continue; + } + $this->queryFields[$fieldNode->name->value][] = $field->name->value; } } + + return $this->queryFields[$fieldNode->name->value]; } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php b/app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php index da4ec37c51da4..f0cdab9498abb 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php +++ b/app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php @@ -8,6 +8,7 @@ namespace Magento\CatalogGraphQl\Model\Category; use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Catalog\Model\Category; use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CustomAttributesFlattener; use Magento\Framework\Reflection\DataObjectProcessor; @@ -41,14 +42,19 @@ public function __construct( /** * Hydrate and flatten category object to flat array * - * @param CategoryInterface $category + * @param Category $category + * @param bool $basicFieldsOnly Set to false to avoid expensive hydration, used for performance optimization * @return array */ - public function hydrateCategory(CategoryInterface $category) : array + public function hydrateCategory(Category $category, $basicFieldsOnly = false) : array { - $categoryData = $this->dataObjectProcessor->buildOutputDataArray($category, CategoryInterface::class); + if ($basicFieldsOnly) { + $categoryData = $category->getData(); + } else { + $categoryData = $this->dataObjectProcessor->buildOutputDataArray($category, CategoryInterface::class); + $categoryData['product_count'] = $category->getProductCount(); + } $categoryData['id'] = $category->getId(); - $categoryData['product_count'] = $category->getProductCount(); $categoryData['children'] = []; $categoryData['available_sort_by'] = $category->getAvailableSortBy(); return $this->flattener->flatten($categoryData); diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php index e2998438bd74c..de4c7dd71ca36 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php @@ -17,7 +17,7 @@ use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Query\Resolver\ValueFactory; -use Magento\Framework\Reflection\DataObjectProcessor; +use Magento\CatalogGraphQl\Model\Category\Hydrator as CategoryHydrator; /** * Resolver for category objects the product is assigned to. @@ -36,11 +36,6 @@ class Categories implements ResolverInterface */ private $categoryIds = []; - /** - * @var DataObjectProcessor - */ - private $dataObjectProcessor; - /** * @var AttributesJoiner */ @@ -57,29 +52,34 @@ class Categories implements ResolverInterface private $valueFactory; /** - * Category constructor. + * @var CategoryHydrator + */ + private $categoryHydrator; + + /** * @param CollectionFactory $collectionFactory - * @param DataObjectProcessor $dataObjectProcessor * @param AttributesJoiner $attributesJoiner * @param CustomAttributesFlattener $customAttributesFlattener * @param ValueFactory $valueFactory + * @param CategoryHydrator $categoryHydrator */ public function __construct( CollectionFactory $collectionFactory, - DataObjectProcessor $dataObjectProcessor, AttributesJoiner $attributesJoiner, CustomAttributesFlattener $customAttributesFlattener, - ValueFactory $valueFactory + ValueFactory $valueFactory, + CategoryHydrator $categoryHydrator ) { $this->collection = $collectionFactory->create(); - $this->dataObjectProcessor = $dataObjectProcessor; $this->attributesJoiner = $attributesJoiner; $this->customAttributesFlattener = $customAttributesFlattener; $this->valueFactory = $valueFactory; + $this->categoryHydrator = $categoryHydrator; } /** * @inheritdoc + * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) @@ -107,14 +107,18 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value /** @var CategoryInterface | \Magento\Catalog\Model\Category $item */ foreach ($this->collection as $item) { if (in_array($item->getId(), $categoryIds)) { - $categories[$item->getId()] = $this->dataObjectProcessor->buildOutputDataArray( - $item, - CategoryInterface::class - ); - $categories[$item->getId()] = $this->customAttributesFlattener - ->flatten($categories[$item->getId()]); - $categories[$item->getId()]['product_count'] = $item->getProductCount(); + // Try to extract all requested fields from the loaded collection data + $categories[$item->getId()] = $this->categoryHydrator->hydrateCategory($item, true); $categories[$item->getId()]['model'] = $item; + $requestedFields = $that->attributesJoiner->getQueryFields($info->fieldNodes[0]); + $extractedFields = array_keys($categories[$item->getId()]); + $foundFields = array_intersect($requestedFields, $extractedFields); + if (count($requestedFields) === count($foundFields)) { + continue; + } + + // If not all requested fields were extracted from the collection, start more complex extraction + $categories[$item->getId()] = $this->categoryHydrator->hydrateCategory($item); } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php new file mode 100644 index 0000000000000..0ec7e12e42d55 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver; + +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; + +/** + * Layered navigation filters resolver, used for GraphQL request processing. + */ +class LayerFilters implements ResolverInterface +{ + /** + * @var Layer\DataProvider\Filters + */ + private $filtersDataProvider; + + /** + * @param \Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters $filtersDataProvider + */ + public function __construct( + \Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters $filtersDataProvider + ) { + $this->filtersDataProvider = $filtersDataProvider; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($value['layer_type'])) { + return null; + } + + return $this->filtersDataProvider->getData($value['layer_type']); + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php index d50100a65f679..ff53299b00e33 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php @@ -43,30 +43,22 @@ class Products implements ResolverInterface */ private $searchFilter; - /** - * @var Layer\DataProvider\Filters - */ - private $filtersDataProvider; - /** * @param Builder $searchCriteriaBuilder * @param Search $searchQuery * @param Filter $filterQuery * @param SearchFilter $searchFilter - * @param Filters $filtersDataProvider */ public function __construct( Builder $searchCriteriaBuilder, Search $searchQuery, Filter $filterQuery, - SearchFilter $searchFilter, - Filters $filtersDataProvider + SearchFilter $searchFilter ) { $this->searchCriteriaBuilder = $searchCriteriaBuilder; $this->searchQuery = $searchQuery; $this->filterQuery = $filterQuery; $this->searchFilter = $searchFilter; - $this->filtersDataProvider = $filtersDataProvider; } /** @@ -119,7 +111,7 @@ public function resolve( 'current_page' => $currentPage, 'total_pages' => $maxPages ], - 'filters' => $this->filtersDataProvider->getData($layerType) + 'layer_type' => $layerType ]; return $data; diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php index 2c73d7a079170..7f1fd71942253 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php @@ -86,8 +86,12 @@ public function getList( $collection->load(); // Methods that perform extra fetches post-load - $collection->addMediaGalleryData(); - $collection->addOptionsToResult(); + if (in_array('media_gallery_entries', $attributes)) { + $collection->addMediaGalleryData(); + } + if (in_array('options', $attributes)) { + $collection->addOptionsToResult(); + } $searchResult = $this->searchResultsFactory->create(); $searchResult->setSearchCriteria($searchCriteria); diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index a1a587f3f37b7..5d62cb63f1662 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -420,7 +420,7 @@ type Products @doc(description: "The Products object is the top-level object ret items: [ProductInterface] @doc(description: "An array of products that match the specified search criteria") page_info: SearchResultPageInfo @doc(description: "An object that includes the page_info and currentPage values specified in the query") total_count: Int @doc(description: "The number of products returned") - filters: [LayerFilter] @doc(description: "Layered navigation filters array") + filters: [LayerFilter] @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\LayerFilters") @doc(description: "Layered navigation filters array") sort_fields: SortFields @doc(description: "An object that includes the default sort field and all available sort fields") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\SortFields") } diff --git a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php index b138ef765a797..5b9905e0c9d81 100644 --- a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php +++ b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php @@ -15,7 +15,6 @@ /** * Catalog Products List widget block * - * Class ProductsList * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ProductsList extends \Magento\Catalog\Block\Product\AbstractProduct implements BlockInterface, IdentityInterface @@ -131,7 +130,9 @@ public function __construct( } /** - * @inheritdoc + * Internal constructor, that is called from real constructor + * + * @return void */ protected function _construct() { @@ -212,7 +213,7 @@ public function getProductPriceHtml( $product, $arguments ); - + return $price; } @@ -255,7 +256,7 @@ public function createCollection() } /** - * Returns conditions + * Get conditions * * @return \Magento\Rule\Model\Condition\Combine */ @@ -394,7 +395,7 @@ public function getTitle() } /** - * Returns PriceCurrencyInterface instance + * Get currency of product * * @return PriceCurrencyInterface * @deprecated 100.2.0 diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml index 6ea8d8a167294..13e4e286311b2 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml @@ -91,9 +91,7 @@ <description value="Should be able to place an order as a customer with multiple addresses and tax rates."/> <testCaseId value="MAGETWO-93109"/> <severity value="AVERAGE"/> - <skip> - <issueId value="MQE-1187" /> - </skip> + <group value="checkout"/> </annotations> <before> <createData entity="SimpleSubCategory" stepKey="simplecategory"/> @@ -183,6 +181,8 @@ <click stepKey="changeShippingAddress" selector="{{CheckoutShippingMethodsSection.shipHereButton}}"/> <waitForElementNotVisible stepKey="waitForShippingMethodLoaderNotVisible" selector="{{CheckoutShippingMethodsSection.shippingMethodLoader}}" time="30"/> + <waitForElementVisible stepKey="waitForShippingMethodRadioToBeVisible" selector="{{CheckoutShippingMethodsSection.firstShippingMethod}}" time="30"/> + <waitForPageLoad stepKey="waitForPageLoad23"/> <click stepKey="selectFirstShippingMethod2" selector="{{CheckoutShippingMethodsSection.firstShippingMethod}}"/> <waitForElement stepKey="waitForShippingMethodSelect2" selector="{{CheckoutShippingMethodsSection.next}}" time="30"/> <click stepKey="clickNextOnShippingMethodLoad2" selector="{{CheckoutShippingMethodsSection.next}}" /> diff --git a/app/code/Magento/Cms/Block/Adminhtml/Block/Edit/DeleteButton.php b/app/code/Magento/Cms/Block/Adminhtml/Block/Edit/DeleteButton.php index a7410cac64d76..51a9313fdefd2 100644 --- a/app/code/Magento/Cms/Block/Adminhtml/Block/Edit/DeleteButton.php +++ b/app/code/Magento/Cms/Block/Adminhtml/Block/Edit/DeleteButton.php @@ -13,7 +13,7 @@ class DeleteButton extends GenericButton implements ButtonProviderInterface { /** - * @return array + * @inheritDoc */ public function getButtonData() { @@ -24,7 +24,7 @@ public function getButtonData() 'class' => 'delete', 'on_click' => 'deleteConfirm(\'' . __( 'Are you sure you want to do this?' - ) . '\', \'' . $this->getDeleteUrl() . '\')', + ) . '\', \'' . $this->getDeleteUrl() . '\', {"data": {}})', 'sort_order' => 20, ]; } @@ -32,6 +32,8 @@ public function getButtonData() } /** + * URL to send delete requests to. + * * @return string */ public function getDeleteUrl() diff --git a/app/code/Magento/Cms/Block/Adminhtml/Page/Edit/DeleteButton.php b/app/code/Magento/Cms/Block/Adminhtml/Page/Edit/DeleteButton.php index 1fc599e4c856a..b434fd3f5d348 100644 --- a/app/code/Magento/Cms/Block/Adminhtml/Page/Edit/DeleteButton.php +++ b/app/code/Magento/Cms/Block/Adminhtml/Page/Edit/DeleteButton.php @@ -13,7 +13,7 @@ class DeleteButton extends GenericButton implements ButtonProviderInterface { /** - * @return array + * @inheritDoc */ public function getButtonData() { @@ -24,7 +24,7 @@ public function getButtonData() 'class' => 'delete', 'on_click' => 'deleteConfirm(\'' . __( 'Are you sure you want to do this?' - ) . '\', \'' . $this->getDeleteUrl() . '\')', + ) . '\', \'' . $this->getDeleteUrl() . '\', {"data": {}})', 'sort_order' => 20, ]; } @@ -32,6 +32,8 @@ public function getButtonData() } /** + * Url to send delete requests to. + * * @return string */ public function getDeleteUrl() diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/SearchBlockOnGridPageActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/SearchBlockOnGridPageActionGroup.xml index e31eb44146af4..12205aeed8fa3 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/SearchBlockOnGridPageActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/SearchBlockOnGridPageActionGroup.xml @@ -16,4 +16,27 @@ <waitForLoadingMaskToDisappear stepKey="waitForSecondIdSortDescendingToFinish2" /> <waitForElementVisible selector="{{WidgetSection.BlockPage(Block.identifier)}}" stepKey="waitForBlockTitle" /> </actionGroup> + <actionGroup name ="deleteBlock"> + <arguments> + <argument name="Block" defaultValue=""/> + </arguments> + <amOnPage url="{{CmsBlocksPage.url}}" stepKey="navigateToCMSBlocksGrid"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <conditionalClick selector="{{BlockPageActionsSection.clearAll}}" dependentSelector="{{BlockPageActionsSection.activeFilters}}" stepKey="clickToResetFilter" visible="true"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + <click selector="{{BlockPageActionsSection.FilterBtn}}" stepKey="clickFilterBtn"/> + <fillField selector="{{BlockPageActionsSection.URLKey}}" userInput="{{Block.identifier}}" stepKey="fillBlockIdentifierInput"/> + <click selector="{{BlockPageActionsSection.ApplyFiltersBtn}}" stepKey="applyFilter"/> + <waitForLoadingMaskToDisappear stepKey="waitForGridToLoadResults" /> + <waitForElementVisible selector="{{BlockPageActionsSection.select(Block.identifier)}}" stepKey="waitForCMSPageGrid" /> + <click selector="{{BlockPageActionsSection.select(Block.identifier)}}" stepKey="clickSelect" /> + <waitForElementVisible selector="{{BlockPageActionsSection.edit(Block.identifier)}}" stepKey="waitForEditLink" /> + <click selector="{{BlockPageActionsSection.edit(Block.identifier)}}" stepKey="clickEdit" /> + <waitForLoadingMaskToDisappear stepKey="waitForPageToLoad" /> + <click selector="{{CmsBlockBlockActionSection.deleteBlock}}" stepKey="deleteBlock"/> + <waitForElementVisible selector="{{CmsBlockBlockActionSection.deleteConfirm}}" stepKey="waitForOkButtonToBeVisible"/> + <click selector="{{CmsBlockBlockActionSection.deleteConfirm}}" stepKey="clickOkButton"/> + <waitForPageLoad stepKey="waitForPageLoad3"/> + <see userInput="You deleted the block." stepKey="seeSuccessMessage"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection.xml index 2e783af6bfc6a..4c6014af51264 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection.xml @@ -24,4 +24,8 @@ <section name="BlockContentSection"> <element name="TextArea" type="input" selector="#cms_block_form_content"/> </section> + <section name="CmsBlockBlockActionSection"> + <element name="deleteBlock" type="button" selector="#delete" timeout="30"/> + <element name="deleteConfirm" type="button" selector=".action-primary.action-accept" timeout="60"/> + </section> </sections> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsBlockTest.xml index e2c74823dfffb..7ab0d9209dde3 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsBlockTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsBlockTest.xml @@ -23,6 +23,9 @@ <actionGroup ref="DisabledWYSIWYG" stepKey="disableWYSIWYG"/> </before> <after> + <actionGroup ref="deleteBlock" stepKey="deleteCreatedBlock"> + <argument name="Block" value="_defaultBlock"/> + </actionGroup> <actionGroup ref="logout" stepKey="logout"/> </after> <amOnPage url="{{CmsNewBlock.url}}" stepKey="amOnBlocksCreationForm"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidation.xml b/app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidation.xml index d1c44383b4ac1..6165def067ef4 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidation.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidation.xml @@ -11,6 +11,7 @@ <test name="StoreFrontMobileViewValidation"> <annotations> <features value="Cms"/> + <stories value="Mobile view page footer should stick to the bottom of page on Store front"/> <title value="Mobile view page footer should stick to the bottom of page on Store front"/> <description value="Mobile view page footer should stick to the bottom of page on Store front"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/BlockActionsTest.php b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/BlockActionsTest.php index 3dcf6c4a3fce0..3095abef7bbe3 100644 --- a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/BlockActionsTest.php +++ b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/BlockActionsTest.php @@ -95,6 +95,7 @@ public function testPrepareDataSource() 'title' => __('Delete %1', $title), 'message' => __('Are you sure you want to delete a %1 record?', $title) ], + 'post' => true ] ], ] diff --git a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php index b0cc1bf061a48..9b3165a2c5517 100644 --- a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php +++ b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php @@ -70,6 +70,7 @@ public function testPrepareItemsByPageId() 'title' => __('Delete %1', $title), 'message' => __('Are you sure you want to delete a %1 record?', $title) ], + 'post' => true ] ], ] diff --git a/app/code/Magento/Cms/Ui/Component/Listing/Column/BlockActions.php b/app/code/Magento/Cms/Ui/Component/Listing/Column/BlockActions.php index 60b9f34d29ae6..f68ef35e534f3 100644 --- a/app/code/Magento/Cms/Ui/Component/Listing/Column/BlockActions.php +++ b/app/code/Magento/Cms/Ui/Component/Listing/Column/BlockActions.php @@ -55,10 +55,7 @@ public function __construct( } /** - * Prepare Data Source - * - * @param array $dataSource - * @return array + * @inheritDoc */ public function prepareDataSource(array $dataSource) { @@ -87,7 +84,8 @@ public function prepareDataSource(array $dataSource) 'confirm' => [ 'title' => __('Delete %1', $title), 'message' => __('Are you sure you want to delete a %1 record?', $title) - ] + ], + 'post' => true ] ]; } @@ -99,6 +97,7 @@ public function prepareDataSource(array $dataSource) /** * Get instance of escaper + * * @return Escaper * @deprecated 101.0.7 */ diff --git a/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php b/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php index ea6882e21c85f..26d31456bf61d 100644 --- a/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php +++ b/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php @@ -67,10 +67,7 @@ public function __construct( } /** - * Prepare Data Source - * - * @param array $dataSource - * @return array + * @inheritDoc */ public function prepareDataSource(array $dataSource) { @@ -89,7 +86,8 @@ public function prepareDataSource(array $dataSource) 'confirm' => [ 'title' => __('Delete %1', $title), 'message' => __('Are you sure you want to delete a %1 record?', $title) - ] + ], + 'post' => true ]; } if (isset($item['identifier'])) { @@ -110,6 +108,7 @@ public function prepareDataSource(array $dataSource) /** * Get instance of escaper + * * @return Escaper * @deprecated 101.0.7 */ diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml index 7062d092e7204..3c043b28801e2 100644 --- a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml @@ -9,7 +9,7 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="EnabledWYSIWYG"> - <amOnPage url="admin/admin/system_config/edit/section/cms/" stepKey="navigateToConfigurationPage" /> + <amOnPage url="{{ConfigurationStoresPage.url}}" stepKey="navigateToConfigurationPage" /> <waitForPageLoad stepKey="wait1"/> <conditionalClick stepKey="expandWYSIWYGOptions" selector="{{ContentManagementSection.WYSIWYGOptions}}" dependentSelector="{{ContentManagementSection.CheckIfTabExpand}}" visible="true" /> <waitForElementVisible selector="{{ContentManagementSection.EnableWYSIWYG}}" stepKey="waitForEnableWYSIWYGDropdown1" /> @@ -20,7 +20,7 @@ <click selector="{{ContentManagementSection.Save}}" stepKey="saveConfig" /> </actionGroup> <actionGroup name="DisabledWYSIWYG"> - <amOnPage url="admin/admin/system_config/edit/section/cms/" stepKey="navigateToConfigurationPage" /> + <amOnPage url="{{ConfigurationStoresPage.url}}" stepKey="navigateToConfigurationPage" /> <waitForPageLoad stepKey="wait3"/> <conditionalClick stepKey="expandWYSIWYGOptions" selector="{{ContentManagementSection.WYSIWYGOptions}}" dependentSelector="{{ContentManagementSection.CheckIfTabExpand}}" visible="true" /> <waitForElementVisible selector="{{ContentManagementSection.EnableWYSIWYG}}" stepKey="waitForEnableWYSIWYGDropdown2" time="30"/> diff --git a/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml b/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml index dbede5491e011..f4698b6865779 100644 --- a/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml +++ b/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml @@ -7,17 +7,6 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminConfigSection"> - <element name="advancedReportingMenuItem" type="text" selector="//a[contains(concat(' ',normalize-space(@class),' '),'item-nav')]/span[text()='Advanced Reporting']"/> - <element name="advancedReportingService" type="select" selector="#analytics_general_enabled"/> - <element name="advancedReportingServiceLabel" type="text" selector="#row_analytics_general_enabled>td.label>label>span"/> - <element name="advancedReportingServiceStatus" type="text" selector="#row_analytics_general_enabled>td.value>p>span"/> - <element name="advancedReportingIndustry" type="select" selector="#analytics_general_vertical"/> - <element name="advancedReportingIndustryLabel" type="text" selector=".config-vertical-label>label>span"/> - <element name="advancedReportingHour" type="select" selector="#row_analytics_general_collection_time>td:nth-child(2)>select:nth-child(2)"/> - <element name="advancedReportingMinute" type="select" selector="#row_analytics_general_collection_time>td:nth-child(2)>select:nth-child(3)"/> - <element name="advancedReportingSeconds" type="select" selector="#row_analytics_general_collection_time>td:nth-child(2)>select:nth-child(4)"/> - <element name="advancedReportingBlankIndustryError" type="text" selector=".message-error>div"/> - <element name="advancedReportingSuccessMessage" type="text" selector=".message-success>div"/> <element name="saveButton" type="button" selector="#save"/> </section> </sections> \ No newline at end of file diff --git a/app/code/Magento/Customer/Api/AccountManagementInterface.php b/app/code/Magento/Customer/Api/AccountManagementInterface.php index d2f9fb7ebc420..10fc2349968ea 100644 --- a/app/code/Magento/Customer/Api/AccountManagementInterface.php +++ b/app/code/Magento/Customer/Api/AccountManagementInterface.php @@ -7,6 +7,8 @@ namespace Magento\Customer\Api; +use Magento\Framework\Exception\InputException; + /** * Interface for managing customers accounts. * @api @@ -144,19 +146,24 @@ public function initiatePasswordReset($email, $template, $websiteId = null); /** * Reset customer password. * - * @param string $email + * @param string $email If empty value given then the customer + * will be matched by the RP token. * @param string $resetToken * @param string $newPassword + * * @return bool true on success * @throws \Magento\Framework\Exception\LocalizedException + * @throws InputException */ public function resetPassword($email, $resetToken, $newPassword); /** * Check if password reset token is valid. * - * @param int $customerId + * @param int $customerId If null is given then a customer + * will be matched by the RP token. * @param string $resetPasswordLinkToken + * * @return bool True if the token is valid * @throws \Magento\Framework\Exception\State\InputMismatchException If token is mismatched * @throws \Magento\Framework\Exception\State\ExpiredException If token is expired diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/ResetPasswordButton.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/ResetPasswordButton.php index f2d1dd9f0a853..c38f182f275f2 100644 --- a/app/code/Magento/Customer/Block/Adminhtml/Edit/ResetPasswordButton.php +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/ResetPasswordButton.php @@ -13,6 +13,8 @@ class ResetPasswordButton extends GenericButton implements ButtonProviderInterface { /** + * Retrieve button-specified settings + * * @return array */ public function getButtonData() @@ -23,7 +25,10 @@ public function getButtonData() $data = [ 'label' => __('Reset Password'), 'class' => 'reset reset-password', - 'on_click' => sprintf("location.href = '%s';", $this->getResetPasswordUrl()), + 'data_attribute' => [ + 'url' => $this->getResetPasswordUrl() + ], + 'on_click' => '', 'sort_order' => 60, ]; } @@ -31,6 +36,8 @@ public function getButtonData() } /** + * Get reset password url + * * @return string */ public function getResetPasswordUrl() diff --git a/app/code/Magento/Customer/Controller/Account/CreatePassword.php b/app/code/Magento/Customer/Controller/Account/CreatePassword.php index fb2e3dd42908b..124ac912a7ccf 100644 --- a/app/code/Magento/Customer/Controller/Account/CreatePassword.php +++ b/app/code/Magento/Customer/Controller/Account/CreatePassword.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -8,10 +7,16 @@ use Magento\Customer\Api\AccountManagementInterface; use Magento\Customer\Model\Session; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\View\Result\PageFactory; use Magento\Framework\App\Action\Context; -class CreatePassword extends \Magento\Customer\Controller\AbstractAccount +/** + * Class CreatePassword + * + * @package Magento\Customer\Controller\Account + */ +class CreatePassword extends \Magento\Customer\Controller\AbstractAccount implements HttpGetActionInterface { /** * @var \Magento\Customer\Api\AccountManagementInterface @@ -54,27 +59,27 @@ public function __construct( public function execute() { $resetPasswordToken = (string)$this->getRequest()->getParam('token'); - $customerId = (int)$this->getRequest()->getParam('id'); - $isDirectLink = $resetPasswordToken != '' && $customerId != 0; + $isDirectLink = $resetPasswordToken != ''; if (!$isDirectLink) { $resetPasswordToken = (string)$this->session->getRpToken(); - $customerId = (int)$this->session->getRpCustomerId(); } try { - $this->accountManagement->validateResetPasswordLinkToken($customerId, $resetPasswordToken); + $this->accountManagement->validateResetPasswordLinkToken(null, $resetPasswordToken); if ($isDirectLink) { $this->session->setRpToken($resetPasswordToken); - $this->session->setRpCustomerId($customerId); $resultRedirect = $this->resultRedirectFactory->create(); $resultRedirect->setPath('*/*/createpassword'); + return $resultRedirect; } else { /** @var \Magento\Framework\View\Result\Page $resultPage */ $resultPage = $this->resultPageFactory->create(); - $resultPage->getLayout()->getBlock('resetPassword')->setCustomerId($customerId) + $resultPage->getLayout() + ->getBlock('resetPassword') ->setResetPasswordLinkToken($resetPasswordToken); + return $resultPage; } } catch (\Exception $exception) { diff --git a/app/code/Magento/Customer/Controller/Account/EditPost.php b/app/code/Magento/Customer/Controller/Account/EditPost.php index e3b3d8345224e..38bc52eac4266 100644 --- a/app/code/Magento/Customer/Controller/Account/EditPost.php +++ b/app/code/Magento/Customer/Controller/Account/EditPost.php @@ -22,6 +22,7 @@ use Magento\Customer\Model\CustomerExtractor; use Magento\Customer\Model\Session; use Magento\Framework\App\Action\Context; +use Magento\Framework\Escaper; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\InvalidEmailOrPasswordException; use Magento\Framework\Exception\State\UserLockedException; @@ -79,6 +80,11 @@ class EditPost extends AbstractAccount implements CsrfAwareActionInterface, Http */ private $customerMapper; + /** + * @var Escaper + */ + private $escaper; + /** * @param Context $context * @param Session $customerSession @@ -86,6 +92,7 @@ class EditPost extends AbstractAccount implements CsrfAwareActionInterface, Http * @param CustomerRepositoryInterface $customerRepository * @param Validator $formKeyValidator * @param CustomerExtractor $customerExtractor + * @param Escaper|null $escaper */ public function __construct( Context $context, @@ -93,7 +100,8 @@ public function __construct( AccountManagementInterface $customerAccountManagement, CustomerRepositoryInterface $customerRepository, Validator $formKeyValidator, - CustomerExtractor $customerExtractor + CustomerExtractor $customerExtractor, + ?Escaper $escaper = null ) { parent::__construct($context); $this->session = $customerSession; @@ -101,6 +109,7 @@ public function __construct( $this->customerRepository = $customerRepository; $this->formKeyValidator = $formKeyValidator; $this->customerExtractor = $customerExtractor; + $this->escaper = $escaper ?: ObjectManager::getInstance()->get(Escaper::class); } /** @@ -196,7 +205,7 @@ public function execute() $this->messageManager->addSuccess(__('You saved the account information.')); return $resultRedirect->setPath('customer/account'); } catch (InvalidEmailOrPasswordException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($this->escaper->escapeHtml($e->getMessage())); } catch (UserLockedException $e) { $message = __( 'The account sign-in was incorrect or your account is disabled temporarily. ' @@ -207,9 +216,9 @@ public function execute() $this->messageManager->addError($message); return $resultRedirect->setPath('customer/account/login'); } catch (InputException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($this->escaper->escapeHtml($e->getMessage())); foreach ($e->getErrors() as $error) { - $this->messageManager->addError($error->getMessage()); + $this->messageManager->addErrorMessage($this->escaper->escapeHtml($error->getMessage())); } } catch (\Magento\Framework\Exception\LocalizedException $e) { $this->messageManager->addError($e->getMessage()); diff --git a/app/code/Magento/Customer/Controller/Account/ResetPasswordPost.php b/app/code/Magento/Customer/Controller/Account/ResetPasswordPost.php index 3de44e35d2447..27a00f86dd95d 100644 --- a/app/code/Magento/Customer/Controller/Account/ResetPasswordPost.php +++ b/app/code/Magento/Customer/Controller/Account/ResetPasswordPost.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -10,11 +9,16 @@ use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Customer\Model\Session; use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Exception\InputException; use Magento\Customer\Model\Customer\CredentialsValidator; -use Magento\Framework\App\ObjectManager; -class ResetPasswordPost extends \Magento\Customer\Controller\AbstractAccount +/** + * Class ResetPasswordPost + * + * @package Magento\Customer\Controller\Account + */ +class ResetPasswordPost extends \Magento\Customer\Controller\AbstractAccount implements HttpPostActionInterface { /** * @var \Magento\Customer\Api\AccountManagementInterface @@ -31,17 +35,14 @@ class ResetPasswordPost extends \Magento\Customer\Controller\AbstractAccount */ protected $session; - /** - * @var CredentialsValidator - */ - private $credentialsValidator; - /** * @param Context $context * @param Session $customerSession * @param AccountManagementInterface $accountManagement * @param CustomerRepositoryInterface $customerRepository * @param CredentialsValidator|null $credentialsValidator + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( Context $context, @@ -53,8 +54,6 @@ public function __construct( $this->session = $customerSession; $this->accountManagement = $accountManagement; $this->customerRepository = $customerRepository; - $this->credentialsValidator = $credentialsValidator ?: ObjectManager::getInstance() - ->get(CredentialsValidator::class); parent::__construct($context); } @@ -70,29 +69,32 @@ public function execute() /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); $resetPasswordToken = (string)$this->getRequest()->getQuery('token'); - $customerId = (int)$this->getRequest()->getQuery('id'); $password = (string)$this->getRequest()->getPost('password'); $passwordConfirmation = (string)$this->getRequest()->getPost('password_confirmation'); if ($password !== $passwordConfirmation) { $this->messageManager->addError(__("New Password and Confirm New Password values didn't match.")); - $resultRedirect->setPath('*/*/createPassword', ['id' => $customerId, 'token' => $resetPasswordToken]); + $resultRedirect->setPath('*/*/createPassword', ['token' => $resetPasswordToken]); + return $resultRedirect; } if (iconv_strlen($password) <= 0) { $this->messageManager->addError(__('Please enter a new password.')); - $resultRedirect->setPath('*/*/createPassword', ['id' => $customerId, 'token' => $resetPasswordToken]); + $resultRedirect->setPath('*/*/createPassword', ['token' => $resetPasswordToken]); + return $resultRedirect; } try { - $customerEmail = $this->customerRepository->getById($customerId)->getEmail(); - $this->credentialsValidator->checkPasswordDifferentFromEmail($customerEmail, $password); - $this->accountManagement->resetPassword($customerEmail, $resetPasswordToken, $password); + $this->accountManagement->resetPassword( + null, + $resetPasswordToken, + $password + ); $this->session->unsRpToken(); - $this->session->unsRpCustomerId(); $this->messageManager->addSuccess(__('You updated your password.')); $resultRedirect->setPath('*/*/login'); + return $resultRedirect; } catch (InputException $e) { $this->messageManager->addError($e->getMessage()); @@ -102,7 +104,8 @@ public function execute() } catch (\Exception $exception) { $this->messageManager->addError(__('Something went wrong while saving the new password.')); } - $resultRedirect->setPath('*/*/createPassword', ['id' => $customerId, 'token' => $resetPasswordToken]); + $resultRedirect->setPath('*/*/createPassword', ['token' => $resetPasswordToken]); + return $resultRedirect; } } diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php index fe17adcb09c0d..9173307a7d270 100644 --- a/app/code/Magento/Customer/Model/AccountManagement.php +++ b/app/code/Magento/Customer/Model/AccountManagement.php @@ -20,6 +20,7 @@ use Magento\Customer\Model\Metadata\Validator; use Magento\Eav\Model\Validator\Attribute\Backend; use Magento\Framework\Api\ExtensibleDataObjectConverter; +use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Framework\App\Area; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ObjectManager; @@ -41,6 +42,7 @@ use Magento\Framework\Intl\DateTimeFactory; use Magento\Framework\Mail\Template\TransportBuilder; use Magento\Framework\Math\Random; +use Magento\Framework\Phrase; use Magento\Framework\Reflection\DataObjectProcessor; use Magento\Framework\Registry; use Magento\Framework\Stdlib\DateTime; @@ -326,6 +328,11 @@ class AccountManagement implements AccountManagementInterface */ private $accountConfirmation; + /** + * @var SearchCriteriaBuilder + */ + private $searchCriteriaBuilder; + /** * @param CustomerFactory $customerFactory * @param ManagerInterface $eventManager @@ -356,6 +363,7 @@ class AccountManagement implements AccountManagementInterface * @param SessionManagerInterface|null $sessionManager * @param SaveHandlerInterface|null $saveHandler * @param CollectionFactory|null $visitorCollectionFactory + * @param SearchCriteriaBuilder|null $searchCriteriaBuilder * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -387,7 +395,8 @@ public function __construct( AccountConfirmation $accountConfirmation = null, SessionManagerInterface $sessionManager = null, SaveHandlerInterface $saveHandler = null, - CollectionFactory $visitorCollectionFactory = null + CollectionFactory $visitorCollectionFactory = null, + SearchCriteriaBuilder $searchCriteriaBuilder = null ) { $this->customerFactory = $customerFactory; $this->eventManager = $eventManager; @@ -423,6 +432,8 @@ public function __construct( ?: ObjectManager::getInstance()->get(SaveHandlerInterface::class); $this->visitorCollectionFactory = $visitorCollectionFactory ?: ObjectManager::getInstance()->get(CollectionFactory::class); + $this->searchCriteriaBuilder = $searchCriteriaBuilder + ?: ObjectManager::getInstance()->get(SearchCriteriaBuilder::class); } /** @@ -591,6 +602,43 @@ public function initiatePasswordReset($email, $template, $websiteId = null) return false; } + /** + * Match a customer by their RP token. + * + * @param string $rpToken + * @throws ExpiredException + * @throws NoSuchEntityException + * + * @return CustomerInterface + * @throws LocalizedException + */ + private function matchCustomerByRpToken(string $rpToken): CustomerInterface + { + $this->searchCriteriaBuilder->addFilter( + 'rp_token', + $rpToken + ); + $this->searchCriteriaBuilder->setPageSize(1); + $found = $this->customerRepository->getList( + $this->searchCriteriaBuilder->create() + ); + if ($found->getTotalCount() > 1) { + //Failed to generated unique RP token + throw new ExpiredException( + new Phrase('Reset password token expired.') + ); + } + if ($found->getTotalCount() === 0) { + //Customer with such token not found. + throw NoSuchEntityException::singleField( + 'rp_token', + $rpToken + ); + } + //Unique customer found. + return $found->getItems()[0]; + } + /** * Handle not supported template * @@ -615,16 +663,24 @@ private function handleUnknownTemplate($template) */ public function resetPassword($email, $resetToken, $newPassword) { - $customer = $this->customerRepository->get($email); + if (!$email) { + $customer = $this->matchCustomerByRpToken($resetToken); + $email = $customer->getEmail(); + } else { + $customer = $this->customerRepository->get($email); + } //Validate Token and new password strength $this->validateResetPasswordToken($customer->getId(), $resetToken); + $this->credentialsValidator->checkPasswordDifferentFromEmail( + $email, + $newPassword + ); $this->checkPasswordStrength($newPassword); //Update secure data $customerSecure = $this->customerRegistry->retrieveSecureData($customer->getId()); $customerSecure->setRpToken(null); $customerSecure->setRpTokenCreatedAt(null); $customerSecure->setPasswordHash($this->createPasswordHash($newPassword)); - $this->getAuthentication()->unlock($customer->getId()); $this->sessionManager->destroy(); $this->destroyCustomerSessions($customer->getId()); $this->customerRepository->save($customer); @@ -781,6 +837,7 @@ public function createAccountWithPasswordHash(CustomerInterface $customer, $hash if ($customer->getWebsiteId()) { $storeId = $this->storeManager->getWebsite($customer->getWebsiteId())->getDefaultStore()->getId(); } else { + $this->storeManager->setCurrentStore(null); $storeId = $this->storeManager->getStore()->getId(); } $customer->setStoreId($storeId); @@ -955,6 +1012,8 @@ protected function createPasswordHash($password) } /** + * Get EAV validator + * * @return Backend */ private function getEavValidator() @@ -1033,10 +1092,11 @@ public function isCustomerInStore($customerWebsiteId, $storeId) * @throws \Magento\Framework\Exception\State\ExpiredException If token is expired * @throws \Magento\Framework\Exception\InputException If token or customer id is invalid * @throws \Magento\Framework\Exception\NoSuchEntityException If customer doesn't exist + * @throws LocalizedException */ private function validateResetPasswordToken($customerId, $resetPasswordLinkToken) { - if (empty($customerId) || $customerId < 0) { + if ($customerId !== null && $customerId <= 0) { throw new InputException( __( 'Invalid value of "%value" provided for the %fieldName field.', @@ -1044,21 +1104,24 @@ private function validateResetPasswordToken($customerId, $resetPasswordLinkToken ) ); } + + if ($customerId === null) { + //Looking for the customer. + $customerId = $this->matchCustomerByRpToken($resetPasswordLinkToken) + ->getId(); + } if (!is_string($resetPasswordLinkToken) || empty($resetPasswordLinkToken)) { $params = ['fieldName' => 'resetPasswordLinkToken']; throw new InputException(__('"%fieldName" is required. Enter and try again.', $params)); } - $customerSecureData = $this->customerRegistry->retrieveSecureData($customerId); $rpToken = $customerSecureData->getRpToken(); $rpTokenCreatedAt = $customerSecureData->getRpTokenCreatedAt(); - if (!Security::compareStrings($rpToken, $resetPasswordLinkToken)) { throw new InputMismatchException(__('The password token is mismatched. Reset and try again.')); } elseif ($this->isResetPasswordLinkTokenExpired($rpToken, $rpTokenCreatedAt)) { throw new ExpiredException(__('The password token is expired. Reset and try again.')); } - return true; } @@ -1141,6 +1204,7 @@ protected function sendPasswordResetNotificationEmail($customer) * @param int|string|null $defaultStoreId * @return int * @deprecated 100.1.0 + * @throws LocalizedException */ protected function getWebsiteStoreId($customer, $defaultStoreId = null) { @@ -1153,6 +1217,8 @@ protected function getWebsiteStoreId($customer, $defaultStoreId = null) } /** + * Get template types + * * @return array * @deprecated 100.1.0 */ diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Account/CreatePasswordTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Account/CreatePasswordTest.php deleted file mode 100644 index 77f41024ba02f..0000000000000 --- a/app/code/Magento/Customer/Test/Unit/Controller/Account/CreatePasswordTest.php +++ /dev/null @@ -1,233 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Customer\Test\Unit\Controller\Account; - -use Magento\Framework\Controller\Result\Redirect; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; - -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class CreatePasswordTest extends \PHPUnit\Framework\TestCase -{ - /** @var \Magento\Customer\Controller\Account\CreatePassword */ - protected $model; - - /** @var ObjectManagerHelper */ - protected $objectManagerHelper; - - /** @var \Magento\Customer\Model\Session|\PHPUnit_Framework_MockObject_MockObject */ - protected $sessionMock; - - /** @var \Magento\Framework\View\Result\PageFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $pageFactoryMock; - - /** @var \Magento\Customer\Api\AccountManagementInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $accountManagementMock; - - /** @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $requestMock; - - /** @var \Magento\Framework\Controller\Result\RedirectFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $redirectFactoryMock; - - /** @var \Magento\Framework\Message\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $messageManagerMock; - - protected function setUp() - { - $this->sessionMock = $this->getMockBuilder(\Magento\Customer\Model\Session::class) - ->disableOriginalConstructor() - ->setMethods(['setRpToken', 'setRpCustomerId', 'getRpToken', 'getRpCustomerId']) - ->getMock(); - $this->pageFactoryMock = $this->getMockBuilder(\Magento\Framework\View\Result\PageFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->accountManagementMock = $this->getMockBuilder(\Magento\Customer\Api\AccountManagementInterface::class) - ->getMockForAbstractClass(); - $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) - ->getMockForAbstractClass(); - $this->redirectFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\RedirectFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->messageManagerMock = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) - ->getMockForAbstractClass(); - - $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->model = $this->objectManagerHelper->getObject( - \Magento\Customer\Controller\Account\CreatePassword::class, - [ - 'customerSession' => $this->sessionMock, - 'resultPageFactory' => $this->pageFactoryMock, - 'accountManagement' => $this->accountManagementMock, - 'request' => $this->requestMock, - 'resultRedirectFactory' => $this->redirectFactoryMock, - 'messageManager' => $this->messageManagerMock, - ] - ); - } - - public function testExecuteWithLink() - { - $token = 'token'; - $customerId = '11'; - - $this->requestMock->expects($this->exactly(2)) - ->method('getParam') - ->willReturnMap( - [ - ['token', null, $token], - ['id', null, $customerId], - ] - ); - - $this->accountManagementMock->expects($this->once()) - ->method('validateResetPasswordLinkToken') - ->with($customerId, $token) - ->willReturn(true); - - $this->sessionMock->expects($this->once()) - ->method('setRpToken') - ->with($token); - $this->sessionMock->expects($this->once()) - ->method('setRpCustomerId') - ->with($customerId); - - /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->redirectFactoryMock->expects($this->once()) - ->method('create') - ->with([]) - ->willReturn($redirectMock); - - $redirectMock->expects($this->once()) - ->method('setPath') - ->with('*/*/createpassword', []) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } - - public function testExecuteWithSession() - { - $token = 'token'; - $customerId = '11'; - - $this->requestMock->expects($this->exactly(2)) - ->method('getParam') - ->willReturnMap( - [ - ['token', null, null], - ['id', null, $customerId], - ] - ); - - $this->sessionMock->expects($this->once()) - ->method('getRpToken') - ->willReturn($token); - $this->sessionMock->expects($this->once()) - ->method('getRpCustomerId') - ->willReturn($customerId); - - $this->accountManagementMock->expects($this->once()) - ->method('validateResetPasswordLinkToken') - ->with($customerId, $token) - ->willReturn(true); - - /** @var \Magento\Framework\View\Result\Page|\PHPUnit_Framework_MockObject_MockObject $pageMock */ - $pageMock = $this->getMockBuilder(\Magento\Framework\View\Result\Page::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->pageFactoryMock->expects($this->once()) - ->method('create') - ->with(false, []) - ->willReturn($pageMock); - - /** @var \Magento\Framework\View\Layout|\PHPUnit_Framework_MockObject_MockObject $layoutMock */ - $layoutMock = $this->getMockBuilder(\Magento\Framework\View\Layout::class) - ->disableOriginalConstructor() - ->getMock(); - - $pageMock->expects($this->once()) - ->method('getLayout') - ->willReturn($layoutMock); - - /** @var \Magento\Customer\Block\Account\Resetpassword|\PHPUnit_Framework_MockObject_MockObject $layoutMock */ - $blockMock = $this->getMockBuilder(\Magento\Customer\Block\Account\Resetpassword::class) - ->disableOriginalConstructor() - ->setMethods(['setCustomerId', 'setResetPasswordLinkToken']) - ->getMock(); - - $layoutMock->expects($this->once()) - ->method('getBlock') - ->with('resetPassword') - ->willReturn($blockMock); - - $blockMock->expects($this->once()) - ->method('setCustomerId') - ->with($customerId) - ->willReturnSelf(); - $blockMock->expects($this->once()) - ->method('setResetPasswordLinkToken') - ->with($token) - ->willReturnSelf(); - - $this->assertEquals($pageMock, $this->model->execute()); - } - - public function testExecuteWithException() - { - $token = 'token'; - $customerId = '11'; - - $this->requestMock->expects($this->exactly(2)) - ->method('getParam') - ->willReturnMap( - [ - ['token', null, $token], - ['id', null, null], - ] - ); - - $this->sessionMock->expects($this->once()) - ->method('getRpToken') - ->willReturn($token); - $this->sessionMock->expects($this->once()) - ->method('getRpCustomerId') - ->willReturn($customerId); - - $this->accountManagementMock->expects($this->once()) - ->method('validateResetPasswordLinkToken') - ->with($customerId, $token) - ->willThrowException(new \Exception('Exception.')); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with(__('Your password reset link has expired.')) - ->willReturnSelf(); - - /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->redirectFactoryMock->expects($this->once()) - ->method('create') - ->with([]) - ->willReturn($redirectMock); - - $redirectMock->expects($this->once()) - ->method('setPath') - ->with('*/*/forgotpassword', []) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } -} diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Account/ResetPasswordPostTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Account/ResetPasswordPostTest.php deleted file mode 100644 index b79ad008e5e44..0000000000000 --- a/app/code/Magento/Customer/Test/Unit/Controller/Account/ResetPasswordPostTest.php +++ /dev/null @@ -1,379 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Customer\Test\Unit\Controller\Account; - -use Magento\Framework\Controller\Result\Redirect; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; - -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class ResetPasswordPostTest extends \PHPUnit\Framework\TestCase -{ - /** @var \Magento\Customer\Controller\Account\ResetPasswordPost */ - protected $model; - - /** @var ObjectManagerHelper */ - protected $objectManagerHelper; - - /** @var \Magento\Customer\Model\Session|\PHPUnit_Framework_MockObject_MockObject */ - protected $sessionMock; - - /** @var \Magento\Framework\View\Result\PageFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $pageFactoryMock; - - /** @var \Magento\Customer\Api\AccountManagementInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $accountManagementMock; - - /** @var \Magento\Customer\Api\CustomerRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $customerRepositoryMock; - - /** @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $requestMock; - - /** @var \Magento\Framework\Controller\Result\RedirectFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $redirectFactoryMock; - - /** @var \Magento\Framework\Message\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $messageManagerMock; - - protected function setUp() - { - $this->sessionMock = $this->getMockBuilder(\Magento\Customer\Model\Session::class) - ->disableOriginalConstructor() - ->setMethods(['unsRpToken', 'unsRpCustomerId']) - ->getMock(); - $this->pageFactoryMock = $this->getMockBuilder(\Magento\Framework\View\Result\PageFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->accountManagementMock = $this->getMockBuilder(\Magento\Customer\Api\AccountManagementInterface::class) - ->getMockForAbstractClass(); - $this->customerRepositoryMock = $this->getMockBuilder(\Magento\Customer\Api\CustomerRepositoryInterface::class) - ->getMockForAbstractClass(); - $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) - ->setMethods(['getQuery', 'getPost']) - ->getMockForAbstractClass(); - $this->redirectFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\RedirectFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->messageManagerMock = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) - ->getMockForAbstractClass(); - - $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->model = $this->objectManagerHelper->getObject( - \Magento\Customer\Controller\Account\ResetPasswordPost::class, - [ - 'customerSession' => $this->sessionMock, - 'resultPageFactory' => $this->pageFactoryMock, - 'accountManagement' => $this->accountManagementMock, - 'customerRepository' => $this->customerRepositoryMock, - 'request' => $this->requestMock, - 'resultRedirectFactory' => $this->redirectFactoryMock, - 'messageManager' => $this->messageManagerMock, - ] - ); - } - - public function testExecute() - { - $token = 'token'; - $customerId = '11'; - $password = 'password'; - $passwordConfirmation = 'password'; - $email = 'email@email.com'; - - $this->requestMock->expects($this->exactly(2)) - ->method('getQuery') - ->willReturnMap( - [ - ['token', $token], - ['id', $customerId], - ] - ); - $this->requestMock->expects($this->exactly(2)) - ->method('getPost') - ->willReturnMap( - [ - ['password', $password], - ['password_confirmation', $passwordConfirmation], - ] - ); - - /** @var \Magento\Customer\Api\Data\CustomerInterface|\PHPUnit_Framework_MockObject_MockObject $customerMock */ - $customerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMockForAbstractClass(); - - $this->customerRepositoryMock->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($customerMock); - - $customerMock->expects($this->once()) - ->method('getEmail') - ->willReturn($email); - - $this->accountManagementMock->expects($this->once()) - ->method('resetPassword') - ->with($email, $token, $password) - ->willReturn(true); - - $this->sessionMock->expects($this->once()) - ->method('unsRpToken'); - $this->sessionMock->expects($this->once()) - ->method('unsRpCustomerId'); - - $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') - ->with(__('You updated your password.')) - ->willReturnSelf(); - - /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->redirectFactoryMock->expects($this->once()) - ->method('create') - ->with([]) - ->willReturn($redirectMock); - - $redirectMock->expects($this->once()) - ->method('setPath') - ->with('*/*/login', []) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } - - public function testExecuteWithException() - { - $token = 'token'; - $customerId = '11'; - $password = 'password'; - $passwordConfirmation = 'password'; - $email = 'email@email.com'; - - $this->requestMock->expects($this->exactly(2)) - ->method('getQuery') - ->willReturnMap( - [ - ['token', $token], - ['id', $customerId], - ] - ); - $this->requestMock->expects($this->exactly(2)) - ->method('getPost') - ->willReturnMap( - [ - ['password', $password], - ['password_confirmation', $passwordConfirmation], - ] - ); - - /** @var \Magento\Customer\Api\Data\CustomerInterface|\PHPUnit_Framework_MockObject_MockObject $customerMock */ - $customerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMockForAbstractClass(); - - $this->customerRepositoryMock->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($customerMock); - - $customerMock->expects($this->once()) - ->method('getEmail') - ->willReturn($email); - - $this->accountManagementMock->expects($this->once()) - ->method('resetPassword') - ->with($email, $token, $password) - ->willThrowException(new \Exception('Exception.')); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with(__('Something went wrong while saving the new password.')) - ->willReturnSelf(); - - /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->redirectFactoryMock->expects($this->once()) - ->method('create') - ->with([]) - ->willReturn($redirectMock); - - $redirectMock->expects($this->once()) - ->method('setPath') - ->with('*/*/createPassword', ['id' => $customerId, 'token' => $token]) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } - - /** - * Test for InputException - */ - public function testExecuteWithInputException() - { - $token = 'token'; - $customerId = '11'; - $password = 'password'; - $passwordConfirmation = 'password'; - $email = 'email@email.com'; - - $this->requestMock->expects($this->exactly(2)) - ->method('getQuery') - ->willReturnMap( - [ - ['token', $token], - ['id', $customerId], - ] - ); - $this->requestMock->expects($this->exactly(2)) - ->method('getPost') - ->willReturnMap( - [ - ['password', $password], - ['password_confirmation', $passwordConfirmation], - ] - ); - - /** @var \Magento\Customer\Api\Data\CustomerInterface|\PHPUnit_Framework_MockObject_MockObject $customerMock */ - $customerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMockForAbstractClass(); - - $this->customerRepositoryMock->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($customerMock); - - $customerMock->expects($this->once()) - ->method('getEmail') - ->willReturn($email); - - $this->accountManagementMock->expects($this->once()) - ->method('resetPassword') - ->with($email, $token, $password) - ->willThrowException(new \Magento\Framework\Exception\InputException(__('InputException.'))); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with(__('InputException.')) - ->willReturnSelf(); - - /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->redirectFactoryMock->expects($this->once()) - ->method('create') - ->with([]) - ->willReturn($redirectMock); - - $redirectMock->expects($this->once()) - ->method('setPath') - ->with('*/*/createPassword', ['id' => $customerId, 'token' => $token]) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } - - public function testExecuteWithWrongConfirmation() - { - $token = 'token'; - $customerId = '11'; - $password = 'password'; - $passwordConfirmation = 'wrong_password'; - - $this->requestMock->expects($this->exactly(2)) - ->method('getQuery') - ->willReturnMap( - [ - ['token', $token], - ['id', $customerId], - ] - ); - $this->requestMock->expects($this->exactly(2)) - ->method('getPost') - ->willReturnMap( - [ - ['password', $password], - ['password_confirmation', $passwordConfirmation], - ] - ); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with(__('New Password and Confirm New Password values didn\'t match.')) - ->willReturnSelf(); - - /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->redirectFactoryMock->expects($this->once()) - ->method('create') - ->with([]) - ->willReturn($redirectMock); - - $redirectMock->expects($this->once()) - ->method('setPath') - ->with('*/*/createPassword', ['id' => $customerId, 'token' => $token]) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } - - public function testExecuteWithEmptyPassword() - { - $token = 'token'; - $customerId = '11'; - $password = ''; - $passwordConfirmation = ''; - - $this->requestMock->expects($this->exactly(2)) - ->method('getQuery') - ->willReturnMap( - [ - ['token', $token], - ['id', $customerId], - ] - ); - $this->requestMock->expects($this->exactly(2)) - ->method('getPost') - ->willReturnMap( - [ - ['password', $password], - ['password_confirmation', $passwordConfirmation], - ] - ); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with(__('Please enter a new password.')) - ->willReturnSelf(); - - /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->redirectFactoryMock->expects($this->once()) - ->method('create') - ->with([]) - ->willReturn($redirectMock); - - $redirectMock->expects($this->once()) - ->method('setPath') - ->with('*/*/createPassword', ['id' => $customerId, 'token' => $token]) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } -} diff --git a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php deleted file mode 100644 index aad20f757e91e..0000000000000 --- a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php +++ /dev/null @@ -1,2057 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Customer\Test\Unit\Model; - -use Magento\Customer\Model\AccountManagement; -use Magento\Customer\Model\AccountConfirmation; -use Magento\Customer\Model\AuthenticationInterface; -use Magento\Customer\Model\EmailNotificationInterface; -use Magento\Framework\App\Area; -use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Framework\Intl\DateTimeFactory; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; -use Magento\Store\Model\ScopeInterface; - -/** - * @SuppressWarnings(PHPMD.TooManyFields) - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class AccountManagementTest extends \PHPUnit\Framework\TestCase -{ - /** @var AccountManagement */ - protected $accountManagement; - - /** @var ObjectManagerHelper */ - protected $objectManagerHelper; - - /** @var \Magento\Customer\Model\CustomerFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $customerFactory; - - /** @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $manager; - - /** @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $storeManager; - - /** @var \Magento\Framework\Math\Random|\PHPUnit_Framework_MockObject_MockObject */ - protected $random; - - /** @var \Magento\Customer\Model\Metadata\Validator|\PHPUnit_Framework_MockObject_MockObject */ - protected $validator; - - /** @var \Magento\Customer\Api\Data\ValidationResultsInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $validationResultsInterfaceFactory; - - /** @var \Magento\Customer\Api\AddressRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $addressRepository; - - /** @var \Magento\Customer\Api\CustomerMetadataInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $customerMetadata; - - /** @var \Magento\Customer\Model\CustomerRegistry|\PHPUnit_Framework_MockObject_MockObject */ - protected $customerRegistry; - - /** @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $logger; - - /** @var \Magento\Framework\Encryption\EncryptorInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $encryptor; - - /** @var \Magento\Customer\Model\Config\Share|\PHPUnit_Framework_MockObject_MockObject */ - protected $share; - - /** @var \Magento\Framework\Stdlib\StringUtils|\PHPUnit_Framework_MockObject_MockObject */ - protected $string; - - /** @var \Magento\Customer\Api\CustomerRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $customerRepository; - - /** @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $scopeConfig; - - /** @var \Magento\Framework\Mail\Template\TransportBuilder|\PHPUnit_Framework_MockObject_MockObject */ - protected $transportBuilder; - - /** @var \Magento\Framework\Reflection\DataObjectProcessor|\PHPUnit_Framework_MockObject_MockObject */ - protected $dataObjectProcessor; - - /** @var \Magento\Framework\Registry|\PHPUnit_Framework_MockObject_MockObject */ - protected $registry; - - /** @var \Magento\Customer\Helper\View|\PHPUnit_Framework_MockObject_MockObject */ - protected $customerViewHelper; - - /** @var \Magento\Framework\Stdlib\DateTime|\PHPUnit_Framework_MockObject_MockObject */ - protected $dateTime; - - /** @var \Magento\Customer\Model\Customer|\PHPUnit_Framework_MockObject_MockObject */ - protected $customer; - - /** @var \Magento\Framework\DataObjectFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $objectFactory; - - /** @var \Magento\Framework\Api\ExtensibleDataObjectConverter|\PHPUnit_Framework_MockObject_MockObject */ - protected $extensibleDataObjectConverter; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Store\Model\Store - */ - protected $store; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Customer\Model\Data\CustomerSecure - */ - protected $customerSecure; - - /** - * @var AuthenticationInterface |\PHPUnit_Framework_MockObject_MockObject - */ - protected $authenticationMock; - - /** - * @var EmailNotificationInterface |\PHPUnit_Framework_MockObject_MockObject - */ - protected $emailNotificationMock; - - /** - * @var DateTimeFactory|\PHPUnit_Framework_MockObject_MockObject - */ - private $dateTimeFactory; - - /** - * @var AccountConfirmation|\PHPUnit_Framework_MockObject_MockObject - */ - private $accountConfirmation; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Session\SessionManagerInterface - */ - private $sessionManager; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory - */ - private $visitorCollectionFactory; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Session\SaveHandlerInterface - */ - private $saveHandler; - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - protected function setUp() - { - $this->customerFactory = $this->createPartialMock(\Magento\Customer\Model\CustomerFactory::class, ['create']); - $this->manager = $this->createMock(\Magento\Framework\Event\ManagerInterface::class); - $this->store = $this->getMockBuilder(\Magento\Store\Model\Store::class) - ->disableOriginalConstructor() - ->getMock(); - $this->storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); - $this->random = $this->createMock(\Magento\Framework\Math\Random::class); - $this->validator = $this->createMock(\Magento\Customer\Model\Metadata\Validator::class); - $this->validationResultsInterfaceFactory = $this->createMock( - \Magento\Customer\Api\Data\ValidationResultsInterfaceFactory::class - ); - $this->addressRepository = $this->createMock(\Magento\Customer\Api\AddressRepositoryInterface::class); - $this->customerMetadata = $this->createMock(\Magento\Customer\Api\CustomerMetadataInterface::class); - $this->customerRegistry = $this->createMock(\Magento\Customer\Model\CustomerRegistry::class); - $this->logger = $this->createMock(\Psr\Log\LoggerInterface::class); - $this->encryptor = $this->createMock(\Magento\Framework\Encryption\EncryptorInterface::class); - $this->share = $this->createMock(\Magento\Customer\Model\Config\Share::class); - $this->string = $this->createMock(\Magento\Framework\Stdlib\StringUtils::class); - $this->customerRepository = $this->createMock(\Magento\Customer\Api\CustomerRepositoryInterface::class); - $this->scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->transportBuilder = $this->createMock(\Magento\Framework\Mail\Template\TransportBuilder::class); - $this->dataObjectProcessor = $this->createMock(\Magento\Framework\Reflection\DataObjectProcessor::class); - $this->registry = $this->createMock(\Magento\Framework\Registry::class); - $this->customerViewHelper = $this->createMock(\Magento\Customer\Helper\View::class); - $this->dateTime = $this->createMock(\Magento\Framework\Stdlib\DateTime::class); - $this->customer = $this->createMock(\Magento\Customer\Model\Customer::class); - $this->objectFactory = $this->createMock(\Magento\Framework\DataObjectFactory::class); - $this->extensibleDataObjectConverter = $this->createMock( - \Magento\Framework\Api\ExtensibleDataObjectConverter::class - ); - $this->authenticationMock = $this->getMockBuilder(AuthenticationInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->emailNotificationMock = $this->getMockBuilder(EmailNotificationInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) - ->setMethods(['setRpToken', 'addData', 'setRpTokenCreatedAt', 'setData']) - ->disableOriginalConstructor() - ->getMock(); - - $this->dateTimeFactory = $this->createMock(DateTimeFactory::class); - $this->accountConfirmation = $this->createMock(AccountConfirmation::class); - - $this->visitorCollectionFactory = $this->getMockBuilder( - \Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory::class - ) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->sessionManager = $this->getMockBuilder(\Magento\Framework\Session\SessionManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->saveHandler = $this->getMockBuilder(\Magento\Framework\Session\SaveHandlerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->accountManagement = $this->objectManagerHelper->getObject( - \Magento\Customer\Model\AccountManagement::class, - [ - 'customerFactory' => $this->customerFactory, - 'eventManager' => $this->manager, - 'storeManager' => $this->storeManager, - 'mathRandom' => $this->random, - 'validator' => $this->validator, - 'validationResultsDataFactory' => $this->validationResultsInterfaceFactory, - 'addressRepository' => $this->addressRepository, - 'customerMetadataService' => $this->customerMetadata, - 'customerRegistry' => $this->customerRegistry, - 'logger' => $this->logger, - 'encryptor' => $this->encryptor, - 'configShare' => $this->share, - 'stringHelper' => $this->string, - 'customerRepository' => $this->customerRepository, - 'scopeConfig' => $this->scopeConfig, - 'transportBuilder' => $this->transportBuilder, - 'dataProcessor' => $this->dataObjectProcessor, - 'registry' => $this->registry, - 'customerViewHelper' => $this->customerViewHelper, - 'dateTime' => $this->dateTime, - 'customerModel' => $this->customer, - 'objectFactory' => $this->objectFactory, - 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverter, - 'dateTimeFactory' => $this->dateTimeFactory, - 'accountConfirmation' => $this->accountConfirmation, - 'sessionManager' => $this->sessionManager, - 'saveHandler' => $this->saveHandler, - 'visitorCollectionFactory' => $this->visitorCollectionFactory, - ] - ); - $this->objectManagerHelper->setBackwardCompatibleProperty( - $this->accountManagement, - 'authentication', - $this->authenticationMock - ); - $this->objectManagerHelper->setBackwardCompatibleProperty( - $this->accountManagement, - 'emailNotification', - $this->emailNotificationMock - ); - } - - /** - * @expectedException \Magento\Framework\Exception\InputException - */ - public function testCreateAccountWithPasswordHashWithExistingCustomer() - { - $websiteId = 1; - $storeId = 1; - $customerId = 1; - $customerEmail = 'email@email.com'; - $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; - - $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); - $website->expects($this->once()) - ->method('getStoreIds') - ->willReturn([1, 2, 3]); - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $customer->expects($this->once()) - ->method('getId') - ->willReturn($customerId); - $customer->expects($this->once()) - ->method('getEmail') - ->willReturn($customerEmail); - $customer->expects($this->once()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $customer->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $this->customerRepository - ->expects($this->once()) - ->method('get') - ->with($customerEmail) - ->willReturn($customer); - $this->share - ->expects($this->once()) - ->method('isWebsiteScope') - ->willReturn(true); - $this->storeManager - ->expects($this->once()) - ->method('getWebsite') - ->with($websiteId) - ->willReturn($website); - $this->accountManagement->createAccountWithPasswordHash($customer, $hash); - } - - /** - * @expectedException \Magento\Framework\Exception\State\InputMismatchException - */ - public function testCreateAccountWithPasswordHashWithCustomerWithoutStoreId() - { - $websiteId = 1; - $storeId = null; - $defaultStoreId = 1; - $customerId = 1; - $customerEmail = 'email@email.com'; - $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; - - $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)->disableOriginalConstructor()->getMock(); - $store->expects($this->once()) - ->method('getId') - ->willReturn($defaultStoreId); - $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); - $website->expects($this->atLeastOnce()) - ->method('getStoreIds') - ->willReturn([1, 2, 3]); - $website->expects($this->once()) - ->method('getDefaultStore') - ->willReturn($store); - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $customer->expects($this->atLeastOnce()) - ->method('getId') - ->willReturn($customerId); - $customer->expects($this->once()) - ->method('getEmail') - ->willReturn($customerEmail); - $customer->expects($this->atLeastOnce()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $customer->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $customer->expects($this->once()) - ->method('setStoreId') - ->with($defaultStoreId); - $customer - ->expects($this->once()) - ->method('getAddresses') - ->willReturn([$address]); - $customer - ->expects($this->once()) - ->method('setAddresses') - ->with(null); - $this->customerRepository - ->expects($this->once()) - ->method('get') - ->with($customerEmail) - ->willReturn($customer); - $this->share - ->expects($this->atLeastOnce()) - ->method('isWebsiteScope') - ->willReturn(true); - $this->storeManager - ->expects($this->atLeastOnce()) - ->method('getWebsite') - ->with($websiteId) - ->willReturn($website); - $exception = new \Magento\Framework\Exception\AlreadyExistsException( - new \Magento\Framework\Phrase('Exception message') - ); - $this->customerRepository - ->expects($this->once()) - ->method('save') - ->with($customer, $hash) - ->willThrowException($exception); - - $this->accountManagement->createAccountWithPasswordHash($customer, $hash); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - */ - public function testCreateAccountWithPasswordHashWithLocalizedException() - { - $websiteId = 1; - $storeId = null; - $defaultStoreId = 1; - $customerId = 1; - $customerEmail = 'email@email.com'; - $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; - - $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)->disableOriginalConstructor()->getMock(); - $store->expects($this->once()) - ->method('getId') - ->willReturn($defaultStoreId); - $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); - $website->expects($this->once()) - ->method('getStoreIds') - ->willReturn([1, 2, 3]); - $website->expects($this->once()) - ->method('getDefaultStore') - ->willReturn($store); - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $customer->expects($this->atLeastOnce()) - ->method('getId') - ->willReturn($customerId); - $customer->expects($this->once()) - ->method('getEmail') - ->willReturn($customerEmail); - $customer->expects($this->atLeastOnce()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $customer->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $customer->expects($this->once()) - ->method('setStoreId') - ->with($defaultStoreId); - $customer - ->expects($this->once()) - ->method('getAddresses') - ->willReturn([$address]); - $customer - ->expects($this->once()) - ->method('setAddresses') - ->with(null); - $this->customerRepository - ->expects($this->once()) - ->method('get') - ->with($customerEmail) - ->willReturn($customer); - $this->share - ->expects($this->once()) - ->method('isWebsiteScope') - ->willReturn(true); - $this->storeManager - ->expects($this->atLeastOnce()) - ->method('getWebsite') - ->with($websiteId) - ->willReturn($website); - $exception = new \Magento\Framework\Exception\LocalizedException( - new \Magento\Framework\Phrase('Exception message') - ); - $this->customerRepository - ->expects($this->once()) - ->method('save') - ->with($customer, $hash) - ->willThrowException($exception); - - $this->accountManagement->createAccountWithPasswordHash($customer, $hash); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - */ - public function testCreateAccountWithPasswordHashWithAddressException() - { - $websiteId = 1; - $storeId = null; - $defaultStoreId = 1; - $customerId = 1; - $customerEmail = 'email@email.com'; - $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; - - $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $address->expects($this->once()) - ->method('setCustomerId') - ->with($customerId); - $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)->disableOriginalConstructor()->getMock(); - $store->expects($this->once()) - ->method('getId') - ->willReturn($defaultStoreId); - $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); - $website->expects($this->once()) - ->method('getStoreIds') - ->willReturn([1, 2, 3]); - $website->expects($this->once()) - ->method('getDefaultStore') - ->willReturn($store); - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $customer->expects($this->atLeastOnce()) - ->method('getId') - ->willReturn($customerId); - $customer->expects($this->once()) - ->method('getEmail') - ->willReturn($customerEmail); - $customer->expects($this->atLeastOnce()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $customer->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $customer->expects($this->once()) - ->method('setStoreId') - ->with($defaultStoreId); - $customer - ->expects($this->once()) - ->method('getAddresses') - ->willReturn([$address]); - $customer - ->expects($this->once()) - ->method('setAddresses') - ->with(null); - $this->customerRepository - ->expects($this->once()) - ->method('get') - ->with($customerEmail) - ->willReturn($customer); - $this->share - ->expects($this->once()) - ->method('isWebsiteScope') - ->willReturn(true); - $this->storeManager - ->expects($this->atLeastOnce()) - ->method('getWebsite') - ->with($websiteId) - ->willReturn($website); - $this->customerRepository - ->expects($this->once()) - ->method('save') - ->with($customer, $hash) - ->willReturn($customer); - $exception = new \Magento\Framework\Exception\InputException( - new \Magento\Framework\Phrase('Exception message') - ); - $this->addressRepository - ->expects($this->atLeastOnce()) - ->method('save') - ->with($address) - ->willThrowException($exception); - $this->customerRepository - ->expects($this->once()) - ->method('delete') - ->with($customer); - - $this->accountManagement->createAccountWithPasswordHash($customer, $hash); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - */ - public function testCreateAccountWithPasswordHashWithNewCustomerAndLocalizedException() - { - $storeId = 1; - $storeName = 'store_name'; - $websiteId = 1; - $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; - - $customerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMockForAbstractClass(); - - $customerMock->expects($this->atLeastOnce()) - ->method('getId') - ->willReturn(null); - $customerMock->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $customerMock->expects($this->atLeastOnce()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $customerMock->expects($this->once()) - ->method('setCreatedIn') - ->with($storeName) - ->willReturnSelf(); - $customerMock->expects($this->once()) - ->method('getAddresses') - ->willReturn([]); - $customerMock->expects($this->once()) - ->method('setAddresses') - ->with(null) - ->willReturnSelf(); - $this->share - ->expects($this->once()) - ->method('isWebsiteScope') - ->willReturn(true); - $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); - $website->expects($this->once()) - ->method('getStoreIds') - ->willReturn([1, 2, 3]); - $this->storeManager - ->expects($this->atLeastOnce()) - ->method('getWebsite') - ->with($websiteId) - ->willReturn($website); - - $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) - ->disableOriginalConstructor() - ->getMock(); - - $storeMock->expects($this->once()) - ->method('getName') - ->willReturn($storeName); - - $this->storeManager->expects($this->exactly(1)) - ->method('getStore') - ->with($storeId) - ->willReturn($storeMock); - $exception = new \Magento\Framework\Exception\LocalizedException( - new \Magento\Framework\Phrase('Exception message') - ); - $this->customerRepository - ->expects($this->once()) - ->method('save') - ->with($customerMock, $hash) - ->willThrowException($exception); - - $this->accountManagement->createAccountWithPasswordHash($customerMock, $hash); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testCreateAccountWithoutPassword() - { - $websiteId = 1; - $storeId = null; - $defaultStoreId = 1; - $customerId = 1; - $customerEmail = 'email@email.com'; - $newLinkToken = '2jh43j5h2345jh23lh452h345hfuzasd96ofu'; - - $datetime = $this->prepareDateTimeFactory(); - - $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $address->expects($this->once()) - ->method('setCustomerId') - ->with($customerId); - $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)->disableOriginalConstructor()->getMock(); - $store->expects($this->once()) - ->method('getId') - ->willReturn($defaultStoreId); - $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); - $website->expects($this->atLeastOnce()) - ->method('getStoreIds') - ->willReturn([1, 2, 3]); - $website->expects($this->once()) - ->method('getDefaultStore') - ->willReturn($store); - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $customer->expects($this->atLeastOnce()) - ->method('getId') - ->willReturn($customerId); - $customer->expects($this->atLeastOnce()) - ->method('getEmail') - ->willReturn($customerEmail); - $customer->expects($this->atLeastOnce()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $customer->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $customer->expects($this->once()) - ->method('setStoreId') - ->with($defaultStoreId); - $customer->expects($this->once()) - ->method('getAddresses') - ->willReturn([$address]); - $customer->expects($this->once()) - ->method('setAddresses') - ->with(null); - $this->customerRepository - ->expects($this->once()) - ->method('get') - ->with($customerEmail) - ->willReturn($customer); - $this->share->expects($this->once()) - ->method('isWebsiteScope') - ->willReturn(true); - $this->storeManager->expects($this->atLeastOnce()) - ->method('getWebsite') - ->with($websiteId) - ->willReturn($website); - $this->customerRepository->expects($this->atLeastOnce()) - ->method('save') - ->willReturn($customer); - $this->addressRepository->expects($this->atLeastOnce()) - ->method('save') - ->with($address); - $this->customerRepository->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($customer); - $this->random->expects($this->once()) - ->method('getUniqueHash') - ->willReturn($newLinkToken); - $customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) - ->setMethods(['setRpToken', 'setRpTokenCreatedAt', 'getPasswordHash']) - ->disableOriginalConstructor() - ->getMock(); - $customerSecure->expects($this->any()) - ->method('setRpToken') - ->with($newLinkToken); - $customerSecure->expects($this->any()) - ->method('setRpTokenCreatedAt') - ->with($datetime) - ->willReturnSelf(); - $customerSecure->expects($this->any()) - ->method('getPasswordHash') - ->willReturn(null); - $this->customerRegistry->expects($this->atLeastOnce()) - ->method('retrieveSecureData') - ->willReturn($customerSecure); - $this->emailNotificationMock->expects($this->once()) - ->method('newAccount') - ->willReturnSelf(); - - $this->accountManagement->createAccount($customer); - } - - /** - * Data provider for testCreateAccountWithPasswordInputException test - * - * @return array - */ - public function dataProviderCheckPasswordStrength() - { - return [ - [ - 'testNumber' => 1, - 'password' => 'qwer', - 'minPasswordLength' => 5, - 'minCharacterSetsNum' => 1, - ], - [ - 'testNumber' => 2, - 'password' => 'wrfewqedf1', - 'minPasswordLength' => 5, - 'minCharacterSetsNum' => 3, - ], - ]; - } - - /** - * @param int $testNumber - * @param string $password - * @param int $minPasswordLength - * @param int $minCharacterSetsNum - * @dataProvider dataProviderCheckPasswordStrength - */ - public function testCreateAccountWithPasswordInputException( - $testNumber, - $password, - $minPasswordLength, - $minCharacterSetsNum - ) { - $this->scopeConfig->expects($this->any()) - ->method('getValue') - ->will( - $this->returnValueMap( - [ - [ - AccountManagement::XML_PATH_MINIMUM_PASSWORD_LENGTH, - 'default', - null, - $minPasswordLength, - ], - [ - AccountManagement::XML_PATH_REQUIRED_CHARACTER_CLASSES_NUMBER, - 'default', - null, - $minCharacterSetsNum, - ], - ] - ) - ); - - $this->string->expects($this->any()) - ->method('strlen') - ->with($password) - ->willReturn(iconv_strlen($password, 'UTF-8')); - - if ($testNumber == 1) { - $this->expectException(\Magento\Framework\Exception\InputException::class); - $this->expectExceptionMessage('The password needs at least ' . $minPasswordLength . ' characters. ' - . 'Create a new password and try again.'); - } - - if ($testNumber == 2) { - $this->expectException(\Magento\Framework\Exception\InputException::class); - $this->expectExceptionMessage('Minimum of different classes of characters in password is ' . - $minCharacterSetsNum . '. Classes of characters: Lower Case, Upper Case, Digits, Special Characters.'); - } - - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $this->accountManagement->createAccount($customer, $password); - } - - /** - * @throws \Magento\Framework\Exception\LocalizedException - */ - public function testCreateAccountInputExceptionExtraLongPassword() - { - $password = '257*chars*************************************************************************************' - . '****************************************************************************************************' - . '***************************************************************'; - - $this->string->expects($this->any()) - ->method('strlen') - ->with($password) - ->willReturn(iconv_strlen($password, 'UTF-8')); - - $this->expectException(\Magento\Framework\Exception\InputException::class); - $this->expectExceptionMessage('Please enter a password with at most 256 characters.'); - - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $this->accountManagement->createAccount($customer, $password); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testCreateAccountWithPassword() - { - $websiteId = 1; - $storeId = null; - $defaultStoreId = 1; - $customerId = 1; - $customerEmail = 'email@email.com'; - $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; - $newLinkToken = '2jh43j5h2345jh23lh452h345hfuzasd96ofu'; - $templateIdentifier = 'Template Identifier'; - $sender = 'Sender'; - $password = 'wrfewqedf1'; - $minPasswordLength = 5; - $minCharacterSetsNum = 2; - - $datetime = $this->prepareDateTimeFactory(); - - $this->scopeConfig->expects($this->any()) - ->method('getValue') - ->willReturnMap( - [ - [ - AccountManagement::XML_PATH_MINIMUM_PASSWORD_LENGTH, - 'default', - null, - $minPasswordLength, - ], - [ - AccountManagement::XML_PATH_REQUIRED_CHARACTER_CLASSES_NUMBER, - 'default', - null, - $minCharacterSetsNum, - ], - [ - AccountManagement::XML_PATH_REGISTER_EMAIL_TEMPLATE, - ScopeInterface::SCOPE_STORE, - $defaultStoreId, - $templateIdentifier, - ], - [ - AccountManagement::XML_PATH_REGISTER_EMAIL_IDENTITY, - ScopeInterface::SCOPE_STORE, - 1, - $sender, - ], - ] - ); - $this->string->expects($this->any()) - ->method('strlen') - ->with($password) - ->willReturn(iconv_strlen($password, 'UTF-8')); - $this->encryptor->expects($this->once()) - ->method('getHash') - ->with($password, true) - ->willReturn($hash); - $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $address->expects($this->once()) - ->method('setCustomerId') - ->with($customerId); - $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)->disableOriginalConstructor()->getMock(); - $store->expects($this->once()) - ->method('getId') - ->willReturn($defaultStoreId); - $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); - $website->expects($this->atLeastOnce()) - ->method('getStoreIds') - ->willReturn([1, 2, 3]); - $website->expects($this->once()) - ->method('getDefaultStore') - ->willReturn($store); - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $customer->expects($this->atLeastOnce()) - ->method('getId') - ->willReturn($customerId); - $customer->expects($this->atLeastOnce()) - ->method('getEmail') - ->willReturn($customerEmail); - $customer->expects($this->atLeastOnce()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $customer->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $customer->expects($this->once()) - ->method('setStoreId') - ->with($defaultStoreId); - $customer->expects($this->once()) - ->method('getAddresses') - ->willReturn([$address]); - $customer->expects($this->once()) - ->method('setAddresses') - ->with(null); - $this->customerRepository - ->expects($this->once()) - ->method('get') - ->with($customerEmail) - ->willReturn($customer); - $this->share->expects($this->once()) - ->method('isWebsiteScope') - ->willReturn(true); - $this->storeManager->expects($this->atLeastOnce()) - ->method('getWebsite') - ->with($websiteId) - ->willReturn($website); - $this->customerRepository->expects($this->atLeastOnce()) - ->method('save') - ->willReturn($customer); - $this->addressRepository->expects($this->atLeastOnce()) - ->method('save') - ->with($address); - $this->customerRepository->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($customer); - $this->random->expects($this->once()) - ->method('getUniqueHash') - ->willReturn($newLinkToken); - $customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) - ->setMethods(['setRpToken', 'setRpTokenCreatedAt', 'getPasswordHash']) - ->disableOriginalConstructor() - ->getMock(); - $customerSecure->expects($this->any()) - ->method('setRpToken') - ->with($newLinkToken); - $customerSecure->expects($this->any()) - ->method('setRpTokenCreatedAt') - ->with($datetime) - ->willReturnSelf(); - $customerSecure->expects($this->any()) - ->method('getPasswordHash') - ->willReturn($hash); - $this->customerRegistry->expects($this->atLeastOnce()) - ->method('retrieveSecureData') - ->willReturn($customerSecure); - $this->emailNotificationMock->expects($this->once()) - ->method('newAccount') - ->willReturnSelf(); - - $this->accountManagement->createAccount($customer, $password); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testSendPasswordReminderEmail() - { - $customerId = 1; - $customerStoreId = 2; - $customerEmail = 'email@email.com'; - $customerData = ['key' => 'value']; - $customerName = 'Customer Name'; - $templateIdentifier = 'Template Identifier'; - $sender = 'Sender'; - - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMock(); - $customer->expects($this->any()) - ->method('getStoreId') - ->willReturn($customerStoreId); - $customer->expects($this->any()) - ->method('getId') - ->willReturn($customerId); - $customer->expects($this->any()) - ->method('getEmail') - ->willReturn($customerEmail); - - $this->store->expects($this->any()) - ->method('getId') - ->willReturn($customerStoreId); - - $this->storeManager->expects($this->at(0)) - ->method('getStore') - ->willReturn($this->store); - - $this->storeManager->expects($this->at(1)) - ->method('getStore') - ->with($customerStoreId) - ->willReturn($this->store); - - $this->customerRegistry->expects($this->once()) - ->method('retrieveSecureData') - ->with($customerId) - ->willReturn($this->customerSecure); - - $this->dataObjectProcessor->expects($this->once()) - ->method('buildOutputDataArray') - ->with($customer, \Magento\Customer\Api\Data\CustomerInterface::class) - ->willReturn($customerData); - - $this->customerViewHelper->expects($this->any()) - ->method('getCustomerName') - ->with($customer) - ->willReturn($customerName); - - $this->customerSecure->expects($this->once()) - ->method('addData') - ->with($customerData) - ->willReturnSelf(); - $this->customerSecure->expects($this->once()) - ->method('setData') - ->with('name', $customerName) - ->willReturnSelf(); - - $this->scopeConfig->expects($this->at(0)) - ->method('getValue') - ->with(AccountManagement::XML_PATH_REMIND_EMAIL_TEMPLATE, ScopeInterface::SCOPE_STORE, $customerStoreId) - ->willReturn($templateIdentifier); - $this->scopeConfig->expects($this->at(1)) - ->method('getValue') - ->with(AccountManagement::XML_PATH_FORGOT_EMAIL_IDENTITY, ScopeInterface::SCOPE_STORE, $customerStoreId) - ->willReturn($sender); - - $transport = $this->getMockBuilder(\Magento\Framework\Mail\TransportInterface::class) - ->getMock(); - - $this->transportBuilder->expects($this->once()) - ->method('setTemplateIdentifier') - ->with($templateIdentifier) - ->willReturnSelf(); - $this->transportBuilder->expects($this->once()) - ->method('setTemplateOptions') - ->with(['area' => Area::AREA_FRONTEND, 'store' => $customerStoreId]) - ->willReturnSelf(); - $this->transportBuilder->expects($this->once()) - ->method('setTemplateVars') - ->with(['customer' => $this->customerSecure, 'store' => $this->store]) - ->willReturnSelf(); - $this->transportBuilder->expects($this->once()) - ->method('setFrom') - ->with($sender) - ->willReturnSelf(); - $this->transportBuilder->expects($this->once()) - ->method('addTo') - ->with($customerEmail, $customerName) - ->willReturnSelf(); - $this->transportBuilder->expects($this->once()) - ->method('getTransport') - ->willReturn($transport); - - $transport->expects($this->once()) - ->method('sendMessage'); - - $this->assertEquals($this->accountManagement, $this->accountManagement->sendPasswordReminderEmail($customer)); - } - - /** - * @param string $email - * @param string $templateIdentifier - * @param string $sender - * @param int $storeId - * @param int $customerId - * @param string $hash - */ - protected function prepareInitiatePasswordReset($email, $templateIdentifier, $sender, $storeId, $customerId, $hash) - { - $websiteId = 1; - - $datetime = $this->prepareDateTimeFactory(); - - $customerData = ['key' => 'value']; - $customerName = 'Customer Name'; - - $this->store->expects($this->once()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $this->store->expects($this->any()) - ->method('getId') - ->willReturn($storeId); - - $this->storeManager->expects($this->any()) - ->method('getStore') - ->willReturn($this->store); - - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMock(); - $customer->expects($this->any()) - ->method('getEmail') - ->willReturn($email); - $customer->expects($this->any()) - ->method('getId') - ->willReturn($customerId); - $customer->expects($this->any()) - ->method('getStoreId') - ->willReturn($storeId); - - $this->customerRepository->expects($this->once()) - ->method('get') - ->with($email, $websiteId) - ->willReturn($customer); - $this->customerRepository->expects($this->once()) - ->method('save') - ->with($customer) - ->willReturnSelf(); - - $this->random->expects($this->once()) - ->method('getUniqueHash') - ->willReturn($hash); - - $this->customerViewHelper->expects($this->any()) - ->method('getCustomerName') - ->with($customer) - ->willReturn($customerName); - - $this->customerSecure->expects($this->any()) - ->method('setRpToken') - ->with($hash) - ->willReturnSelf(); - $this->customerSecure->expects($this->any()) - ->method('setRpTokenCreatedAt') - ->with($datetime) - ->willReturnSelf(); - $this->customerSecure->expects($this->any()) - ->method('addData') - ->with($customerData) - ->willReturnSelf(); - $this->customerSecure->expects($this->any()) - ->method('setData') - ->with('name', $customerName) - ->willReturnSelf(); - - $this->customerRegistry->expects($this->any()) - ->method('retrieveSecureData') - ->with($customerId) - ->willReturn($this->customerSecure); - - $this->dataObjectProcessor->expects($this->any()) - ->method('buildOutputDataArray') - ->with($customer, \Magento\Customer\Api\Data\CustomerInterface::class) - ->willReturn($customerData); - - $this->prepareEmailSend($email, $templateIdentifier, $sender, $storeId, $customerName); - } - - /** - * @param string $email - * @param int $templateIdentifier - * @param string $sender - * @param int $storeId - * @param string $customerName - */ - protected function prepareEmailSend($email, $templateIdentifier, $sender, $storeId, $customerName) - { - $transport = $this->getMockBuilder(\Magento\Framework\Mail\TransportInterface::class) - ->getMock(); - - $this->transportBuilder->expects($this->any()) - ->method('setTemplateIdentifier') - ->with($templateIdentifier) - ->willReturnSelf(); - $this->transportBuilder->expects($this->any()) - ->method('setTemplateOptions') - ->with(['area' => Area::AREA_FRONTEND, 'store' => $storeId]) - ->willReturnSelf(); - $this->transportBuilder->expects($this->any()) - ->method('setTemplateVars') - ->with(['customer' => $this->customerSecure, 'store' => $this->store]) - ->willReturnSelf(); - $this->transportBuilder->expects($this->any()) - ->method('setFrom') - ->with($sender) - ->willReturnSelf(); - $this->transportBuilder->expects($this->any()) - ->method('addTo') - ->with($email, $customerName) - ->willReturnSelf(); - $this->transportBuilder->expects($this->any()) - ->method('getTransport') - ->willReturn($transport); - - $transport->expects($this->any()) - ->method('sendMessage'); - } - - /** - * @throws \Magento\Framework\Exception\LocalizedException - */ - public function testInitiatePasswordResetEmailReminder() - { - $customerId = 1; - - $email = 'test@example.com'; - $template = AccountManagement::EMAIL_REMINDER; - $templateIdentifier = 'Template Identifier'; - $sender = 'Sender'; - - $storeId = 1; - - mt_srand(mt_rand() + (100000000 * (float)microtime()) % PHP_INT_MAX); - $hash = md5(uniqid(microtime() . mt_rand(0, mt_getrandmax()), true)); - - $this->emailNotificationMock->expects($this->once()) - ->method('passwordReminder') - ->willReturnSelf(); - - $this->prepareInitiatePasswordReset($email, $templateIdentifier, $sender, $storeId, $customerId, $hash); - - $this->assertTrue($this->accountManagement->initiatePasswordReset($email, $template)); - } - - /** - * @throws \Magento\Framework\Exception\LocalizedException - */ - public function testInitiatePasswordResetEmailReset() - { - $storeId = 1; - $customerId = 1; - - $email = 'test@example.com'; - $template = AccountManagement::EMAIL_RESET; - $templateIdentifier = 'Template Identifier'; - $sender = 'Sender'; - - mt_srand(mt_rand() + (100000000 * (float)microtime()) % PHP_INT_MAX); - $hash = md5(uniqid(microtime() . mt_rand(0, mt_getrandmax()), true)); - - $this->emailNotificationMock->expects($this->once()) - ->method('passwordResetConfirmation') - ->willReturnSelf(); - - $this->prepareInitiatePasswordReset($email, $templateIdentifier, $sender, $storeId, $customerId, $hash); - - $this->assertTrue($this->accountManagement->initiatePasswordReset($email, $template)); - } - - /** - * @throws \Magento\Framework\Exception\LocalizedException - */ - public function testInitiatePasswordResetNoTemplate() - { - $storeId = 1; - $customerId = 1; - - $email = 'test@example.com'; - $template = null; - $templateIdentifier = 'Template Identifier'; - $sender = 'Sender'; - - mt_srand(mt_rand() + (100000000 * (float)microtime()) % PHP_INT_MAX); - $hash = md5(uniqid(microtime() . mt_rand(0, mt_getrandmax()), true)); - - $this->prepareInitiatePasswordReset($email, $templateIdentifier, $sender, $storeId, $customerId, $hash); - - $this->expectException(\Magento\Framework\Exception\InputException::class); - $this->expectExceptionMessage( - 'Invalid value of "" provided for the template field. Possible values: email_reminder or email_reset.' - ); - $this->accountManagement->initiatePasswordReset($email, $template); - } - - /** - * @expectedException \Magento\Framework\Exception\InputException - * @expectedExceptionMessage Invalid value of "" provided for the customerId field - */ - public function testValidateResetPasswordTokenBadCustomerId() - { - $this->accountManagement->validateResetPasswordLinkToken(null, ''); - } - - /** - * @expectedException \Magento\Framework\Exception\InputException - * @expectedExceptionMessage "resetPasswordLinkToken" is required. Enter and try again. - */ - public function testValidateResetPasswordTokenBadResetPasswordLinkToken() - { - $this->accountManagement->validateResetPasswordLinkToken(22, null); - } - - /** - * @expectedException \Magento\Framework\Exception\State\InputMismatchException - * @expectedExceptionMessage The password token is mismatched. Reset and try again. - */ - public function testValidateResetPasswordTokenTokenMismatch() - { - $this->customerRegistry->expects($this->atLeastOnce()) - ->method('retrieveSecureData') - ->willReturn($this->customerSecure); - - $this->accountManagement->validateResetPasswordLinkToken(22, 'newStringToken'); - } - - /** - * @expectedException \Magento\Framework\Exception\State\ExpiredException - * @expectedExceptionMessage The password token is expired. Reset and try again. - */ - public function testValidateResetPasswordTokenTokenExpired() - { - $this->reInitModel(); - $this->customerRegistry->expects($this->atLeastOnce()) - ->method('retrieveSecureData') - ->willReturn($this->customerSecure); - - $this->accountManagement->validateResetPasswordLinkToken(22, 'newStringToken'); - } - - /** - * return bool - */ - public function testValidateResetPasswordToken() - { - $this->reInitModel(); - - $this->customer - ->expects($this->once()) - ->method('getResetPasswordLinkExpirationPeriod') - ->willReturn(100000); - - $this->customerRegistry->expects($this->atLeastOnce()) - ->method('retrieveSecureData') - ->willReturn($this->customerSecure); - - $this->assertTrue($this->accountManagement->validateResetPasswordLinkToken(22, 'newStringToken')); - } - - /** - * reInit $this->accountManagement object - */ - private function reInitModel() - { - $this->customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) - ->disableOriginalConstructor() - ->setMethods( - [ - 'getRpToken', - 'getRpTokenCreatedAt', - 'getPasswordHash', - 'setPasswordHash', - 'setRpToken', - 'setRpTokenCreatedAt', - ] - ) - ->getMock(); - $this->customerSecure->expects($this->any()) - ->method('getRpToken') - ->willReturn('newStringToken'); - $pastDateTime = '2016-10-25 00:00:00'; - $this->customerSecure->expects($this->any()) - ->method('getRpTokenCreatedAt') - ->willReturn($pastDateTime); - $this->customer = $this->getMockBuilder(\Magento\Customer\Model\Customer::class) - ->disableOriginalConstructor() - ->setMethods(['getResetPasswordLinkExpirationPeriod']) - ->getMock(); - - $this->prepareDateTimeFactory(); - $this->sessionManager = $this->getMockBuilder(\Magento\Framework\Session\SessionManagerInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $this->visitorCollectionFactory = $this->getMockBuilder( - \Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory::class - ) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->saveHandler = $this->getMockBuilder(\Magento\Framework\Session\SaveHandlerInterface::class) - ->disableOriginalConstructor() - ->setMethods(['destroy']) - ->getMockForAbstractClass(); - - $dateTime = '2017-10-25 18:57:08'; - $timestamp = '1508983028'; - $dateTimeMock = $this->getMockBuilder(\DateTime::class) - ->disableOriginalConstructor() - ->setMethods(['format', 'getTimestamp', 'setTimestamp']) - ->getMock(); - - $dateTimeMock->expects($this->any()) - ->method('format') - ->with(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT) - ->willReturn($dateTime); - $dateTimeMock->expects($this->any()) - ->method('getTimestamp') - ->willReturn($timestamp); - $dateTimeMock->expects($this->any()) - ->method('setTimestamp') - ->willReturnSelf(); - $dateTimeFactory = $this->getMockBuilder(DateTimeFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $dateTimeFactory->expects($this->any())->method('create')->willReturn($dateTimeMock); - - $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->accountManagement = $this->objectManagerHelper->getObject( - \Magento\Customer\Model\AccountManagement::class, - [ - 'customerFactory' => $this->customerFactory, - 'customerRegistry' => $this->customerRegistry, - 'customerRepository' => $this->customerRepository, - 'customerModel' => $this->customer, - 'dateTimeFactory' => $dateTimeFactory, - 'stringHelper' => $this->string, - 'scopeConfig' => $this->scopeConfig, - 'sessionManager' => $this->sessionManager, - 'visitorCollectionFactory' => $this->visitorCollectionFactory, - 'saveHandler' => $this->saveHandler, - 'encryptor' => $this->encryptor, - 'dataProcessor' => $this->dataObjectProcessor, - 'storeManager' => $this->storeManager, - 'transportBuilder' => $this->transportBuilder, - ] - ); - $this->objectManagerHelper->setBackwardCompatibleProperty( - $this->accountManagement, - 'authentication', - $this->authenticationMock - ); - } - - /** - * @return void - */ - public function testChangePassword() - { - $customerId = 7; - $email = 'test@example.com'; - $currentPassword = '1234567'; - $newPassword = 'abcdefg'; - $passwordHash = '1a2b3f4c'; - - $this->reInitModel(); - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMock(); - $customer->expects($this->any()) - ->method('getId') - ->willReturn($customerId); - - $this->customerRepository - ->expects($this->once()) - ->method('get') - ->with($email) - ->willReturn($customer); - - $this->authenticationMock->expects($this->once()) - ->method('authenticate'); - - $this->customerSecure->expects($this->once()) - ->method('setRpToken') - ->with(null); - $this->customerSecure->expects($this->once()) - ->method('setRpTokenCreatedAt') - ->willReturnSelf(); - $this->customerSecure->expects($this->any()) - ->method('getPasswordHash') - ->willReturn($passwordHash); - - $this->customerRegistry->expects($this->any()) - ->method('retrieveSecureData') - ->with($customerId) - ->willReturn($this->customerSecure); - - $this->scopeConfig->expects($this->any()) - ->method('getValue') - ->willReturnMap( - [ - [ - AccountManagement::XML_PATH_MINIMUM_PASSWORD_LENGTH, - 'default', - null, - 7, - ], - [ - AccountManagement::XML_PATH_REQUIRED_CHARACTER_CLASSES_NUMBER, - 'default', - null, - 1, - ], - ] - ); - $this->string->expects($this->any()) - ->method('strlen') - ->with($newPassword) - ->willReturn(7); - - $this->customerRepository - ->expects($this->once()) - ->method('save') - ->with($customer); - - $this->sessionManager->expects($this->atLeastOnce())->method('getSessionId'); - - $visitor = $this->getMockBuilder(\Magento\Customer\Model\Visitor::class) - ->disableOriginalConstructor() - ->setMethods(['getSessionId']) - ->getMock(); - $visitor->expects($this->atLeastOnce())->method('getSessionId') - ->willReturnOnConsecutiveCalls('session_id_1', 'session_id_2'); - $visitorCollection = $this->getMockBuilder( - \Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory::class - ) - ->disableOriginalConstructor()->setMethods(['addFieldToFilter', 'getItems'])->getMock(); - $visitorCollection->expects($this->atLeastOnce())->method('addFieldToFilter')->willReturnSelf(); - $visitorCollection->expects($this->atLeastOnce())->method('getItems')->willReturn([$visitor, $visitor]); - $this->visitorCollectionFactory->expects($this->atLeastOnce())->method('create') - ->willReturn($visitorCollection); - $this->saveHandler->expects($this->atLeastOnce())->method('destroy') - ->withConsecutive( - ['session_id_1'], - ['session_id_2'] - ); - - $this->assertTrue($this->accountManagement->changePassword($email, $currentPassword, $newPassword)); - } - - /** - * @throws \Magento\Framework\Exception\LocalizedException - */ - public function testResetPassword() - { - $customerEmail = 'customer@example.com'; - $customerId = '1'; - $resetToken = 'newStringToken'; - $newPassword = 'new_password'; - - $this->reInitModel(); - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $customer->expects($this->any())->method('getId')->willReturn($customerId); - $this->customerRepository->expects($this->atLeastOnce())->method('get')->with($customerEmail) - ->willReturn($customer); - $this->customer->expects($this->atLeastOnce())->method('getResetPasswordLinkExpirationPeriod') - ->willReturn(100000); - $this->string->expects($this->any())->method('strlen')->willReturnCallback( - function ($string) { - return strlen($string); - } - ); - $this->customerRegistry->expects($this->atLeastOnce())->method('retrieveSecureData') - ->willReturn($this->customerSecure); - - $this->customerSecure->expects($this->once())->method('setRpToken')->with(null); - $this->customerSecure->expects($this->once())->method('setRpTokenCreatedAt')->with(null); - $this->customerSecure->expects($this->any())->method('setPasswordHash')->willReturn(null); - - $this->sessionManager->expects($this->atLeastOnce())->method('destroy'); - $this->sessionManager->expects($this->atLeastOnce())->method('getSessionId'); - $visitor = $this->getMockBuilder(\Magento\Customer\Model\Visitor::class) - ->disableOriginalConstructor() - ->setMethods(['getSessionId']) - ->getMock(); - $visitor->expects($this->atLeastOnce())->method('getSessionId') - ->willReturnOnConsecutiveCalls('session_id_1', 'session_id_2'); - $visitorCollection = $this->getMockBuilder( - \Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory::class - ) - ->disableOriginalConstructor()->setMethods(['addFieldToFilter', 'getItems'])->getMock(); - $visitorCollection->expects($this->atLeastOnce())->method('addFieldToFilter')->willReturnSelf(); - $visitorCollection->expects($this->atLeastOnce())->method('getItems')->willReturn([$visitor, $visitor]); - $this->visitorCollectionFactory->expects($this->atLeastOnce())->method('create') - ->willReturn($visitorCollection); - $this->saveHandler->expects($this->atLeastOnce())->method('destroy') - ->withConsecutive( - ['session_id_1'], - ['session_id_2'] - ); - $this->assertTrue($this->accountManagement->resetPassword($customerEmail, $resetToken, $newPassword)); - } - - /** - * @return void - */ - public function testChangePasswordException() - { - $email = 'test@example.com'; - $currentPassword = '1234567'; - $newPassword = 'abcdefg'; - - $exception = new NoSuchEntityException( - new \Magento\Framework\Phrase('Exception message') - ); - $this->customerRepository - ->expects($this->once()) - ->method('get') - ->with($email) - ->willThrowException($exception); - - $this->expectException(\Magento\Framework\Exception\InvalidEmailOrPasswordException::class); - $this->expectExceptionMessage('Invalid login or password.'); - - $this->accountManagement->changePassword($email, $currentPassword, $newPassword); - } - - /** - * @return void - */ - public function testAuthenticate() - { - $username = 'login'; - $password = '1234567'; - $passwordHash = '1a2b3f4c'; - - $customerData = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMock(); - - $customerModel = $this->getMockBuilder(\Magento\Customer\Model\Customer::class) - ->disableOriginalConstructor() - ->getMock(); - $customerModel->expects($this->once()) - ->method('updateData') - ->willReturn($customerModel); - - $this->customerRepository - ->expects($this->once()) - ->method('get') - ->with($username) - ->willReturn($customerData); - - $this->authenticationMock->expects($this->once()) - ->method('authenticate'); - - $customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) - ->setMethods(['getPasswordHash']) - ->disableOriginalConstructor() - ->getMock(); - $customerSecure->expects($this->any()) - ->method('getPasswordHash') - ->willReturn($passwordHash); - - $this->customerRegistry->expects($this->any()) - ->method('retrieveSecureData') - ->willReturn($customerSecure); - - $this->customerFactory->expects($this->once()) - ->method('create') - ->willReturn($customerModel); - - $this->manager->expects($this->exactly(2)) - ->method('dispatch') - ->withConsecutive( - [ - 'customer_customer_authenticated', - ['model' => $customerModel, 'password' => $password], - ], - [ - 'customer_data_object_login', ['customer' => $customerData], - ] - ); - - $this->assertEquals($customerData, $this->accountManagement->authenticate($username, $password)); - } - - /** - * @param int $isConfirmationRequired - * @param string|null $confirmation - * @param string $expected - * @dataProvider dataProviderGetConfirmationStatus - */ - public function testGetConfirmationStatus( - $isConfirmationRequired, - $confirmation, - $expected - ) { - $websiteId = 1; - $customerId = 1; - $customerEmail = 'test1@example.com'; - - $customerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $customerMock->expects($this->once()) - ->method('getId') - ->willReturn($customerId); - $customerMock->expects($this->any()) - ->method('getConfirmation') - ->willReturn($confirmation); - $customerMock->expects($this->once()) - ->method('getEmail') - ->willReturn($customerEmail); - $customerMock->expects($this->once()) - ->method('getWebsiteId') - ->willReturn($websiteId); - - $this->accountConfirmation->expects($this->once()) - ->method('isConfirmationRequired') - ->with($websiteId, $customerId, $customerEmail) - ->willReturn($isConfirmationRequired); - - $this->customerRepository->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($customerMock); - - $this->assertEquals($expected, $this->accountManagement->getConfirmationStatus($customerId)); - } - - /** - * @return array - */ - public function dataProviderGetConfirmationStatus() - { - return [ - [0, null, AccountManagement::ACCOUNT_CONFIRMATION_NOT_REQUIRED], - [0, null, AccountManagement::ACCOUNT_CONFIRMATION_NOT_REQUIRED], - [0, null, AccountManagement::ACCOUNT_CONFIRMATION_NOT_REQUIRED], - [1, null, AccountManagement::ACCOUNT_CONFIRMED], - [1, 'test', AccountManagement::ACCOUNT_CONFIRMATION_REQUIRED], - ]; - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - */ - public function testCreateAccountWithPasswordHashForGuest() - { - $storeId = 1; - $storeName = 'store_name'; - $websiteId = 1; - $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; - - $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) - ->disableOriginalConstructor() - ->getMock(); - $storeMock->expects($this->once()) - ->method('getId') - ->willReturn($storeId); - $storeMock->expects($this->once()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $storeMock->expects($this->once()) - ->method('getName') - ->willReturn($storeName); - - $this->storeManager->expects($this->exactly(3)) - ->method('getStore') - ->willReturn($storeMock); - - $customerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMockForAbstractClass(); - $customerMock->expects($this->exactly(2)) - ->method('getId') - ->willReturn(null); - $customerMock->expects($this->exactly(3)) - ->method('getStoreId') - ->willReturn(null); - $customerMock->expects($this->exactly(3)) - ->method('getWebsiteId') - ->willReturn(null); - $customerMock->expects($this->once()) - ->method('setStoreId') - ->with($storeId) - ->willReturnSelf(); - $customerMock->expects($this->once()) - ->method('setWebsiteId') - ->with($websiteId) - ->willReturnSelf(); - $customerMock->expects($this->once()) - ->method('setCreatedIn') - ->with($storeName) - ->willReturnSelf(); - $customerMock->expects($this->once()) - ->method('getAddresses') - ->willReturn(null); - $customerMock->expects($this->once()) - ->method('setAddresses') - ->with(null) - ->willReturnSelf(); - - $this->customerRepository - ->expects($this->once()) - ->method('save') - ->with($customerMock, $hash) - ->willThrowException(new \Magento\Framework\Exception\LocalizedException(__('Exception message'))); - - $this->accountManagement->createAccountWithPasswordHash($customerMock, $hash); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testCreateAccountWithPasswordHashWithCustomerAddresses() - { - $websiteId = 1; - $addressId = 2; - $customerId = null; - $storeId = 1; - $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; - - $this->prepareDateTimeFactory(); - - //Handle store - $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)->disableOriginalConstructor()->getMock(); - $store->expects($this->any()) - ->method('getWebsiteId') - ->willReturn($websiteId); - //Handle address - existing and non-existing. Non-Existing should return null when call getId method - $existingAddress = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $nonExistingAddress = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->disableOriginalConstructor() - ->getMock(); - //Ensure that existing address is not in use - $this->addressRepository - ->expects($this->atLeastOnce()) - ->method("save") - ->withConsecutive( - [$this->logicalNot($this->identicalTo($existingAddress))], - [$this->identicalTo($nonExistingAddress)] - ); - - $existingAddress - ->expects($this->any()) - ->method("getId") - ->willReturn($addressId); - //Expects that id for existing address should be unset - $existingAddress - ->expects($this->once()) - ->method("setId") - ->with(null); - //Handle Customer calls - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $customer - ->expects($this->atLeastOnce()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $customer - ->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $customer - ->expects($this->any()) - ->method("getId") - ->willReturn($customerId); - //Return Customer from customer repository - $this->customerRepository - ->expects($this->atLeastOnce()) - ->method('save') - ->willReturn($customer); - $this->customerRepository - ->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($customer); - $customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) - ->setMethods(['setRpToken', 'setRpTokenCreatedAt', 'getPasswordHash']) - ->disableOriginalConstructor() - ->getMock(); - $customerSecure->expects($this->once()) - ->method('setRpToken') - ->with($hash); - - $customerSecure->expects($this->any()) - ->method('getPasswordHash') - ->willReturn($hash); - - $this->customerRegistry->expects($this->any()) - ->method('retrieveSecureData') - ->with($customerId) - ->willReturn($customerSecure); - - $this->random->expects($this->once()) - ->method('getUniqueHash') - ->willReturn($hash); - - $customer - ->expects($this->atLeastOnce()) - ->method('getAddresses') - ->willReturn([$existingAddress, $nonExistingAddress]); - - $this->storeManager - ->expects($this->atLeastOnce()) - ->method('getStore') - ->willReturn($store); - $this->share - ->expects($this->once()) - ->method('isWebsiteScope') - ->willReturn(true); - $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); - $website->expects($this->once()) - ->method('getStoreIds') - ->willReturn([1, 2, 3]); - $this->storeManager - ->expects($this->atLeastOnce()) - ->method('getWebsite') - ->with($websiteId) - ->willReturn($website); - - $this->assertSame($customer, $this->accountManagement->createAccountWithPasswordHash($customer, $hash)); - } - - /** - * @return string - */ - private function prepareDateTimeFactory() - { - $dateTime = '2017-10-25 18:57:08'; - $timestamp = '1508983028'; - $dateTimeMock = $this->createMock(\DateTime::class); - $dateTimeMock->expects($this->any()) - ->method('format') - ->with(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT) - ->willReturn($dateTime); - - $dateTimeMock - ->expects($this->any()) - ->method('getTimestamp') - ->willReturn($timestamp); - - $this->dateTimeFactory - ->expects($this->any()) - ->method('create') - ->willReturn($dateTimeMock); - - return $dateTime; - } - - /** - * @return void - */ - public function testCreateAccountUnexpectedValueException(): void - { - $websiteId = 1; - $storeId = null; - $defaultStoreId = 1; - $customerId = 1; - $customerEmail = 'email@email.com'; - $newLinkToken = '2jh43j5h2345jh23lh452h345hfuzasd96ofu'; - $exception = new \UnexpectedValueException('Template file was not found'); - - $datetime = $this->prepareDateTimeFactory(); - - $address = $this->createMock(\Magento\Customer\Api\Data\AddressInterface::class); - $address->expects($this->once()) - ->method('setCustomerId') - ->with($customerId); - $store = $this->createMock(\Magento\Store\Model\Store::class); - $store->expects($this->once()) - ->method('getId') - ->willReturn($defaultStoreId); - $website = $this->createMock(\Magento\Store\Model\Website::class); - $website->expects($this->atLeastOnce()) - ->method('getStoreIds') - ->willReturn([1, 2, 3]); - $website->expects($this->once()) - ->method('getDefaultStore') - ->willReturn($store); - $customer = $this->createMock(\Magento\Customer\Api\Data\CustomerInterface::class); - $customer->expects($this->atLeastOnce()) - ->method('getId') - ->willReturn($customerId); - $customer->expects($this->atLeastOnce()) - ->method('getEmail') - ->willReturn($customerEmail); - $customer->expects($this->atLeastOnce()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $customer->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $customer->expects($this->once()) - ->method('setStoreId') - ->with($defaultStoreId); - $customer->expects($this->once()) - ->method('getAddresses') - ->willReturn([$address]); - $customer->expects($this->once()) - ->method('setAddresses') - ->with(null); - $this->customerRepository->expects($this->once()) - ->method('get') - ->with($customerEmail) - ->willReturn($customer); - $this->share->expects($this->once()) - ->method('isWebsiteScope') - ->willReturn(true); - $this->storeManager->expects($this->atLeastOnce()) - ->method('getWebsite') - ->with($websiteId) - ->willReturn($website); - $this->customerRepository->expects($this->atLeastOnce()) - ->method('save') - ->willReturn($customer); - $this->addressRepository->expects($this->atLeastOnce()) - ->method('save') - ->with($address); - $this->customerRepository->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($customer); - $this->random->expects($this->once()) - ->method('getUniqueHash') - ->willReturn($newLinkToken); - $customerSecure = $this->createPartialMock( - \Magento\Customer\Model\Data\CustomerSecure::class, - ['setRpToken', 'setRpTokenCreatedAt', 'getPasswordHash'] - ); - $customerSecure->expects($this->any()) - ->method('setRpToken') - ->with($newLinkToken); - $customerSecure->expects($this->any()) - ->method('setRpTokenCreatedAt') - ->with($datetime) - ->willReturnSelf(); - $customerSecure->expects($this->any()) - ->method('getPasswordHash') - ->willReturn(null); - $this->customerRegistry->expects($this->atLeastOnce()) - ->method('retrieveSecureData') - ->willReturn($customerSecure); - $this->emailNotificationMock->expects($this->once()) - ->method('newAccount') - ->willThrowException($exception); - $this->logger->expects($this->once())->method('error')->with($exception); - - $this->accountManagement->createAccount($customer); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - */ - public function testCreateAccountWithStoreNotInWebsite() - { - $storeId = 1; - $websiteId = 1; - $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; - $customerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMockForAbstractClass(); - $customerMock->expects($this->atLeastOnce()) - ->method('getId') - ->willReturn(null); - $customerMock->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $customerMock->expects($this->atLeastOnce()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $this->share - ->expects($this->once()) - ->method('isWebsiteScope') - ->willReturn(true); - $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); - $website->expects($this->once()) - ->method('getStoreIds') - ->willReturn([2, 3]); - $this->storeManager - ->expects($this->atLeastOnce()) - ->method('getWebsite') - ->with($websiteId) - ->willReturn($website); - $this->accountManagement->createAccountWithPasswordHash($customerMock, $hash); - } -} diff --git a/app/code/Magento/Customer/view/adminhtml/web/edit/post-wrapper.js b/app/code/Magento/Customer/view/adminhtml/web/edit/post-wrapper.js index 76b060015c5ff..27fa5eb783bba 100644 --- a/app/code/Magento/Customer/view/adminhtml/web/edit/post-wrapper.js +++ b/app/code/Magento/Customer/view/adminhtml/web/edit/post-wrapper.js @@ -44,4 +44,12 @@ define([ return false; }); + + $('#resetPassword').click(function () { + var url = $('#resetPassword').data('url'); + + getForm(url).appendTo('body').submit(); + + return false; + }); }); diff --git a/app/code/Magento/Customer/view/frontend/email/password_reset_confirmation.html b/app/code/Magento/Customer/view/frontend/email/password_reset_confirmation.html index 6c17762a88227..59e7f16adfd51 100644 --- a/app/code/Magento/Customer/view/frontend/email/password_reset_confirmation.html +++ b/app/code/Magento/Customer/view/frontend/email/password_reset_confirmation.html @@ -21,7 +21,7 @@ <table class="inner-wrapper" border="0" cellspacing="0" cellpadding="0" align="center"> <tr> <td align="center"> - <a href="{{var this.getUrl($store,'customer/account/createPassword/',[_query:[id:$customer.id,token:$customer.rp_token],_nosid:1])}}" target="_blank">{{trans "Set a New Password"}}</a> + <a href="{{var this.getUrl($store,'customer/account/createPassword/',[_query:[token:$customer.rp_token],_nosid:1])}}" target="_blank">{{trans "Set a New Password"}}</a> </td> </tr> </table> diff --git a/app/code/Magento/Customer/view/frontend/templates/form/resetforgottenpassword.phtml b/app/code/Magento/Customer/view/frontend/templates/form/resetforgottenpassword.phtml index 15e570da04beb..e79cea80ac838 100644 --- a/app/code/Magento/Customer/view/frontend/templates/form/resetforgottenpassword.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/form/resetforgottenpassword.phtml @@ -8,7 +8,7 @@ /** @var \Magento\Customer\Block\Account\Resetpassword $block */ ?> -<form action="<?= $block->escapeUrl($block->getUrl('*/*/resetpasswordpost', ['_query' => ['id' => $block->getCustomerId(), 'token' => $block->getResetPasswordLinkToken()]])) ?>" +<form action="<?= $block->escapeUrl($block->getUrl('*/*/resetpasswordpost', ['_query' => ['token' => $block->getResetPasswordLinkToken()]])) ?>" method="post" <?php if ($block->isAutocompleteDisabled()) :?> autocomplete="off"<?php endif; ?> id="form-validate" diff --git a/app/code/Magento/GraphQl/Controller/GraphQl.php b/app/code/Magento/GraphQl/Controller/GraphQl.php index 493cabfaee922..c4a0b55de9bfc 100644 --- a/app/code/Magento/GraphQl/Controller/GraphQl.php +++ b/app/code/Magento/GraphQl/Controller/GraphQl.php @@ -17,6 +17,7 @@ use Magento\Framework\GraphQl\Schema\SchemaGeneratorInterface; use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\Webapi\Response; +use Magento\Framework\GraphQl\Query\Fields as QueryFields; /** * Front controller for web API GraphQL area. @@ -60,6 +61,11 @@ class GraphQl implements FrontControllerInterface */ private $requestProcessor; + /** + * @var QueryFields + */ + private $queryFields; + /** * @param Response $response * @param SchemaGeneratorInterface $schemaGenerator @@ -68,6 +74,7 @@ class GraphQl implements FrontControllerInterface * @param \Magento\Framework\GraphQl\Exception\ExceptionFormatter $graphQlError * @param \Magento\Framework\GraphQl\Query\Resolver\ContextInterface $resolverContext * @param HttpRequestProcessor $requestProcessor + * @param QueryFields $queryFields */ public function __construct( Response $response, @@ -76,7 +83,8 @@ public function __construct( QueryProcessor $queryProcessor, ExceptionFormatter $graphQlError, ContextInterface $resolverContext, - HttpRequestProcessor $requestProcessor + HttpRequestProcessor $requestProcessor, + QueryFields $queryFields ) { $this->response = $response; $this->schemaGenerator = $schemaGenerator; @@ -85,6 +93,7 @@ public function __construct( $this->graphQlError = $graphQlError; $this->resolverContext = $resolverContext; $this->requestProcessor = $requestProcessor; + $this->queryFields = $queryFields; } /** @@ -100,10 +109,17 @@ public function dispatch(RequestInterface $request) : ResponseInterface /** @var Http $request */ $this->requestProcessor->processHeaders($request); $data = $this->jsonSerializer->unserialize($request->getContent()); + + $query = isset($data['query']) ? $data['query'] : ''; + + // We have to extract queried field names to avoid instantiation of non necessary fields in webonyx schema + // Temporal coupling is required for performance optimization + $this->queryFields->setQuery($query); $schema = $this->schemaGenerator->generate(); + $result = $this->queryProcessor->process( $schema, - isset($data['query']) ? $data['query'] : '', + $query, $this->resolverContext, isset($data['variables']) ? $data['variables'] : [] ); diff --git a/app/code/Magento/MessageQueue/Model/Cron/ConsumersRunner.php b/app/code/Magento/MessageQueue/Model/Cron/ConsumersRunner.php index f301fb9289efb..f5011248d7772 100644 --- a/app/code/Magento/MessageQueue/Model/Cron/ConsumersRunner.php +++ b/app/code/Magento/MessageQueue/Model/Cron/ConsumersRunner.php @@ -5,9 +5,13 @@ */ namespace Magento\MessageQueue\Model\Cron; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\MessageQueue\ConnectionTypeResolver; +use Magento\Framework\MessageQueue\Consumer\Config\ConsumerConfigItemInterface; use Magento\Framework\ShellInterface; use Magento\Framework\MessageQueue\Consumer\ConfigInterface as ConsumerConfigInterface; use Magento\Framework\App\DeploymentConfig; +use Psr\Log\LoggerInterface; use Symfony\Component\Process\PhpExecutableFinder; use Magento\MessageQueue\Model\Cron\ConsumersRunner\PidConsumerManager; @@ -56,6 +60,16 @@ class ConsumersRunner */ private $pidConsumerManager; + /** + * @var ConnectionTypeResolver + */ + private $mqConnectionTypeResolver; + + /** + * @var LoggerInterface + */ + private $logger; + /** * @param PhpExecutableFinder $phpExecutableFinder The executable finder specifically designed * for the PHP executable @@ -63,19 +77,27 @@ class ConsumersRunner * @param DeploymentConfig $deploymentConfig The application deployment configuration * @param ShellInterface $shellBackground The shell command line wrapper for executing command in background * @param PidConsumerManager $pidConsumerManager The class for checking status of process by PID + * @param ConnectionTypeResolver $mqConnectionTypeResolver Consumer connection resolver + * @param LoggerInterface $logger Logger */ public function __construct( PhpExecutableFinder $phpExecutableFinder, ConsumerConfigInterface $consumerConfig, DeploymentConfig $deploymentConfig, ShellInterface $shellBackground, - PidConsumerManager $pidConsumerManager + PidConsumerManager $pidConsumerManager, + ConnectionTypeResolver $mqConnectionTypeResolver = null, + LoggerInterface $logger = null ) { $this->phpExecutableFinder = $phpExecutableFinder; $this->consumerConfig = $consumerConfig; $this->deploymentConfig = $deploymentConfig; $this->shellBackground = $shellBackground; $this->pidConsumerManager = $pidConsumerManager; + $this->mqConnectionTypeResolver = $mqConnectionTypeResolver + ?: ObjectManager::getInstance()->get(ConnectionTypeResolver::class); + $this->logger = $logger + ?: ObjectManager::getInstance()->get(LoggerInterface::class); } /** @@ -94,12 +116,12 @@ public function run() $php = $this->phpExecutableFinder->find() ?: 'php'; foreach ($this->consumerConfig->getConsumers() as $consumer) { - $consumerName = $consumer->getName(); - - if (!$this->canBeRun($consumerName, $allowedConsumers)) { + if (!$this->canBeRun($consumer, $allowedConsumers)) { continue; } + $consumerName = $consumer->getName(); + $arguments = [ $consumerName, '--pid-file-path=' . $this->getPidFilePath($consumerName), @@ -119,16 +141,37 @@ public function run() /** * Checks that the consumer can be run * - * @param string $consumerName The consumer name + * @param ConsumerConfigItemInterface $consumerConfig The consumer config * @param array $allowedConsumers The list of allowed consumers * If $allowedConsumers is empty it means that all consumers are allowed * @return bool Returns true if the consumer can be run + * @throws \Magento\Framework\Exception\FileSystemException */ - private function canBeRun($consumerName, array $allowedConsumers = []) + private function canBeRun(ConsumerConfigItemInterface $consumerConfig, array $allowedConsumers = []): bool { - $allowed = empty($allowedConsumers) ?: in_array($consumerName, $allowedConsumers); + $consumerName = $consumerConfig->getName(); + if (!empty($allowedConsumers) && !in_array($consumerName, $allowedConsumers)) { + return false; + } + + if ($this->pidConsumerManager->isRun($this->getPidFilePath($consumerName))) { + return false; + } + + $connectionName = $consumerConfig->getConnection(); + try { + $this->mqConnectionTypeResolver->getConnectionType($connectionName); + } catch (\LogicException $e) { + $this->logger->info(sprintf( + 'Consumer "%s" skipped as required connection "%s" is not configured. %s', + $consumerName, + $connectionName, + $e->getMessage() + )); + return false; + } - return $allowed && !$this->pidConsumerManager->isRun($this->getPidFilePath($consumerName)); + return true; } /** diff --git a/app/code/Magento/MessageQueue/Test/Unit/Model/Cron/ConsumersRunnerTest.php b/app/code/Magento/MessageQueue/Test/Unit/Model/Cron/ConsumersRunnerTest.php index bf8796a03f52a..006354b997d3a 100644 --- a/app/code/Magento/MessageQueue/Test/Unit/Model/Cron/ConsumersRunnerTest.php +++ b/app/code/Magento/MessageQueue/Test/Unit/Model/Cron/ConsumersRunnerTest.php @@ -5,6 +5,7 @@ */ namespace Magento\MessageQueue\Test\Unit\Model\Cron; +use Magento\Framework\MessageQueue\ConnectionTypeResolver; use \PHPUnit_Framework_MockObject_MockObject as MockObject; use Magento\Framework\ShellInterface; use Magento\Framework\MessageQueue\Consumer\ConfigInterface as ConsumerConfigInterface; @@ -41,6 +42,11 @@ class ConsumersRunnerTest extends \PHPUnit\Framework\TestCase */ private $phpExecutableFinderMock; + /** + * @var ConnectionTypeResolver + */ + private $connectionTypeResover; + /** * @var ConsumersRunner */ @@ -66,13 +72,18 @@ protected function setUp() $this->deploymentConfigMock = $this->getMockBuilder(DeploymentConfig::class) ->disableOriginalConstructor() ->getMock(); + $this->connectionTypeResover = $this->getMockBuilder(ConnectionTypeResolver::class) + ->disableOriginalConstructor() + ->getMock(); + $this->connectionTypeResover->method('getConnectionType')->willReturn('something'); $this->consumersRunner = new ConsumersRunner( $this->phpExecutableFinderMock, $this->consumerConfigMock, $this->deploymentConfigMock, $this->shellBackgroundMock, - $this->pidConsumerManagerMock + $this->pidConsumerManagerMock, + $this->connectionTypeResover ); } diff --git a/app/code/Magento/Rule/Model/Condition/Sql/Builder.php b/app/code/Magento/Rule/Model/Condition/Sql/Builder.php index 0b824ca94ca3e..995999c3a0cde 100644 --- a/app/code/Magento/Rule/Model/Condition/Sql/Builder.php +++ b/app/code/Magento/Rule/Model/Condition/Sql/Builder.php @@ -41,6 +41,14 @@ class Builder '!()' => ':field NOT IN (?)', ]; + /** + * @var array + */ + private $stringConditionOperatorMap = [ + '{}' => ':field LIKE ?', + '!{}' => ':field NOT LIKE ?', + ]; + /** * @var \Magento\Rule\Model\Condition\Sql\ExpressionFactory */ @@ -152,15 +160,27 @@ protected function _getMappedSqlCondition( } $defaultValue = 0; - $sql = str_replace( - ':field', - $this->_connection->getIfNullSql($this->_connection->quoteIdentifier($argument), $defaultValue), - $this->_conditionOperatorMap[$conditionOperator] - ); - - $bindValue = $condition->getBindArgumentValue(); - $expression = $value . $this->_connection->quoteInto($sql, $bindValue); - + //operator 'contains {}' is mapped to 'IN()' query that cannot work with substrings + // adding mapping to 'LIKE %%' + if ($condition->getInputType() === 'string' + && in_array($conditionOperator, array_keys($this->stringConditionOperatorMap), true) + ) { + $sql = str_replace( + ':field', + $this->_connection->getIfNullSql($this->_connection->quoteIdentifier($argument), $defaultValue), + $this->stringConditionOperatorMap[$conditionOperator] + ); + $bindValue = $condition->getBindArgumentValue(); + $expression = $value . $this->_connection->quoteInto($sql, "%$bindValue%"); + } else { + $sql = str_replace( + ':field', + $this->_connection->getIfNullSql($this->_connection->quoteIdentifier($argument), $defaultValue), + $this->_conditionOperatorMap[$conditionOperator] + ); + $bindValue = $condition->getBindArgumentValue(); + $expression = $value . $this->_connection->quoteInto($sql, $bindValue); + } // values for multiselect attributes can be saved in comma-separated format // below is a solution for matching such conditions with selected values if (is_array($bindValue) && \in_array($conditionOperator, ['()', '{}'], true)) { diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/MassHold.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/MassHold.php index 241255f68e39f..5c96fbbe9e247 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/MassHold.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/MassHold.php @@ -5,6 +5,7 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection; use Magento\Backend\App\Action\Context; use Magento\Ui\Component\MassAction\Filter; @@ -14,7 +15,7 @@ /** * Class MassHold */ -class MassHold extends \Magento\Sales\Controller\Adminhtml\Order\AbstractMassAction +class MassHold extends \Magento\Sales\Controller\Adminhtml\Order\AbstractMassAction implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/MassUnhold.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/MassUnhold.php index 5e8ea9c4ad071..e862710379a04 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/MassUnhold.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/MassUnhold.php @@ -5,13 +5,19 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection; use Magento\Backend\App\Action\Context; use Magento\Ui\Component\MassAction\Filter; use Magento\Sales\Model\ResourceModel\Order\CollectionFactory; use Magento\Sales\Api\OrderManagementInterface; -class MassUnhold extends AbstractMassAction +/** + * Class MassUnhold, change status for select orders + * + * @package Magento\Sales\Controller\Adminhtml\Order + */ +class MassUnhold extends AbstractMassAction implements HttpPostActionInterface { /** * Authorization level of a basic admin session @@ -24,6 +30,8 @@ class MassUnhold extends AbstractMassAction private $orderManagement; /** + * Class constructor + * * @param Context $context * @param Filter $filter * @param CollectionFactory $collectionFactory diff --git a/app/code/Magento/Sales/Model/Order.php b/app/code/Magento/Sales/Model/Order.php index 9f30342ed331f..9968e0c121026 100644 --- a/app/code/Magento/Sales/Model/Order.php +++ b/app/code/Magento/Sales/Model/Order.php @@ -546,7 +546,13 @@ public function canCancel() break; } } - if ($allInvoiced) { + + $allRefunded = true; + foreach ($this->getAllItems() as $orderItem) { + $allRefunded = $allRefunded && ((float)$orderItem->getQtyRefunded() == (float)$orderItem->getQtyInvoiced()); + } + + if ($allInvoiced && !$allRefunded) { return false; } @@ -973,7 +979,7 @@ public function getShippingAddress() } /** - * Set order state + * @inheritdoc * * @param string $state * @return $this @@ -1008,9 +1014,9 @@ public function addStatusToHistory($status, $comment = '', $isCustomerNotified = } /** - * Add a comment to order + * Add a comment to order. * - * Different or default status may be specified + * Different or default status may be specified. * * @param string $comment * @param bool|string $status @@ -1024,9 +1030,9 @@ public function addStatusHistoryComment($comment, $status = false) } /** - * Add a comment to order status history + * Add a comment to order status history. * - * Different or default status may be specified + * Different or default status may be specified. * * @param string $comment * @param bool|string $status @@ -1238,7 +1244,7 @@ public function getShippingMethod($asObject = false) /*********************** ADDRESSES ***************************/ /** - * Returns address collection instance + * Get addresses collection * * @return Collection */ @@ -1254,7 +1260,7 @@ public function getAddressesCollection() } /** - * Returns address by id + * Get address by id * * @param mixed $addressId * @return false @@ -1270,7 +1276,7 @@ public function getAddressById($addressId) } /** - * Add address to order + * Add address * * @param \Magento\Sales\Model\Order\Address $address * @return $this @@ -1286,7 +1292,7 @@ public function addAddress(\Magento\Sales\Model\Order\Address $address) } /** - * Returns items collection + * Get items collection * * @param array $filterByTypes * @param bool $nonChildrenOnly @@ -1360,7 +1366,7 @@ protected function _getItemsRandomCollection($limit, $nonChildrenOnly = false) } /** - * Returns all order items + * Get all items * * @return \Magento\Sales\Model\Order\Item[] */ @@ -1376,7 +1382,7 @@ public function getAllItems() } /** - * Returns all visible items + * Get all visible items * * @return array */ @@ -1409,7 +1415,7 @@ public function getItemById($itemId) } /** - * Returns Item By QuoteItem Id + * Get item by quote item id * * @param mixed $quoteItemId * @return \Magento\Framework\DataObject|null @@ -1425,7 +1431,7 @@ public function getItemByQuoteItemId($quoteItemId) } /** - * Add item to order + * Add item * * @param \Magento\Sales\Model\Order\Item $item * @return $this @@ -1442,7 +1448,7 @@ public function addItem(\Magento\Sales\Model\Order\Item $item) /*********************** PAYMENTS ***************************/ /** - * Returns payment collection + * Get payments collection * * @return PaymentCollection */ @@ -1458,7 +1464,7 @@ public function getPaymentsCollection() } /** - * Returns all payments + * Get all payments * * @return array */ @@ -1474,7 +1480,7 @@ public function getAllPayments() } /** - * Returns payment by id + * Get payment by id * * @param mixed $paymentId * @return Payment|false @@ -1557,7 +1563,7 @@ public function getVisibleStatusHistory() } /** - * Returns status history by id + * Get status history by id * * @param mixed $statusId * @return string|false @@ -1573,7 +1579,7 @@ public function getStatusHistoryById($statusId) } /** - * Set the order status history object and the order object to each other + * @inheritdoc * * Adds the object to the status history collection, which is automatically saved when the order is saved. * See the entity_id attribute backend model. @@ -1594,7 +1600,7 @@ public function addStatusHistory(\Magento\Sales\Model\Order\Status\History $hist } /** - * Returns real order id + * Get real order id * * @return string */ @@ -1694,7 +1700,7 @@ public function formatBasePricePrecision($price, $precision) } /** - * Is Currency Different + * Is currency different * * @return bool */ @@ -1728,7 +1734,7 @@ public function getBaseTotalDue() } /** - * Returns object data + * Get data * * @param string $key * @param null|string|int $index @@ -1765,7 +1771,7 @@ public function getInvoiceCollection() } /** - * Set order invoices collection + * @inheritdoc * * @param InvoiceCollection $invoices * @return $this @@ -1870,7 +1876,7 @@ public function getRelatedObjects() } /** - * Returns customer name + * Get customer name * * @return string */ @@ -1914,7 +1920,7 @@ public function getCreatedAtFormatted($format) } /** - * Returns email customer note + * Get email customer note * * @return string */ @@ -1927,7 +1933,7 @@ public function getEmailCustomerNote() } /** - * Returns store group name + * Get store group name * * @return string */ @@ -1941,9 +1947,7 @@ public function getStoreGroupName() } /** - * Resets all data in object - * - * So after another load it will be complete new object + * Reset all data in object so after another load it will be complete new object. * * @return $this */ @@ -1999,7 +2003,7 @@ public function isCanceled() } /** - * Returns increment id + * Return increment id * * @codeCoverageIgnore * @@ -2011,7 +2015,7 @@ public function getIncrementId() } /** - * Returns order items + * Get Items * * @return \Magento\Sales\Api\Data\OrderItemInterface[] */ @@ -2036,7 +2040,7 @@ public function setItems($items) } /** - * Returns order addresses + * Get addresses * * @return \Magento\Sales\Api\Data\OrderAddressInterface[] */ @@ -2052,7 +2056,7 @@ public function getAddresses() } /** - * Returns status history + * Get status History * * @return \Magento\Sales\Api\Data\OrderStatusHistoryInterface[]|null */ @@ -2091,7 +2095,7 @@ public function setExtensionAttributes(\Magento\Sales\Api\Data\OrderExtensionInt //@codeCoverageIgnoreStart /** - * Returns adjustment_negative + * Return adjustment_negative * * @return float|null */ @@ -2101,7 +2105,7 @@ public function getAdjustmentNegative() } /** - * Returns adjustment_positive + * Return adjustment_positive * * @return float|null */ @@ -2111,7 +2115,7 @@ public function getAdjustmentPositive() } /** - * Returns applied_rule_ids + * Return applied_rule_ids * * @return string|null */ @@ -2121,7 +2125,7 @@ public function getAppliedRuleIds() } /** - * Returns base_adjustment_negative + * Return base_adjustment_negative * * @return float|null */ @@ -2131,7 +2135,7 @@ public function getBaseAdjustmentNegative() } /** - * Returns base_adjustment_positive + * Return base_adjustment_positive * * @return float|null */ @@ -2141,7 +2145,7 @@ public function getBaseAdjustmentPositive() } /** - * Returns base_currency_code + * Return base_currency_code * * @return string|null */ @@ -2151,7 +2155,7 @@ public function getBaseCurrencyCode() } /** - * Returns base_discount_amount + * Return base_discount_amount * * @return float|null */ @@ -2161,7 +2165,7 @@ public function getBaseDiscountAmount() } /** - * Returns base_discount_canceled + * Return base_discount_canceled * * @return float|null */ @@ -2171,7 +2175,7 @@ public function getBaseDiscountCanceled() } /** - * Returns base_discount_invoiced + * Return base_discount_invoiced * * @return float|null */ @@ -2181,7 +2185,7 @@ public function getBaseDiscountInvoiced() } /** - * Returns base_discount_refunded + * Return base_discount_refunded * * @return float|null */ @@ -2191,7 +2195,7 @@ public function getBaseDiscountRefunded() } /** - * Returns base_grand_total + * Return base_grand_total * * @return float */ @@ -2201,7 +2205,7 @@ public function getBaseGrandTotal() } /** - * Returns base_discount_tax_compensation_amount + * Return base_discount_tax_compensation_amount * * @return float|null */ @@ -2211,7 +2215,7 @@ public function getBaseDiscountTaxCompensationAmount() } /** - * Returns base_discount_tax_compensation_invoiced + * Return base_discount_tax_compensation_invoiced * * @return float|null */ @@ -2221,7 +2225,7 @@ public function getBaseDiscountTaxCompensationInvoiced() } /** - * Returns base_discount_tax_compensation_refunded + * Return base_discount_tax_compensation_refunded * * @return float|null */ @@ -2231,7 +2235,7 @@ public function getBaseDiscountTaxCompensationRefunded() } /** - * Returns base_shipping_amount + * Return base_shipping_amount * * @return float|null */ @@ -2241,7 +2245,7 @@ public function getBaseShippingAmount() } /** - * Returns base_shipping_canceled + * Return base_shipping_canceled * * @return float|null */ @@ -2251,7 +2255,7 @@ public function getBaseShippingCanceled() } /** - * Returns base_shipping_discount_amount + * Return base_shipping_discount_amount * * @return float|null */ @@ -2261,7 +2265,7 @@ public function getBaseShippingDiscountAmount() } /** - * Returns base_shipping_discount_tax_compensation_amnt + * Return base_shipping_discount_tax_compensation_amnt * * @return float|null */ @@ -2271,7 +2275,7 @@ public function getBaseShippingDiscountTaxCompensationAmnt() } /** - * Returns base_shipping_incl_tax + * Return base_shipping_incl_tax * * @return float|null */ @@ -2281,7 +2285,7 @@ public function getBaseShippingInclTax() } /** - * Returns base_shipping_invoiced + * Return base_shipping_invoiced * * @return float|null */ @@ -2291,7 +2295,7 @@ public function getBaseShippingInvoiced() } /** - * Returns base_shipping_refunded + * Return base_shipping_refunded * * @return float|null */ @@ -2301,7 +2305,7 @@ public function getBaseShippingRefunded() } /** - * Returns base_shipping_tax_amount + * Return base_shipping_tax_amount * * @return float|null */ @@ -2311,7 +2315,7 @@ public function getBaseShippingTaxAmount() } /** - * Returns base_shipping_tax_refunded + * Return base_shipping_tax_refunded * * @return float|null */ @@ -2321,7 +2325,7 @@ public function getBaseShippingTaxRefunded() } /** - * Returns base_subtotal + * Return base_subtotal * * @return float|null */ @@ -2331,7 +2335,7 @@ public function getBaseSubtotal() } /** - * Returns base_subtotal_canceled + * Return base_subtotal_canceled * * @return float|null */ @@ -2341,7 +2345,7 @@ public function getBaseSubtotalCanceled() } /** - * Returns base_subtotal_incl_tax + * Return base_subtotal_incl_tax * * @return float|null */ @@ -2351,7 +2355,7 @@ public function getBaseSubtotalInclTax() } /** - * Returns base_subtotal_invoiced + * Return base_subtotal_invoiced * * @return float|null */ @@ -2361,7 +2365,7 @@ public function getBaseSubtotalInvoiced() } /** - * Returns base_subtotal_refunded + * Return base_subtotal_refunded * * @return float|null */ @@ -2371,7 +2375,7 @@ public function getBaseSubtotalRefunded() } /** - * Returns base_tax_amount + * Return base_tax_amount * * @return float|null */ @@ -2381,7 +2385,7 @@ public function getBaseTaxAmount() } /** - * Returns base_tax_canceled + * Return base_tax_canceled * * @return float|null */ @@ -2391,7 +2395,7 @@ public function getBaseTaxCanceled() } /** - * Returns base_tax_invoiced + * Return base_tax_invoiced * * @return float|null */ @@ -2401,7 +2405,7 @@ public function getBaseTaxInvoiced() } /** - * Returns base_tax_refunded + * Return base_tax_refunded * * @return float|null */ @@ -2411,7 +2415,7 @@ public function getBaseTaxRefunded() } /** - * Returns base_total_canceled + * Return base_total_canceled * * @return float|null */ @@ -2421,7 +2425,7 @@ public function getBaseTotalCanceled() } /** - * Returns base_total_invoiced + * Return base_total_invoiced * * @return float|null */ @@ -2431,7 +2435,7 @@ public function getBaseTotalInvoiced() } /** - * Returns base_total_invoiced_cost + * Return base_total_invoiced_cost * * @return float|null */ @@ -2441,7 +2445,7 @@ public function getBaseTotalInvoicedCost() } /** - * Returns base_total_offline_refunded + * Return base_total_offline_refunded * * @return float|null */ @@ -2451,7 +2455,7 @@ public function getBaseTotalOfflineRefunded() } /** - * Returns base_total_online_refunded + * Return base_total_online_refunded * * @return float|null */ @@ -2461,7 +2465,7 @@ public function getBaseTotalOnlineRefunded() } /** - * Returns base_total_paid + * Return base_total_paid * * @return float|null */ @@ -2471,7 +2475,7 @@ public function getBaseTotalPaid() } /** - * Returns base_total_qty_ordered + * Return base_total_qty_ordered * * @return float|null */ @@ -2481,7 +2485,7 @@ public function getBaseTotalQtyOrdered() } /** - * Returns base_total_refunded + * Return base_total_refunded * * @return float|null */ @@ -2491,7 +2495,7 @@ public function getBaseTotalRefunded() } /** - * Returns base_to_global_rate + * Return base_to_global_rate * * @return float|null */ @@ -2501,7 +2505,7 @@ public function getBaseToGlobalRate() } /** - * Returns base_to_order_rate + * Return base_to_order_rate * * @return float|null */ @@ -2511,7 +2515,7 @@ public function getBaseToOrderRate() } /** - * Returns billing_address_id + * Return billing_address_id * * @return int|null */ @@ -2521,7 +2525,7 @@ public function getBillingAddressId() } /** - * Returns can_ship_partially + * Return can_ship_partially * * @return int|null */ @@ -2531,7 +2535,7 @@ public function getCanShipPartially() } /** - * Returns can_ship_partially_item + * Return can_ship_partially_item * * @return int|null */ @@ -2541,7 +2545,7 @@ public function getCanShipPartiallyItem() } /** - * Returns coupon_code + * Return coupon_code * * @return string|null */ @@ -2551,7 +2555,7 @@ public function getCouponCode() } /** - * Returns created_at + * Return created_at * * @return string|null */ @@ -2569,7 +2573,7 @@ public function setCreatedAt($createdAt) } /** - * Returns customer_dob + * Return customer_dob * * @return string|null */ @@ -2579,7 +2583,7 @@ public function getCustomerDob() } /** - * Returns customer_email + * Return customer_email * * @return string */ @@ -2589,7 +2593,7 @@ public function getCustomerEmail() } /** - * Returns customer_firstname + * Return customer_firstname * * @return string|null */ @@ -2599,7 +2603,7 @@ public function getCustomerFirstname() } /** - * Returns customer_gender + * Return customer_gender * * @return int|null */ @@ -2609,7 +2613,7 @@ public function getCustomerGender() } /** - * Returns customer_group_id + * Return customer_group_id * * @return int|null */ @@ -2619,7 +2623,7 @@ public function getCustomerGroupId() } /** - * Returns customer_id + * Return customer_id * * @return int|null */ @@ -2629,7 +2633,7 @@ public function getCustomerId() } /** - * Returns customer_is_guest + * Return customer_is_guest * * @return int|null */ @@ -2639,7 +2643,7 @@ public function getCustomerIsGuest() } /** - * Returns customer_lastname + * Return customer_lastname * * @return string|null */ @@ -2649,7 +2653,7 @@ public function getCustomerLastname() } /** - * Returns customer_middlename + * Return customer_middlename * * @return string|null */ @@ -2659,7 +2663,7 @@ public function getCustomerMiddlename() } /** - * Returns customer_note + * Return customer_note * * @return string|null */ @@ -2669,7 +2673,7 @@ public function getCustomerNote() } /** - * Returns customer_note_notify + * Return customer_note_notify * * @return int|null */ @@ -2679,7 +2683,7 @@ public function getCustomerNoteNotify() } /** - * Returns customer_prefix + * Return customer_prefix * * @return string|null */ @@ -2689,7 +2693,7 @@ public function getCustomerPrefix() } /** - * Returns customer_suffix + * Return customer_suffix * * @return string|null */ @@ -2699,7 +2703,7 @@ public function getCustomerSuffix() } /** - * Returns customer_taxvat + * Return customer_taxvat * * @return string|null */ @@ -2709,7 +2713,7 @@ public function getCustomerTaxvat() } /** - * Returns discount_amount + * Return discount_amount * * @return float|null */ @@ -2719,7 +2723,7 @@ public function getDiscountAmount() } /** - * Returns discount_canceled + * Return discount_canceled * * @return float|null */ @@ -2729,7 +2733,7 @@ public function getDiscountCanceled() } /** - * Returns discount_description + * Return discount_description * * @return string|null */ @@ -2739,7 +2743,7 @@ public function getDiscountDescription() } /** - * Returns discount_invoiced + * Return discount_invoiced * * @return float|null */ @@ -2749,7 +2753,7 @@ public function getDiscountInvoiced() } /** - * Returns discount_refunded + * Return discount_refunded * * @return float|null */ @@ -2759,7 +2763,7 @@ public function getDiscountRefunded() } /** - * Returns edit_increment + * Return edit_increment * * @return int|null */ @@ -2769,7 +2773,7 @@ public function getEditIncrement() } /** - * Returns email_sent + * Return email_sent * * @return int|null */ @@ -2779,7 +2783,7 @@ public function getEmailSent() } /** - * Returns ext_customer_id + * Return ext_customer_id * * @return string|null */ @@ -2789,7 +2793,7 @@ public function getExtCustomerId() } /** - * Returns ext_order_id + * Return ext_order_id * * @return string|null */ @@ -2799,7 +2803,7 @@ public function getExtOrderId() } /** - * Returns forced_shipment_with_invoice + * Return forced_shipment_with_invoice * * @return int|null */ @@ -2809,7 +2813,7 @@ public function getForcedShipmentWithInvoice() } /** - * Returns global_currency_code + * Return global_currency_code * * @return string|null */ @@ -2819,7 +2823,7 @@ public function getGlobalCurrencyCode() } /** - * Returns grand_total + * Return grand_total * * @return float */ @@ -2829,7 +2833,7 @@ public function getGrandTotal() } /** - * Returns discount_tax_compensation_amount + * Return discount_tax_compensation_amount * * @return float|null */ @@ -2839,7 +2843,7 @@ public function getDiscountTaxCompensationAmount() } /** - * Returns discount_tax_compensation_invoiced + * Return discount_tax_compensation_invoiced * * @return float|null */ @@ -2849,7 +2853,7 @@ public function getDiscountTaxCompensationInvoiced() } /** - * Returns discount_tax_compensation_refunded + * Return discount_tax_compensation_refunded * * @return float|null */ @@ -2859,7 +2863,7 @@ public function getDiscountTaxCompensationRefunded() } /** - * Returns hold_before_state + * Return hold_before_state * * @return string|null */ @@ -2869,7 +2873,7 @@ public function getHoldBeforeState() } /** - * Returns hold_before_status + * Return hold_before_status * * @return string|null */ @@ -2879,7 +2883,7 @@ public function getHoldBeforeStatus() } /** - * Returns is_virtual + * Return is_virtual * * @return int|null */ @@ -2889,7 +2893,7 @@ public function getIsVirtual() } /** - * Returns order_currency_code + * Return order_currency_code * * @return string|null */ @@ -2899,7 +2903,7 @@ public function getOrderCurrencyCode() } /** - * Returns original_increment_id + * Return original_increment_id * * @return string|null */ @@ -2909,7 +2913,7 @@ public function getOriginalIncrementId() } /** - * Returns payment_authorization_amount + * Return payment_authorization_amount * * @return float|null */ @@ -2919,7 +2923,7 @@ public function getPaymentAuthorizationAmount() } /** - * Returns payment_auth_expiration + * Return payment_auth_expiration * * @return int|null */ @@ -2929,7 +2933,7 @@ public function getPaymentAuthExpiration() } /** - * Returns protect_code + * Return protect_code * * @return string|null */ @@ -2939,7 +2943,7 @@ public function getProtectCode() } /** - * Returns quote_address_id + * Return quote_address_id * * @return int|null */ @@ -2949,7 +2953,7 @@ public function getQuoteAddressId() } /** - * Returns quote_id + * Return quote_id * * @return int|null */ @@ -2959,7 +2963,7 @@ public function getQuoteId() } /** - * Returns relation_child_id + * Return relation_child_id * * @return string|null */ @@ -2969,7 +2973,7 @@ public function getRelationChildId() } /** - * Returns relation_child_real_id + * Return relation_child_real_id * * @return string|null */ @@ -2979,7 +2983,7 @@ public function getRelationChildRealId() } /** - * Returns relation_parent_id + * Return relation_parent_id * * @return string|null */ @@ -2989,7 +2993,7 @@ public function getRelationParentId() } /** - * Returns relation_parent_real_id + * Return relation_parent_real_id * * @return string|null */ @@ -2999,7 +3003,7 @@ public function getRelationParentRealId() } /** - * Returns remote_ip + * Return remote_ip * * @return string|null */ @@ -3009,7 +3013,7 @@ public function getRemoteIp() } /** - * Returns shipping_amount + * Return shipping_amount * * @return float|null */ @@ -3019,7 +3023,7 @@ public function getShippingAmount() } /** - * Returns shipping_canceled + * Return shipping_canceled * * @return float|null */ @@ -3029,7 +3033,7 @@ public function getShippingCanceled() } /** - * Returns shipping_description + * Return shipping_description * * @return string|null */ @@ -3039,7 +3043,7 @@ public function getShippingDescription() } /** - * Returns shipping_discount_amount + * Return shipping_discount_amount * * @return float|null */ @@ -3049,7 +3053,7 @@ public function getShippingDiscountAmount() } /** - * Returns shipping_discount_tax_compensation_amount + * Return shipping_discount_tax_compensation_amount * * @return float|null */ @@ -3059,7 +3063,7 @@ public function getShippingDiscountTaxCompensationAmount() } /** - * Returns shipping_incl_tax + * Return shipping_incl_tax * * @return float|null */ @@ -3069,7 +3073,7 @@ public function getShippingInclTax() } /** - * Returns shipping_invoiced + * Return shipping_invoiced * * @return float|null */ @@ -3079,7 +3083,7 @@ public function getShippingInvoiced() } /** - * Returns shipping_refunded + * Return shipping_refunded * * @return float|null */ @@ -3089,7 +3093,7 @@ public function getShippingRefunded() } /** - * Returns shipping_tax_amount + * Return shipping_tax_amount * * @return float|null */ @@ -3099,7 +3103,7 @@ public function getShippingTaxAmount() } /** - * Returns shipping_tax_refunded + * Return shipping_tax_refunded * * @return float|null */ @@ -3109,7 +3113,7 @@ public function getShippingTaxRefunded() } /** - * Returns state + * Return state * * @return string|null */ @@ -3119,7 +3123,7 @@ public function getState() } /** - * Returns status + * Return status * * @return string|null */ @@ -3129,7 +3133,7 @@ public function getStatus() } /** - * Returns store_currency_code + * Return store_currency_code * * @return string|null */ @@ -3139,7 +3143,7 @@ public function getStoreCurrencyCode() } /** - * Returns store_id + * Return store_id * * @return int|null */ @@ -3149,7 +3153,7 @@ public function getStoreId() } /** - * Returns store_name + * Return store_name * * @return string|null */ @@ -3159,7 +3163,7 @@ public function getStoreName() } /** - * Returns store_to_base_rate + * Return store_to_base_rate * * @return float|null */ @@ -3169,7 +3173,7 @@ public function getStoreToBaseRate() } /** - * Returns store_to_order_rate + * Return store_to_order_rate * * @return float|null */ @@ -3179,7 +3183,7 @@ public function getStoreToOrderRate() } /** - * Returns subtotal + * Return subtotal * * @return float|null */ @@ -3189,7 +3193,7 @@ public function getSubtotal() } /** - * Returns subtotal_canceled + * Return subtotal_canceled * * @return float|null */ @@ -3199,7 +3203,7 @@ public function getSubtotalCanceled() } /** - * Returns subtotal_incl_tax + * Return subtotal_incl_tax * * @return float|null */ @@ -3209,7 +3213,7 @@ public function getSubtotalInclTax() } /** - * Returns subtotal_invoiced + * Return subtotal_invoiced * * @return float|null */ @@ -3219,7 +3223,7 @@ public function getSubtotalInvoiced() } /** - * Returns subtotal_refunded + * Return subtotal_refunded * * @return float|null */ @@ -3229,7 +3233,7 @@ public function getSubtotalRefunded() } /** - * Returns tax_amount + * Return tax_amount * * @return float|null */ @@ -3239,7 +3243,7 @@ public function getTaxAmount() } /** - * Returns tax_canceled + * Return tax_canceled * * @return float|null */ @@ -3249,7 +3253,7 @@ public function getTaxCanceled() } /** - * Returns tax_invoiced + * Return tax_invoiced * * @return float|null */ @@ -3259,7 +3263,7 @@ public function getTaxInvoiced() } /** - * Returns tax_refunded + * Return tax_refunded * * @return float|null */ @@ -3269,7 +3273,7 @@ public function getTaxRefunded() } /** - * Returns total_canceled + * Return total_canceled * * @return float|null */ @@ -3279,7 +3283,7 @@ public function getTotalCanceled() } /** - * Returns total_invoiced + * Return total_invoiced * * @return float|null */ @@ -3289,7 +3293,7 @@ public function getTotalInvoiced() } /** - * Returns total_item_count + * Return total_item_count * * @return int|null */ @@ -3299,7 +3303,7 @@ public function getTotalItemCount() } /** - * Returns total_offline_refunded + * Return total_offline_refunded * * @return float|null */ @@ -3309,7 +3313,7 @@ public function getTotalOfflineRefunded() } /** - * Returns total_online_refunded + * Return total_online_refunded * * @return float|null */ @@ -3319,7 +3323,7 @@ public function getTotalOnlineRefunded() } /** - * Returns total_paid + * Return total_paid * * @return float|null */ @@ -3329,7 +3333,7 @@ public function getTotalPaid() } /** - * Returns total_qty_ordered + * Return total_qty_ordered * * @return float|null */ @@ -3339,7 +3343,7 @@ public function getTotalQtyOrdered() } /** - * Returns total_refunded + * Return total_refunded * * @return float|null */ @@ -3349,7 +3353,7 @@ public function getTotalRefunded() } /** - * Returns updated_at + * Return updated_at * * @return string|null */ @@ -3359,7 +3363,7 @@ public function getUpdatedAt() } /** - * Returns weight + * Return weight * * @return float|null */ @@ -3369,7 +3373,7 @@ public function getWeight() } /** - * Returns x_forwarded_for + * Return x_forwarded_for * * @return string|null */ diff --git a/app/code/Magento/Sales/Model/Order/Item.php b/app/code/Magento/Sales/Model/Order/Item.php index d2f5f5ce56692..cc8bb7aae780c 100644 --- a/app/code/Magento/Sales/Model/Order/Item.php +++ b/app/code/Magento/Sales/Model/Order/Item.php @@ -232,7 +232,7 @@ public function getQtyToShip() */ public function getSimpleQtyToShip() { - $qty = $this->getQtyOrdered() - $this->getQtyShipped() - $this->getQtyRefunded() - $this->getQtyCanceled(); + $qty = $this->getQtyOrdered() - $this->getQtyShipped() - $this->getQtyCanceled(); return max(round($qty, 8), 0); } @@ -483,6 +483,7 @@ public function getProductOptions() /** * Get product options array by code. + * * If code is null return all options * * @param string $code @@ -540,8 +541,7 @@ public function getChildrenItems() } /** - * Return checking of what calculation - * type was for this product + * Return checking of what calculation type was for this product * * @return bool */ @@ -581,8 +581,7 @@ public function getForceApplyDiscountToParentItem() } /** - * Return checking of what shipment - * type was for this product + * Return checking of what shipment type was for this product * * @return bool */ @@ -605,9 +604,9 @@ public function isShipSeparately() } /** - * This is Dummy item or not - * if $shipment is true then we checking this for shipping situation if not - * then we checking this for calculation + * This is Dummy item or not. + * + * If $shipment is true then we checking this for shipping situation if not, we checking this for calculation. * * @param bool $shipment * @return bool @@ -652,8 +651,9 @@ public function isDummy($shipment = false) } /** - * Returns formatted buy request - object, holding request received from - * product view page with keys and options for configured product + * Return formatted buy request. + * + * This object is holding request received from product view page with keys and options for configured product. * * @return \Magento\Framework\DataObject */ @@ -703,7 +703,7 @@ public function getStore() //@codeCoverageIgnoreStart /** - * Returns additional_data + * Return additional_data * * @return string|null */ @@ -713,7 +713,7 @@ public function getAdditionalData() } /** - * Returns amount_refunded + * Return amount_refunded * * @return float|null */ @@ -723,7 +723,7 @@ public function getAmountRefunded() } /** - * Returns applied_rule_ids + * Return applied_rule_ids * * @return string|null */ @@ -733,7 +733,7 @@ public function getAppliedRuleIds() } /** - * Returns base_amount_refunded + * Return base_amount_refunded * * @return float|null */ @@ -743,7 +743,7 @@ public function getBaseAmountRefunded() } /** - * Returns base_cost + * Return base_cost * * @return float|null */ @@ -753,7 +753,7 @@ public function getBaseCost() } /** - * Returns base_discount_amount + * Return base_discount_amount * * @return float|null */ @@ -763,7 +763,7 @@ public function getBaseDiscountAmount() } /** - * Returns base_discount_invoiced + * Return base_discount_invoiced * * @return float|null */ @@ -773,7 +773,7 @@ public function getBaseDiscountInvoiced() } /** - * Returns base_discount_refunded + * Return base_discount_refunded * * @return float|null */ @@ -783,7 +783,7 @@ public function getBaseDiscountRefunded() } /** - * Returns base_discount_tax_compensation_amount + * Return base_discount_tax_compensation_amount * * @return float|null */ @@ -793,7 +793,7 @@ public function getBaseDiscountTaxCompensationAmount() } /** - * Returns base_discount_tax_compensation_invoiced + * Return base_discount_tax_compensation_invoiced * * @return float|null */ @@ -803,7 +803,7 @@ public function getBaseDiscountTaxCompensationInvoiced() } /** - * Returns base_discount_tax_compensation_refunded + * Return base_discount_tax_compensation_refunded * * @return float|null */ @@ -813,7 +813,7 @@ public function getBaseDiscountTaxCompensationRefunded() } /** - * Returns base_original_price + * Return base_original_price * * @return float|null */ @@ -823,7 +823,7 @@ public function getBaseOriginalPrice() } /** - * Returns base_price + * Return base_price * * @return float|null */ @@ -833,7 +833,7 @@ public function getBasePrice() } /** - * Returns base_price_incl_tax + * Return base_price_incl_tax * * @return float|null */ @@ -843,7 +843,7 @@ public function getBasePriceInclTax() } /** - * Returns base_row_invoiced + * Return base_row_invoiced * * @return float|null */ @@ -853,7 +853,7 @@ public function getBaseRowInvoiced() } /** - * Returns base_row_total + * Return base_row_total * * @return float|null */ @@ -863,7 +863,7 @@ public function getBaseRowTotal() } /** - * Returns base_row_total_incl_tax + * Return base_row_total_incl_tax * * @return float|null */ @@ -873,7 +873,7 @@ public function getBaseRowTotalInclTax() } /** - * Returns base_tax_amount + * Return base_tax_amount * * @return float|null */ @@ -883,7 +883,7 @@ public function getBaseTaxAmount() } /** - * Returns base_tax_before_discount + * Return base_tax_before_discount * * @return float|null */ @@ -893,7 +893,7 @@ public function getBaseTaxBeforeDiscount() } /** - * Returns base_tax_invoiced + * Return base_tax_invoiced * * @return float|null */ @@ -903,7 +903,7 @@ public function getBaseTaxInvoiced() } /** - * Returns base_tax_refunded + * Return base_tax_refunded * * @return float|null */ @@ -913,7 +913,7 @@ public function getBaseTaxRefunded() } /** - * Returns base_weee_tax_applied_amount + * Return base_weee_tax_applied_amount * * @return float|null */ @@ -923,7 +923,7 @@ public function getBaseWeeeTaxAppliedAmount() } /** - * Returns base_weee_tax_applied_row_amnt + * Return base_weee_tax_applied_row_amnt * * @return float|null */ @@ -933,7 +933,7 @@ public function getBaseWeeeTaxAppliedRowAmnt() } /** - * Returns base_weee_tax_disposition + * Return base_weee_tax_disposition * * @return float|null */ @@ -943,7 +943,7 @@ public function getBaseWeeeTaxDisposition() } /** - * Returns base_weee_tax_row_disposition + * Return base_weee_tax_row_disposition * * @return float|null */ @@ -953,7 +953,7 @@ public function getBaseWeeeTaxRowDisposition() } /** - * Returns created_at + * Return created_at * * @return string|null */ @@ -963,7 +963,7 @@ public function getCreatedAt() } /** - * {@inheritdoc} + * @inheritdoc */ public function setCreatedAt($createdAt) { @@ -971,7 +971,7 @@ public function setCreatedAt($createdAt) } /** - * Returns description + * Return description * * @return string|null */ @@ -981,7 +981,7 @@ public function getDescription() } /** - * Returns discount_amount + * Return discount_amount * * @return float|null */ @@ -991,7 +991,7 @@ public function getDiscountAmount() } /** - * Returns discount_invoiced + * Return discount_invoiced * * @return float|null */ @@ -1001,7 +1001,7 @@ public function getDiscountInvoiced() } /** - * Returns discount_percent + * Return discount_percent * * @return float|null */ @@ -1011,7 +1011,7 @@ public function getDiscountPercent() } /** - * Returns discount_refunded + * Return discount_refunded * * @return float|null */ @@ -1021,7 +1021,7 @@ public function getDiscountRefunded() } /** - * Returns event_id + * Return event_id * * @return int|null */ @@ -1031,7 +1031,7 @@ public function getEventId() } /** - * Returns ext_order_item_id + * Return ext_order_item_id * * @return string|null */ @@ -1041,7 +1041,7 @@ public function getExtOrderItemId() } /** - * Returns free_shipping + * Return free_shipping * * @return int|null */ @@ -1051,7 +1051,7 @@ public function getFreeShipping() } /** - * Returns gw_base_price + * Return gw_base_price * * @return float|null */ @@ -1061,7 +1061,7 @@ public function getGwBasePrice() } /** - * Returns gw_base_price_invoiced + * Return gw_base_price_invoiced * * @return float|null */ @@ -1071,7 +1071,7 @@ public function getGwBasePriceInvoiced() } /** - * Returns gw_base_price_refunded + * Return gw_base_price_refunded * * @return float|null */ @@ -1081,7 +1081,7 @@ public function getGwBasePriceRefunded() } /** - * Returns gw_base_tax_amount + * Return gw_base_tax_amount * * @return float|null */ @@ -1091,7 +1091,7 @@ public function getGwBaseTaxAmount() } /** - * Returns gw_base_tax_amount_invoiced + * Return gw_base_tax_amount_invoiced * * @return float|null */ @@ -1101,7 +1101,7 @@ public function getGwBaseTaxAmountInvoiced() } /** - * Returns gw_base_tax_amount_refunded + * Return gw_base_tax_amount_refunded * * @return float|null */ @@ -1111,7 +1111,7 @@ public function getGwBaseTaxAmountRefunded() } /** - * Returns gw_id + * Return gw_id * * @return int|null */ @@ -1121,7 +1121,7 @@ public function getGwId() } /** - * Returns gw_price + * Return gw_price * * @return float|null */ @@ -1131,7 +1131,7 @@ public function getGwPrice() } /** - * Returns gw_price_invoiced + * Return gw_price_invoiced * * @return float|null */ @@ -1141,7 +1141,7 @@ public function getGwPriceInvoiced() } /** - * Returns gw_price_refunded + * Return gw_price_refunded * * @return float|null */ @@ -1151,7 +1151,7 @@ public function getGwPriceRefunded() } /** - * Returns gw_tax_amount + * Return gw_tax_amount * * @return float|null */ @@ -1161,7 +1161,7 @@ public function getGwTaxAmount() } /** - * Returns gw_tax_amount_invoiced + * Return gw_tax_amount_invoiced * * @return float|null */ @@ -1171,7 +1171,7 @@ public function getGwTaxAmountInvoiced() } /** - * Returns gw_tax_amount_refunded + * Return gw_tax_amount_refunded * * @return float|null */ @@ -1181,7 +1181,7 @@ public function getGwTaxAmountRefunded() } /** - * Returns discount_tax_compensation_amount + * Return discount_tax_compensation_amount * * @return float|null */ @@ -1191,7 +1191,7 @@ public function getDiscountTaxCompensationAmount() } /** - * Returns discount_tax_compensation_canceled + * Return discount_tax_compensation_canceled * * @return float|null */ @@ -1201,7 +1201,7 @@ public function getDiscountTaxCompensationCanceled() } /** - * Returns discount_tax_compensation_invoiced + * Return discount_tax_compensation_invoiced * * @return float|null */ @@ -1211,7 +1211,7 @@ public function getDiscountTaxCompensationInvoiced() } /** - * Returns discount_tax_compensation_refunded + * Return discount_tax_compensation_refunded * * @return float|null */ @@ -1221,7 +1221,7 @@ public function getDiscountTaxCompensationRefunded() } /** - * Returns is_qty_decimal + * Return is_qty_decimal * * @return int|null */ @@ -1231,7 +1231,7 @@ public function getIsQtyDecimal() } /** - * Returns is_virtual + * Return is_virtual * * @return int|null */ @@ -1241,7 +1241,7 @@ public function getIsVirtual() } /** - * Returns item_id + * Return item_id * * @return int|null */ @@ -1251,7 +1251,7 @@ public function getItemId() } /** - * Returns locked_do_invoice + * Return locked_do_invoice * * @return int|null */ @@ -1261,7 +1261,7 @@ public function getLockedDoInvoice() } /** - * Returns locked_do_ship + * Return locked_do_ship * * @return int|null */ @@ -1271,7 +1271,7 @@ public function getLockedDoShip() } /** - * Returns name + * Return name * * @return string|null */ @@ -1281,7 +1281,7 @@ public function getName() } /** - * Returns no_discount + * Return no_discount * * @return int|null */ @@ -1291,7 +1291,7 @@ public function getNoDiscount() } /** - * Returns order_id + * Return order_id * * @return int|null */ @@ -1301,7 +1301,7 @@ public function getOrderId() } /** - * Returns parent_item_id + * Return parent_item_id * * @return int|null */ @@ -1311,7 +1311,7 @@ public function getParentItemId() } /** - * Returns price + * Return price * * @return float|null */ @@ -1321,7 +1321,7 @@ public function getPrice() } /** - * Returns price_incl_tax + * Return price_incl_tax * * @return float|null */ @@ -1331,7 +1331,7 @@ public function getPriceInclTax() } /** - * Returns product_id + * Return product_id * * @return int|null */ @@ -1341,7 +1341,7 @@ public function getProductId() } /** - * Returns product_type + * Return product_type * * @return string|null */ @@ -1351,7 +1351,7 @@ public function getProductType() } /** - * Returns qty_backordered + * Return qty_backordered * * @return float|null */ @@ -1361,7 +1361,7 @@ public function getQtyBackordered() } /** - * Returns qty_canceled + * Return qty_canceled * * @return float|null */ @@ -1371,7 +1371,7 @@ public function getQtyCanceled() } /** - * Returns qty_invoiced + * Return qty_invoiced * * @return float|null */ @@ -1381,7 +1381,7 @@ public function getQtyInvoiced() } /** - * Returns qty_ordered + * Return qty_ordered * * @return float|null */ @@ -1391,7 +1391,7 @@ public function getQtyOrdered() } /** - * Returns qty_refunded + * Return qty_refunded * * @return float|null */ @@ -1401,7 +1401,7 @@ public function getQtyRefunded() } /** - * Returns qty_returned + * Return qty_returned * * @return float|null */ @@ -1411,7 +1411,7 @@ public function getQtyReturned() } /** - * Returns qty_shipped + * Return qty_shipped * * @return float|null */ @@ -1421,7 +1421,7 @@ public function getQtyShipped() } /** - * Returns quote_item_id + * Return quote_item_id * * @return int|null */ @@ -1431,7 +1431,7 @@ public function getQuoteItemId() } /** - * Returns row_invoiced + * Return row_invoiced * * @return float|null */ @@ -1441,7 +1441,7 @@ public function getRowInvoiced() } /** - * Returns row_total + * Return row_total * * @return float|null */ @@ -1451,7 +1451,7 @@ public function getRowTotal() } /** - * Returns row_total_incl_tax + * Return row_total_incl_tax * * @return float|null */ @@ -1461,7 +1461,7 @@ public function getRowTotalInclTax() } /** - * Returns row_weight + * Return row_weight * * @return float|null */ @@ -1471,7 +1471,7 @@ public function getRowWeight() } /** - * Returns sku + * Return sku * * @return string */ @@ -1481,7 +1481,7 @@ public function getSku() } /** - * Returns store_id + * Return store_id * * @return int|null */ @@ -1491,7 +1491,7 @@ public function getStoreId() } /** - * Returns tax_amount + * Return tax_amount * * @return float|null */ @@ -1501,7 +1501,7 @@ public function getTaxAmount() } /** - * Returns tax_before_discount + * Return tax_before_discount * * @return float|null */ @@ -1511,7 +1511,7 @@ public function getTaxBeforeDiscount() } /** - * Returns tax_canceled + * Return tax_canceled * * @return float|null */ @@ -1521,7 +1521,7 @@ public function getTaxCanceled() } /** - * Returns tax_invoiced + * Return tax_invoiced * * @return float|null */ @@ -1531,7 +1531,7 @@ public function getTaxInvoiced() } /** - * Returns tax_percent + * Return tax_percent * * @return float|null */ @@ -1541,7 +1541,7 @@ public function getTaxPercent() } /** - * Returns tax_refunded + * Return tax_refunded * * @return float|null */ @@ -1551,7 +1551,7 @@ public function getTaxRefunded() } /** - * Returns updated_at + * Return updated_at * * @return string|null */ @@ -1561,7 +1561,7 @@ public function getUpdatedAt() } /** - * Returns weee_tax_applied + * Return weee_tax_applied * * @return string|null */ @@ -1571,7 +1571,7 @@ public function getWeeeTaxApplied() } /** - * Returns weee_tax_applied_amount + * Return weee_tax_applied_amount * * @return float|null */ @@ -1581,7 +1581,7 @@ public function getWeeeTaxAppliedAmount() } /** - * Returns weee_tax_applied_row_amount + * Return weee_tax_applied_row_amount * * @return float|null */ @@ -1591,7 +1591,7 @@ public function getWeeeTaxAppliedRowAmount() } /** - * Returns weee_tax_disposition + * Return weee_tax_disposition * * @return float|null */ @@ -1601,7 +1601,7 @@ public function getWeeeTaxDisposition() } /** - * Returns weee_tax_row_disposition + * Return weee_tax_row_disposition * * @return float|null */ @@ -1611,7 +1611,7 @@ public function getWeeeTaxRowDisposition() } /** - * Returns weight + * Return weight * * @return float|null */ @@ -1621,7 +1621,7 @@ public function getWeight() } /** - * {@inheritdoc} + * @inheritdoc */ public function setUpdatedAt($timestamp) { @@ -1629,7 +1629,7 @@ public function setUpdatedAt($timestamp) } /** - * {@inheritdoc} + * @inheritdoc */ public function setItemId($id) { @@ -1637,7 +1637,7 @@ public function setItemId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setOrderId($id) { @@ -1645,7 +1645,7 @@ public function setOrderId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setParentItemId($id) { @@ -1653,7 +1653,7 @@ public function setParentItemId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setQuoteItemId($id) { @@ -1661,7 +1661,7 @@ public function setQuoteItemId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setStoreId($id) { @@ -1669,7 +1669,7 @@ public function setStoreId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setProductId($id) { @@ -1677,7 +1677,7 @@ public function setProductId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setProductType($productType) { @@ -1685,7 +1685,7 @@ public function setProductType($productType) } /** - * {@inheritdoc} + * @inheritdoc */ public function setWeight($weight) { @@ -1693,7 +1693,7 @@ public function setWeight($weight) } /** - * {@inheritdoc} + * @inheritdoc */ public function setIsVirtual($isVirtual) { @@ -1701,7 +1701,7 @@ public function setIsVirtual($isVirtual) } /** - * {@inheritdoc} + * @inheritdoc */ public function setSku($sku) { @@ -1709,7 +1709,7 @@ public function setSku($sku) } /** - * {@inheritdoc} + * @inheritdoc */ public function setName($name) { @@ -1717,7 +1717,7 @@ public function setName($name) } /** - * {@inheritdoc} + * @inheritdoc */ public function setDescription($description) { @@ -1725,7 +1725,7 @@ public function setDescription($description) } /** - * {@inheritdoc} + * @inheritdoc */ public function setAppliedRuleIds($appliedRuleIds) { @@ -1733,7 +1733,7 @@ public function setAppliedRuleIds($appliedRuleIds) } /** - * {@inheritdoc} + * @inheritdoc */ public function setAdditionalData($additionalData) { @@ -1741,7 +1741,7 @@ public function setAdditionalData($additionalData) } /** - * {@inheritdoc} + * @inheritdoc */ public function setIsQtyDecimal($isQtyDecimal) { @@ -1749,7 +1749,7 @@ public function setIsQtyDecimal($isQtyDecimal) } /** - * {@inheritdoc} + * @inheritdoc */ public function setNoDiscount($noDiscount) { @@ -1757,7 +1757,7 @@ public function setNoDiscount($noDiscount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setQtyBackordered($qtyBackordered) { @@ -1765,7 +1765,7 @@ public function setQtyBackordered($qtyBackordered) } /** - * {@inheritdoc} + * @inheritdoc */ public function setQtyCanceled($qtyCanceled) { @@ -1773,7 +1773,7 @@ public function setQtyCanceled($qtyCanceled) } /** - * {@inheritdoc} + * @inheritdoc */ public function setQtyInvoiced($qtyInvoiced) { @@ -1781,7 +1781,7 @@ public function setQtyInvoiced($qtyInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setQtyOrdered($qtyOrdered) { @@ -1789,7 +1789,7 @@ public function setQtyOrdered($qtyOrdered) } /** - * {@inheritdoc} + * @inheritdoc */ public function setQtyRefunded($qtyRefunded) { @@ -1797,7 +1797,7 @@ public function setQtyRefunded($qtyRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setQtyShipped($qtyShipped) { @@ -1805,7 +1805,7 @@ public function setQtyShipped($qtyShipped) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseCost($baseCost) { @@ -1813,7 +1813,7 @@ public function setBaseCost($baseCost) } /** - * {@inheritdoc} + * @inheritdoc */ public function setPrice($price) { @@ -1821,7 +1821,7 @@ public function setPrice($price) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBasePrice($price) { @@ -1829,7 +1829,7 @@ public function setBasePrice($price) } /** - * {@inheritdoc} + * @inheritdoc */ public function setOriginalPrice($price) { @@ -1837,7 +1837,7 @@ public function setOriginalPrice($price) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseOriginalPrice($price) { @@ -1845,7 +1845,7 @@ public function setBaseOriginalPrice($price) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTaxPercent($taxPercent) { @@ -1853,7 +1853,7 @@ public function setTaxPercent($taxPercent) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTaxAmount($amount) { @@ -1861,7 +1861,7 @@ public function setTaxAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseTaxAmount($amount) { @@ -1869,7 +1869,7 @@ public function setBaseTaxAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTaxInvoiced($taxInvoiced) { @@ -1877,7 +1877,7 @@ public function setTaxInvoiced($taxInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseTaxInvoiced($baseTaxInvoiced) { @@ -1885,7 +1885,7 @@ public function setBaseTaxInvoiced($baseTaxInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setDiscountPercent($discountPercent) { @@ -1893,7 +1893,7 @@ public function setDiscountPercent($discountPercent) } /** - * {@inheritdoc} + * @inheritdoc */ public function setDiscountAmount($amount) { @@ -1901,7 +1901,7 @@ public function setDiscountAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseDiscountAmount($amount) { @@ -1909,7 +1909,7 @@ public function setBaseDiscountAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setDiscountInvoiced($discountInvoiced) { @@ -1917,7 +1917,7 @@ public function setDiscountInvoiced($discountInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseDiscountInvoiced($baseDiscountInvoiced) { @@ -1925,7 +1925,7 @@ public function setBaseDiscountInvoiced($baseDiscountInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setAmountRefunded($amountRefunded) { @@ -1933,7 +1933,7 @@ public function setAmountRefunded($amountRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseAmountRefunded($baseAmountRefunded) { @@ -1941,7 +1941,7 @@ public function setBaseAmountRefunded($baseAmountRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setRowTotal($amount) { @@ -1949,7 +1949,7 @@ public function setRowTotal($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseRowTotal($amount) { @@ -1957,7 +1957,7 @@ public function setBaseRowTotal($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setRowInvoiced($rowInvoiced) { @@ -1965,7 +1965,7 @@ public function setRowInvoiced($rowInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseRowInvoiced($baseRowInvoiced) { @@ -1973,7 +1973,7 @@ public function setBaseRowInvoiced($baseRowInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setRowWeight($rowWeight) { @@ -1981,7 +1981,7 @@ public function setRowWeight($rowWeight) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseTaxBeforeDiscount($baseTaxBeforeDiscount) { @@ -1989,7 +1989,7 @@ public function setBaseTaxBeforeDiscount($baseTaxBeforeDiscount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTaxBeforeDiscount($taxBeforeDiscount) { @@ -1997,7 +1997,7 @@ public function setTaxBeforeDiscount($taxBeforeDiscount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setExtOrderItemId($id) { @@ -2005,7 +2005,7 @@ public function setExtOrderItemId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setLockedDoInvoice($flag) { @@ -2013,7 +2013,7 @@ public function setLockedDoInvoice($flag) } /** - * {@inheritdoc} + * @inheritdoc */ public function setLockedDoShip($flag) { @@ -2021,7 +2021,7 @@ public function setLockedDoShip($flag) } /** - * {@inheritdoc} + * @inheritdoc */ public function setPriceInclTax($amount) { @@ -2029,7 +2029,7 @@ public function setPriceInclTax($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBasePriceInclTax($amount) { @@ -2037,7 +2037,7 @@ public function setBasePriceInclTax($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setRowTotalInclTax($amount) { @@ -2045,7 +2045,7 @@ public function setRowTotalInclTax($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseRowTotalInclTax($amount) { @@ -2053,7 +2053,7 @@ public function setBaseRowTotalInclTax($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setDiscountTaxCompensationAmount($amount) { @@ -2061,7 +2061,7 @@ public function setDiscountTaxCompensationAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseDiscountTaxCompensationAmount($amount) { @@ -2069,7 +2069,7 @@ public function setBaseDiscountTaxCompensationAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setDiscountTaxCompensationInvoiced($discountTaxCompensationInvoiced) { @@ -2077,7 +2077,7 @@ public function setDiscountTaxCompensationInvoiced($discountTaxCompensationInvoi } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseDiscountTaxCompensationInvoiced($baseDiscountTaxCompensationInvoiced) { @@ -2088,7 +2088,7 @@ public function setBaseDiscountTaxCompensationInvoiced($baseDiscountTaxCompensat } /** - * {@inheritdoc} + * @inheritdoc */ public function setDiscountTaxCompensationRefunded($discountTaxCompensationRefunded) { @@ -2096,7 +2096,7 @@ public function setDiscountTaxCompensationRefunded($discountTaxCompensationRefun } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseDiscountTaxCompensationRefunded($baseDiscountTaxCompensationRefunded) { @@ -2107,7 +2107,7 @@ public function setBaseDiscountTaxCompensationRefunded($baseDiscountTaxCompensat } /** - * {@inheritdoc} + * @inheritdoc */ public function setTaxCanceled($taxCanceled) { @@ -2115,7 +2115,7 @@ public function setTaxCanceled($taxCanceled) } /** - * {@inheritdoc} + * @inheritdoc */ public function setDiscountTaxCompensationCanceled($discountTaxCompensationCanceled) { @@ -2123,7 +2123,7 @@ public function setDiscountTaxCompensationCanceled($discountTaxCompensationCance } /** - * {@inheritdoc} + * @inheritdoc */ public function setTaxRefunded($taxRefunded) { @@ -2131,7 +2131,7 @@ public function setTaxRefunded($taxRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseTaxRefunded($baseTaxRefunded) { @@ -2139,7 +2139,7 @@ public function setBaseTaxRefunded($baseTaxRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setDiscountRefunded($discountRefunded) { @@ -2147,7 +2147,7 @@ public function setDiscountRefunded($discountRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseDiscountRefunded($baseDiscountRefunded) { @@ -2155,7 +2155,7 @@ public function setBaseDiscountRefunded($baseDiscountRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setGwId($id) { @@ -2163,7 +2163,7 @@ public function setGwId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setGwBasePrice($price) { @@ -2171,7 +2171,7 @@ public function setGwBasePrice($price) } /** - * {@inheritdoc} + * @inheritdoc */ public function setGwPrice($price) { @@ -2179,7 +2179,7 @@ public function setGwPrice($price) } /** - * {@inheritdoc} + * @inheritdoc */ public function setGwBaseTaxAmount($amount) { @@ -2187,7 +2187,7 @@ public function setGwBaseTaxAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setGwTaxAmount($amount) { @@ -2195,7 +2195,7 @@ public function setGwTaxAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setGwBasePriceInvoiced($gwBasePriceInvoiced) { @@ -2203,7 +2203,7 @@ public function setGwBasePriceInvoiced($gwBasePriceInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setGwPriceInvoiced($gwPriceInvoiced) { @@ -2211,7 +2211,7 @@ public function setGwPriceInvoiced($gwPriceInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setGwBaseTaxAmountInvoiced($gwBaseTaxAmountInvoiced) { @@ -2219,7 +2219,7 @@ public function setGwBaseTaxAmountInvoiced($gwBaseTaxAmountInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setGwTaxAmountInvoiced($gwTaxAmountInvoiced) { @@ -2227,7 +2227,7 @@ public function setGwTaxAmountInvoiced($gwTaxAmountInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setGwBasePriceRefunded($gwBasePriceRefunded) { @@ -2235,7 +2235,7 @@ public function setGwBasePriceRefunded($gwBasePriceRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setGwPriceRefunded($gwPriceRefunded) { @@ -2243,7 +2243,7 @@ public function setGwPriceRefunded($gwPriceRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setGwBaseTaxAmountRefunded($gwBaseTaxAmountRefunded) { @@ -2251,7 +2251,7 @@ public function setGwBaseTaxAmountRefunded($gwBaseTaxAmountRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setGwTaxAmountRefunded($gwTaxAmountRefunded) { @@ -2259,7 +2259,7 @@ public function setGwTaxAmountRefunded($gwTaxAmountRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setFreeShipping($freeShipping) { @@ -2267,7 +2267,7 @@ public function setFreeShipping($freeShipping) } /** - * {@inheritdoc} + * @inheritdoc */ public function setQtyReturned($qtyReturned) { @@ -2275,7 +2275,7 @@ public function setQtyReturned($qtyReturned) } /** - * {@inheritdoc} + * @inheritdoc */ public function setEventId($id) { @@ -2283,7 +2283,7 @@ public function setEventId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseWeeeTaxAppliedAmount($amount) { @@ -2291,7 +2291,7 @@ public function setBaseWeeeTaxAppliedAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseWeeeTaxAppliedRowAmnt($amnt) { @@ -2299,7 +2299,7 @@ public function setBaseWeeeTaxAppliedRowAmnt($amnt) } /** - * {@inheritdoc} + * @inheritdoc */ public function setWeeeTaxAppliedAmount($amount) { @@ -2307,7 +2307,7 @@ public function setWeeeTaxAppliedAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setWeeeTaxAppliedRowAmount($amount) { @@ -2315,7 +2315,7 @@ public function setWeeeTaxAppliedRowAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setWeeeTaxApplied($weeeTaxApplied) { @@ -2323,7 +2323,7 @@ public function setWeeeTaxApplied($weeeTaxApplied) } /** - * {@inheritdoc} + * @inheritdoc */ public function setWeeeTaxDisposition($weeeTaxDisposition) { @@ -2331,7 +2331,7 @@ public function setWeeeTaxDisposition($weeeTaxDisposition) } /** - * {@inheritdoc} + * @inheritdoc */ public function setWeeeTaxRowDisposition($weeeTaxRowDisposition) { @@ -2339,7 +2339,7 @@ public function setWeeeTaxRowDisposition($weeeTaxRowDisposition) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseWeeeTaxDisposition($baseWeeeTaxDisposition) { @@ -2347,7 +2347,7 @@ public function setBaseWeeeTaxDisposition($baseWeeeTaxDisposition) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseWeeeTaxRowDisposition($baseWeeeTaxRowDisposition) { @@ -2355,7 +2355,7 @@ public function setBaseWeeeTaxRowDisposition($baseWeeeTaxRowDisposition) } /** - * {@inheritdoc} + * @inheritdoc */ public function getProductOption() { @@ -2363,7 +2363,7 @@ public function getProductOption() } /** - * {@inheritdoc} + * @inheritdoc */ public function setProductOption(\Magento\Catalog\Api\Data\ProductOptionInterface $productOption) { @@ -2371,7 +2371,7 @@ public function setProductOption(\Magento\Catalog\Api\Data\ProductOptionInterfac } /** - * {@inheritdoc} + * @inheritdoc * * @return \Magento\Sales\Api\Data\OrderItemExtensionInterface|null */ @@ -2381,7 +2381,7 @@ public function getExtensionAttributes() } /** - * {@inheritdoc} + * @inheritdoc * * @param \Magento\Sales\Api\Data\OrderItemExtensionInterface $extensionAttributes * @return $this diff --git a/app/code/Magento/Sales/Model/Order/Shipment.php b/app/code/Magento/Sales/Model/Order/Shipment.php index 8be12ec6ad57f..ebedc869e14bd 100644 --- a/app/code/Magento/Sales/Model/Order/Shipment.php +++ b/app/code/Magento/Sales/Model/Order/Shipment.php @@ -277,6 +277,8 @@ public function register() } /** + * Retrieves the collection used to track the shipment's items + * * @return mixed */ public function getItemsCollection() @@ -295,6 +297,8 @@ public function getItemsCollection() } /** + * Retrieves all non-deleted items from the shipment + * * @return array */ public function getAllItems() @@ -309,6 +313,8 @@ public function getAllItems() } /** + * Retrieves an item from the shipment using its ID + * * @param string|int $itemId * @return bool|\Magento\Sales\Model\Order\Shipment\Item */ @@ -323,6 +329,8 @@ public function getItemById($itemId) } /** + * Adds an item to the shipment + * * @param \Magento\Sales\Model\Order\Shipment\Item $item * @return $this */ @@ -353,6 +361,8 @@ public function getTracksCollection() } /** + * Retrieves all available tracks in the collection that aren't deleted + * * @return array */ public function getAllTracks() @@ -367,6 +377,8 @@ public function getAllTracks() } /** + * Retrieves a track using its ID + * * @param string|int $trackId * @return bool|\Magento\Sales\Model\Order\Shipment\Track */ @@ -381,6 +393,8 @@ public function getTrackById($trackId) } /** + * Addes a track to the collection and associates the shipment to the track + * * @param \Magento\Sales\Model\Order\Shipment\Track $track * @return $this */ @@ -409,8 +423,7 @@ public function addTrack(\Magento\Sales\Model\Order\Shipment\Track $track) } /** - * Adds comment to shipment with additional possibility to send it to customer via email - * and show it in customer account + * Adds comment to shipment with option to send it to customer via email and show it in customer account * * @param \Magento\Sales\Model\Order\Shipment\Comment|string $comment * @param bool $notify @@ -574,13 +587,10 @@ public function setItems($items) public function getTracks() { if ($this->getData(ShipmentInterface::TRACKS) === null) { - $collection = $this->_trackCollectionFactory->create()->setShipmentFilter($this->getId()); - if ($this->getId()) { - foreach ($collection as $item) { - $item->setShipment($this); - } - $this->setData(ShipmentInterface::TRACKS, $collection->getItems()); + foreach ($this->getTracksCollection() as $item) { + $item->setShipment($this); } + $this->setData(ShipmentInterface::TRACKS, $this->getTracksCollection()->getItems()); } return $this->getData(ShipmentInterface::TRACKS); } diff --git a/app/code/Magento/Sales/Model/Service/InvoiceService.php b/app/code/Magento/Sales/Model/Service/InvoiceService.php index 718f55c3e551c..2806f76b1389b 100644 --- a/app/code/Magento/Sales/Model/Service/InvoiceService.php +++ b/app/code/Magento/Sales/Model/Service/InvoiceService.php @@ -125,6 +125,8 @@ public function setVoid($id) } /** + * Creates an invoice based on the order and quantities provided + * * @param Order $order * @param array $qtys * @return \Magento\Sales\Model\Order\Invoice @@ -136,7 +138,7 @@ public function prepareInvoice(Order $order, array $qtys = []) $totalQty = 0; $qtys = $this->prepareItemsQty($order, $qtys); foreach ($order->getAllItems() as $orderItem) { - if (!$this->_canInvoiceItem($orderItem)) { + if (!$this->_canInvoiceItem($orderItem, $qtys)) { continue; } $item = $this->orderConverter->itemToInvoiceItem($orderItem); @@ -192,16 +194,15 @@ private function prepareItemsQty(Order $order, array $qtys = []) } /** - * Check if order item can be invoiced. Dummy item can be invoiced or with his children or - * with parent item which is included to invoice + * Check if order item can be invoiced. * * @param \Magento\Sales\Api\Data\OrderItemInterface $item + * @param array $qtys * @return bool * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - protected function _canInvoiceItem(\Magento\Sales\Api\Data\OrderItemInterface $item) + protected function _canInvoiceItem(\Magento\Sales\Api\Data\OrderItemInterface $item, array $qtys = []) { - $qtys = []; if ($item->getLockedDoInvoice()) { return false; } diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml index c82623632d782..a26fd6bc1c3a7 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml @@ -94,6 +94,24 @@ <click selector="{{AdminOrderFormItemsSection.addSelected}}" stepKey="clickAddSelectedProducts"/> </actionGroup> + <!--Add a simple product to order with user defined quantity--> + <actionGroup name="addSimpleProductToOrderWithCustomQuantity"> + <arguments> + <argument name="product" defaultValue="_defaultProduct"/> + <argument name="quantity" type="string"/> + </arguments> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMastToDisappear"/> + <waitForElementVisible selector="{{AdminOrderFormItemsSection.addProducts}}" stepKey="waitForAddProductButton"/> + <click selector="{{AdminOrderFormItemsSection.addProducts}}" stepKey="clickAddProducts"/> + <fillField selector="{{AdminOrderFormItemsSection.skuFilter}}" userInput="{{product.sku}}" stepKey="fillSkuFilter"/> + <click selector="{{AdminOrderFormItemsSection.search}}" stepKey="clickSearch"/> + <scrollTo selector="{{AdminOrderFormItemsSection.rowCheck('1')}}" x="0" y="-100" stepKey="scrollToCheckColumn"/> + <checkOption selector="{{AdminOrderFormItemsSection.rowCheck('1')}}" stepKey="selectProduct"/> + <fillField selector="{{AdminOrderFormItemsSection.rowQty('1')}}" userInput="{{quantity}}" stepKey="fillProductQty"/> + <scrollTo selector="{{AdminOrderFormItemsSection.addSelected}}" x="0" y="-100" stepKey="scrollToAddSelectedButton"/> + <click selector="{{AdminOrderFormItemsSection.addSelected}}" stepKey="clickAddSelectedProducts"/> + </actionGroup> + <!--Add configurable product to order --> <actionGroup name="addConfigurableProductToOrder"> <arguments> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminAbleToShipPartiallyInvoicedItemsTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminAbleToShipPartiallyInvoicedItemsTest.xml new file mode 100644 index 0000000000000..e4fd894f608c5 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminAbleToShipPartiallyInvoicedItemsTest.xml @@ -0,0 +1,168 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminAbleToShipPartiallyInvoicedItemsTest"> + <annotations> + <title value="Ship Action is available for remaining of the partially invoiced items "/> + <description value="Admin should be able to ship remaining ordered items if some of them are already refunded"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-93030"/> + <group value="sales"/> + + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <!--login to Admin--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <comment userInput="Admin creates order" stepKey="adminCreateOrderComment"/> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="navigateToOrderIndexPage"/> + <waitForPageLoad stepKey="waitForIndexPageLoad"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Orders" stepKey="seeIndexPageTitle"/> + + <!--Create new order--> + <click selector="{{AdminOrdersGridSection.createNewOrder}}" stepKey="clickCreateNewOrder"/> + <click selector="{{AdminOrderFormActionSection.CreateNewCustomer}}" stepKey="clickCreateCustomer"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Create New Order" stepKey="seeNewOrderPageTitle"/> + <actionGroup ref="addSimpleProductToOrderWithCustomQuantity" stepKey="addSimpleProductToOrderWithUserDefinedQty"> + <argument name="product" value="_defaultProduct"/> + <argument name="quantity" value="10"/> + </actionGroup> + + <!--Fill customer group and customer email which both are now required fields--> + <selectOption selector="{{AdminOrderFormAccountSection.group}}" userInput="{{GeneralCustomerGroup.code}}" stepKey="selectCustomerGroup" after="addSimpleProductToOrderWithUserDefinedQty"/> + <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"/> + + <!--Verify totals on Order page--> + <see selector="{{AdminOrderFormTotalSection.total('Subtotal')}}" userInput="$1,230.00" stepKey="seeOrderSubTotal" after="selectFlatRateShipping"/> + <see selector="{{AdminOrderFormTotalSection.total('Shipping')}}" userInput="$50.00" stepKey="seeOrderShipping" after="seeOrderSubTotal"/> + <scrollTo selector="{{AdminOrderFormTotalSection.grandTotal}}" stepKey="scrollToOrderGrandTotal"/> + <see selector="{{AdminOrderFormTotalSection.grandTotal}}" userInput="$1,280.00" stepKey="seeCorrectGrandTotal" after="scrollToOrderGrandTotal"/> + + <!--Submit Order and verify information--> + <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="clickSubmitOrder" after="seeCorrectGrandTotal"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskTodisappear"/> + <waitForPageLoad stepKey="waitForOrderToProcess"/> + <seeInCurrentUrl url="{{AdminOrderDetailsPage.url}}" stepKey="seeViewOrderPage" after="clickSubmitOrder"/> + <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="You created the order." stepKey="seeSuccessMessage" after="seeViewOrderPage"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="getOrderId" after="seeSuccessMessage"/> + <scrollTo selector="{{AdminOrderItemsOrderedSection.qtyColumn}}" stepKey="scrollToItemsOrdered" after="getOrderId"/> + <see selector="{{AdminOrderItemsOrderedSection.itemQty('1')}}" userInput="Ordered 10" stepKey="seeQtyOfItemsOrdered" after="scrollToItemsOrdered"/> + + <!--Create order invoice for first half of ordered items--> + <comment userInput="Admin creates invoice for order" stepKey="adminCreateInvoiceComment" /> + <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceActionButton"/> + <!--<seeInCurrentUrl url="{{AdminInvoiceNewPage.url}}" stepKey="seeOrderInvoiceUrl" after="clickInvoiceAction"/>--> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Invoice" stepKey="seePageNameNewInvoicePage" after="clickInvoiceActionButton"/> + <scrollTo selector="{{AdminInvoiceItemsSection.itemQtyToInvoice('1')}}" stepKey="scrollToItemsInvoiced"/> + + <!--Verify items invoiced information--> + <fillField selector="{{AdminInvoiceItemsSection.itemQtyToInvoice('1')}}" userInput="5" stepKey="invoiceHalfTheItems"/> + <click selector="{{AdminInvoiceItemsSection.updateQty}}" stepKey="updateQtyToBeInvoiced"/> + <waitForLoadingMaskToDisappear stepKey="WaitForQtyToUpdate"/> + <waitForElementVisible selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="waitforSubmitInvoiceBtn"/> + + <!--Submit Invoice--> + <click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="submitInvoice"/> + <waitForLoadingMaskToDisappear stepKey="WaitForInvoiceToSubmit"/> + <!--<waitForElementVisible selector="{{AdminOrderDetailsMessagesSection.successMessage}}" stepKey="waitUntilInvoiceSucesssMsg"/>--> + + <!--Invoice created successfully--> + <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="The invoice has been created." stepKey="seeInvoiceSuccessMessage"/> + <click selector="{{AdminOrderDetailsOrderViewSection.invoices}}" stepKey="clickInvoicesTab"/> + <waitForLoadingMaskToDisappear stepKey="waitForInvoiceGridToLoad" after="clickInvoicesTab"/> + <see selector="{{AdminOrderInvoicesTabSection.gridRow('1')}}" userInput="$665.00" stepKey="seeOrderInvoiceTabInGrid" after="waitForInvoiceGridToLoad"/> + <click selector="{{AdminOrderInvoicesTabSection.viewGridRow('1')}}" stepKey="clickToViewInvoice" after="seeOrderInvoiceTabInGrid"/> + <click selector="{{AdminInvoiceOrderInformationSection.orderId}}" stepKey="clickOrderIdLinkOnInvoice"/> + + <!--Ship Order--> + <comment userInput="Admin creates shipment" stepKey="adminCreatesShipmentComment" before="clickShip"/> + <click selector="{{AdminOrderDetailsMainActionsSection.ship}}" stepKey="clickShip" after="clickOrderIdLinkOnInvoice"/> + <seeInCurrentUrl url="{{AdminShipmentNewPage.url}}" stepKey="OrderShipmentUrl" after="clickShip"/> + <scrollTo selector="{{AdminShipmentItemsSection.itemQtyToShip('1')}}" stepKey="scrollToItemsToShip"/> + <see selector="{{AdminShipmentItemsSection.itemQty('1')}}" userInput="Invoiced 5" stepKey="see5itemsInvoiced"/> + <fillField selector="{{AdminShipmentItemsSection.itemQtyToShip('1')}}" userInput="5" stepKey="fillQtyOfItemsToShip"/> + + <!--Submit Shipment--> + <click selector="{{AdminShipmentMainActionsSection.submitShipment}}" stepKey="submitShipment" after="fillQtyOfItemsToShip"/> + <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="The shipment has been created." stepKey="successfullShipmentCreation" after="submitShipment"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="$getOrderId" stepKey="seeOrderIdInPageTitleAfterShip"/> + + <!--Verify Items Status and Shipped Qty in the Items Ordered section--> + <scrollTo selector="{{AdminOrderItemsOrderedSection.itemStatus('1')}}" stepKey="scrollToItemsShipped"/> + <see selector="{{AdminOrderItemsOrderedSection.itemQty('1')}}" userInput="Shipped 5" stepKey="see5itemsShipped"/> + + <!--Create Credit Memo--> + <comment userInput="Admin creates credit memo" stepKey="createCreditMemoComment"/> + <click selector="{{AdminOrderDetailsMainActionsSection.creditMemo}}" stepKey="clickCreateCreditMemo" after="createCreditMemoComment"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Memo" stepKey="seeNewMemoInPageTitle"/> + + <!--Submit refund--> + <scrollTo selector="{{AdminCreditMemoItemsSection.itemQtyToRefund('1')}}" stepKey="scrollToItemsToRefund"/> + <!--<fillField selector="{{AdminCreditMemoItemsSection.itemQtyToRefund('1')}}" userInput="5" stepKey="fillQtyOfItemsToRefund" after="scrollToItemsToRefund"/>--> + <!--<click selector="{{AdminCreditMemoItemsSection.updateQty}}" stepKey="updateRefundQty"/>--> + <waitForLoadingMaskToDisappear stepKey="waitForRefundQtyToUpdate"/> + <waitForElementVisible selector="{{AdminCreditMemoTotalSection.submitRefundOffline}}" stepKey="seeSubmitRefundBtn"/> + <click selector="{{AdminCreditMemoTotalSection.submitRefundOffline}}" stepKey="submitRefundOffline"/> + <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="You created the credit memo." stepKey="seeCreditMemoSuccessMsg" after="submitRefundOffline"/> + + <!--Create invoice for rest of the ordered items--> + <comment userInput="Admin creates invoice for rest of the items" stepKey="adminCreateInvoiceComment2" /> + <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceActionForRestOfItems"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Invoice" stepKey="seePageNameNewInvoicePage2" after="clickInvoiceActionForRestOfItems"/> + + <comment userInput="Qty To Invoice is 5" stepKey="seeRemainderInQtyToInvoice"/> + <scrollTo selector="{{AdminInvoiceItemsSection.itemQtyToInvoice('1')}}" stepKey="scrollToItemsInvoiced2"/> + <!--<see selector="{{AdminInvoiceItemsSection.itemQtyToInvoice('1')}}" stepKey="scrollToItemsInvoiced2"/>--> + <seeInField userInput="5" selector="{{AdminInvoiceItemsSection.itemQtyToInvoice('1')}}" stepKey="see5InTheQtyToInvoice"/> + + <!--Verify items invoiced information--> + <see selector="{{AdminInvoiceItemsSection.itemQty('1')}}" userInput="Refunded 5" stepKey="seeQtyOfItemsRefunded"/> + + <!--Submit Invoice--> + <click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="submitInvoice2" /> + + <!--Invoice created successfully for the rest of the ordered items--> + <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="The invoice has been created." stepKey="seeInvoiceSuccessMessage2" after="submitInvoice2"/> + + <!--Verify Ship Action can be done for the rest of the invoiced items --> + <click selector="{{AdminOrderDetailsMainActionsSection.ship}}" stepKey="clickShipActionForRestOfItems" after="seeInvoiceSuccessMessage2"/> + + <!--Verify Items To Ship section --> + <scrollTo selector="{{AdminShipmentItemsSection.itemQtyToShip('1')}}" stepKey="scrollToItemsToShip2"/> + <see selector="{{AdminShipmentItemsSection.itemQty('1')}}" userInput="Invoiced 10" stepKey="SeeAll10ItemsInvoiced"/> + <fillField selector="{{AdminShipmentItemsSection.itemQtyToShip('1')}}" userInput="5" stepKey="fillRestOfItemsToShip"/> + + <!--Submit Shipment--> + <click selector="{{AdminShipmentMainActionsSection.submitShipment}}" stepKey="submitShipment2" after="fillRestOfItemsToShip"/> + <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="The shipment has been created." stepKey="successfullyCreatedShipment" after="submitShipment2"/> + + <!--Verify Items Status and Shipped Qty in the Items Ordered section--> + <scrollTo selector="{{AdminOrderItemsOrderedSection.itemStatus('1')}}" stepKey="scrollToItemsOrdered2"/> + <see selector="{{AdminOrderItemsOrderedSection.itemQty('1')}}" userInput="Shipped 10" stepKey="seeAllItemsShipped"/> + </test> +</tests> + diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml index 82f5b515eb967..0032a6c987e82 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml @@ -68,6 +68,7 @@ <click selector="{{AdminOrdersGridSection.firstRow}}" stepKey="clickOrderRow"/> <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoice"/> <click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="clickSubmitInvoice"/> + <waitForPageLoad stepKey="waitForInvoicePageLoad"/> <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="The invoice has been created." stepKey="seeSuccessMessage"/> <click selector="{{AdminOrderDetailsOrderViewSection.invoices}}" stepKey="clickInvoices"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask5" /> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml index ec725ad5fe4c5..19bcca985f974 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml @@ -11,6 +11,7 @@ <test name="StorefrontRedirectToOrderHistory"> <annotations> <features value="Redirection Rules"/> + <stories value="Create Invoice"/> <title value="Create Invoice"/> <description value="Check while order printing URL with an id of not relevant order redirects to order history"/> diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassCancelTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassCancelTest.php deleted file mode 100644 index 38449c5686631..0000000000000 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassCancelTest.php +++ /dev/null @@ -1,294 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Sales\Test\Unit\Controller\Adminhtml\Order; - -use Magento\Framework\App\Action\Context; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; - -/** - * Class MassCancelTest - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class MassCancelTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Sales\Controller\Adminhtml\Order\MassCancel - */ - protected $massAction; - - /** - * @var Context|\PHPUnit_Framework_MockObject_MockObject - */ - protected $contextMock; - - /** - * @var \Magento\Backend\Model\View\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject - */ - protected $resultRedirectMock; - - /** - * @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject - */ - protected $requestMock; - - /** - * @var \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $responseMock; - - /** - * @var \Magento\Framework\Message\Manager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $messageManagerMock; - - /** - * @var \Magento\Framework\ObjectManager\ObjectManager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $objectManagerMock; - - /** - * @var \Magento\Backend\Model\Session|\PHPUnit_Framework_MockObject_MockObject - */ - protected $sessionMock; - - /** - * @var \Magento\Framework\App\ActionFlag|\PHPUnit_Framework_MockObject_MockObject - */ - protected $actionFlagMock; - - /** - * @var \Magento\Backend\Helper\Data|\PHPUnit_Framework_MockObject_MockObject - */ - protected $helperMock; - - /** - * @var \Magento\Sales\Model\Order|\PHPUnit_Framework_MockObject_MockObject - */ - protected $orderMock; - - /** - * @var \Magento\Sales\Model\ResourceModel\Order\Collection|\PHPUnit_Framework_MockObject_MockObject - */ - protected $orderCollectionMock; - - /** - * @var \Magento\Sales\Model\ResourceModel\Order\CollectionFactory|\PHPUnit_Framework_MockObject_MockObject - */ - protected $orderCollectionFactoryMock; - - /** - * @var \Magento\Ui\Component\MassAction\Filter|\PHPUnit_Framework_MockObject_MockObject - */ - protected $filterMock; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $orderManagementMock; - - /** - * Test setup - */ - protected function setUp() - { - $objectManagerHelper = new ObjectManagerHelper($this); - $this->contextMock = $this->createMock(\Magento\Backend\App\Action\Context::class); - $this->messageManagerMock = $this->createMock(\Magento\Framework\Message\Manager::class); - $this->responseMock = $this->createMock(\Magento\Framework\App\ResponseInterface::class); - $this->requestMock = $this->createMock(\Magento\Framework\App\Request\Http::class); - $this->objectManagerMock = $this->createMock(\Magento\Framework\ObjectManager\ObjectManager::class); - - $resultRedirectFactory = $this->createMock(\Magento\Backend\Model\View\Result\RedirectFactory::class); - - $this->orderCollectionMock = $this->getMockBuilder(\Magento\Sales\Model\ResourceModel\Order\Collection::class) - ->disableOriginalConstructor() - ->getMock(); - - $resourceCollection = \Magento\Sales\Model\ResourceModel\Order\CollectionFactory::class; - $this->orderCollectionFactoryMock = $this->getMockBuilder($resourceCollection) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - - $this->sessionMock = $this->createPartialMock(\Magento\Backend\Model\Session::class, ['setIsUrlNotice']); - $this->actionFlagMock = $this->createPartialMock(\Magento\Framework\App\ActionFlag::class, ['get', 'set']); - $this->helperMock = $this->createPartialMock(\Magento\Backend\Helper\Data::class, ['getUrl']); - $this->resultRedirectMock = $this->createMock(\Magento\Backend\Model\View\Result\Redirect::class); - $resultRedirectFactory->expects($this->any())->method('create')->willReturn($this->resultRedirectMock); - - $redirectMock = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $resultFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\ResultFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $resultFactoryMock->expects($this->any()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT) - ->willReturn($redirectMock); - - $this->contextMock->expects($this->once())->method('getMessageManager')->willReturn($this->messageManagerMock); - $this->contextMock->expects($this->once())->method('getRequest')->willReturn($this->requestMock); - $this->contextMock->expects($this->once())->method('getResponse')->willReturn($this->responseMock); - $this->contextMock->expects($this->once())->method('getObjectManager')->willReturn($this->objectManagerMock); - $this->contextMock->expects($this->once())->method('getSession')->willReturn($this->sessionMock); - $this->contextMock->expects($this->once())->method('getActionFlag')->willReturn($this->actionFlagMock); - $this->contextMock->expects($this->once())->method('getHelper')->willReturn($this->helperMock); - $this->contextMock->expects($this->once()) - ->method('getResultRedirectFactory') - ->willReturn($resultRedirectFactory); - $this->contextMock->expects($this->any()) - ->method('getResultFactory') - ->willReturn($resultFactoryMock); - $this->filterMock = $this->createMock(\Magento\Ui\Component\MassAction\Filter::class); - $this->filterMock->expects($this->once()) - ->method('getCollection') - ->with($this->orderCollectionMock) - ->willReturn($this->orderCollectionMock); - $this->orderCollectionFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($this->orderCollectionMock); - $this->orderManagementMock = $this->createMock(\Magento\Sales\Api\OrderManagementInterface::class); - - $this->massAction = $objectManagerHelper->getObject( - \Magento\Sales\Controller\Adminhtml\Order\MassCancel::class, - [ - 'context' => $this->contextMock, - 'filter' => $this->filterMock, - 'collectionFactory' => $this->orderCollectionFactoryMock, - 'orderManagement' => $this->orderManagementMock - ] - ); - } - - /** - * Test for selected orders - * Two orders, only $order1 can be canceled - */ - public function testExecuteCanCancelOneOrder() - { - $order1id = 100; - $order2id = 200; - - $order1 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - $order2 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - $orders = [$order1, $order2]; - $countOrders = count($orders); - - $this->orderCollectionMock->expects($this->any()) - ->method('getItems') - ->willReturn($orders); - - $order1->expects($this->once()) - ->method('getEntityId') - ->willReturn($order1id); - - $order2->expects($this->once()) - ->method('getEntityId') - ->willReturn($order2id); - - $this->orderCollectionMock->expects($this->once()) - ->method('count') - ->willReturn($countOrders); - - $this->orderManagementMock->expects($this->at(0))->method('cancel')->with($order1id)->willReturn(true); - $this->orderManagementMock->expects($this->at(1))->method('cancel')->with($order2id)->willReturn(false); - - $this->messageManagerMock->expects($this->once()) - ->method('addErrorMessage') - ->with('1 order(s) cannot be canceled.'); - - $this->messageManagerMock->expects($this->once()) - ->method('addSuccessMessage') - ->with('We canceled 1 order(s).'); - - $this->resultRedirectMock->expects($this->once()) - ->method('setPath') - ->with('sales/*/') - ->willReturnSelf(); - - $this->massAction->execute(); - } - - /** - * Test for excluded orders - * Two orders could't be canceled - */ - public function testExcludedCannotCancelOrders() - { - $order1 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - $order2 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - - $orders = [$order1, $order2]; - $countOrders = count($orders); - - $order1->expects($this->once()) - ->method('getEntityId') - ->willReturn(100); - - $order2->expects($this->once()) - ->method('getEntityId') - ->willReturn(200); - - $this->orderCollectionMock->expects($this->any()) - ->method('getItems') - ->willReturn([$order1, $order2]); - - $this->orderCollectionMock->expects($this->once()) - ->method('count') - ->willReturn($countOrders); - - $this->orderManagementMock->expects($this->atLeastOnce())->method('cancel')->willReturn(false); - - $this->messageManagerMock->expects($this->once()) - ->method('addErrorMessage') - ->with('You cannot cancel the order(s).'); - - $this->resultRedirectMock->expects($this->once()) - ->method('setPath') - ->with('sales/*/') - ->willReturnSelf(); - - $this->massAction->execute(); - } - - /** - * Order throws exception while canceling - */ - public function testException() - { - $exception = new \Exception('Can not cancel'); - - $order1 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - $this->orderCollectionMock->expects($this->any()) - ->method('getItems') - ->willReturn([$order1]); - - $order1->expects($this->once()) - ->method('getEntityId') - ->willReturn(100); - - $this->orderManagementMock->expects($this->atLeastOnce())->method('cancel')->willThrowException($exception); - - $this->messageManagerMock->expects($this->once()) - ->method('addErrorMessage') - ->with('Can not cancel'); - - $this->massAction->execute(); - } -} diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassHoldTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassHoldTest.php deleted file mode 100644 index bfd1d1e825f89..0000000000000 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassHoldTest.php +++ /dev/null @@ -1,262 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Sales\Test\Unit\Controller\Adminhtml\Order; - -use Magento\Framework\App\Action\Context; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; - -/** - * Class MassHoldTest - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class MassHoldTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Sales\Controller\Adminhtml\Order\MassHold - */ - protected $massAction; - - /** - * @var Context|\PHPUnit_Framework_MockObject_MockObject - */ - protected $contextMock; - - /** - * @var \Magento\Backend\Model\View\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject - */ - protected $resultRedirectMock; - - /** - * @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject - */ - protected $requestMock; - - /** - * @var \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $responseMock; - - /** - * @var \Magento\Framework\Message\Manager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $messageManagerMock; - - /** - * @var \Magento\Framework\ObjectManager\ObjectManager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $objectManagerMock; - - /** - * @var \Magento\Backend\Model\Session|\PHPUnit_Framework_MockObject_MockObject - */ - protected $sessionMock; - - /** - * @var \Magento\Framework\App\ActionFlag|\PHPUnit_Framework_MockObject_MockObject - */ - protected $actionFlagMock; - - /** - * @var \Magento\Backend\Helper\Data|\PHPUnit_Framework_MockObject_MockObject - */ - protected $helperMock; - - /** - * @var \Magento\Sales\Model\Order|\PHPUnit_Framework_MockObject_MockObject - */ - protected $orderMock; - - /** - * @var \Magento\Sales\Model\ResourceModel\Order\Collection|\PHPUnit_Framework_MockObject_MockObject - */ - protected $orderCollectionMock; - - /** - * @var \Magento\Sales\Model\ResourceModel\Order\CollectionFactory|\PHPUnit_Framework_MockObject_MockObject - */ - protected $orderCollectionFactoryMock; - - /** - * @var \Magento\Ui\Component\MassAction\Filter|\PHPUnit_Framework_MockObject_MockObject - */ - protected $filterMock; - - /** - * @var \Magento\Sales\Api\OrderManagementInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $orderManagementMock; - - /** - * Test setup - */ - protected function setUp() - { - $objectManagerHelper = new ObjectManagerHelper($this); - $this->orderManagementMock = $this->getMockBuilder(\Magento\Sales\Api\OrderManagementInterface::class) - ->getMockForAbstractClass(); - $this->contextMock = $this->createMock(\Magento\Backend\App\Action\Context::class); - $resultRedirectFactory = $this->createMock(\Magento\Backend\Model\View\Result\RedirectFactory::class); - $this->responseMock = $this->createMock(\Magento\Framework\App\ResponseInterface::class); - $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) - ->disableOriginalConstructor()->getMock(); - $this->objectManagerMock = $this->createPartialMock( - \Magento\Framework\ObjectManager\ObjectManager::class, - ['create'] - ); - $this->messageManagerMock = $this->createMock(\Magento\Framework\Message\Manager::class); - $this->orderCollectionMock = $this->getMockBuilder(\Magento\Sales\Model\ResourceModel\Order\Collection::class) - ->disableOriginalConstructor() - ->getMock(); - $orderCollection = \Magento\Sales\Model\ResourceModel\Order\CollectionFactory::class; - $this->orderCollectionFactoryMock = $this->getMockBuilder($orderCollection) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $redirectMock = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $resultFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\ResultFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $resultFactoryMock->expects($this->any()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT) - ->willReturn($redirectMock); - - $this->sessionMock = $this->createPartialMock(\Magento\Backend\Model\Session::class, ['setIsUrlNotice']); - $this->actionFlagMock = $this->createPartialMock(\Magento\Framework\App\ActionFlag::class, ['get', 'set']); - $this->helperMock = $this->createPartialMock(\Magento\Backend\Helper\Data::class, ['getUrl']); - $this->resultRedirectMock = $this->createMock(\Magento\Backend\Model\View\Result\Redirect::class); - $resultRedirectFactory->expects($this->any())->method('create')->willReturn($this->resultRedirectMock); - - $this->contextMock->expects($this->once())->method('getMessageManager')->willReturn($this->messageManagerMock); - $this->contextMock->expects($this->once())->method('getRequest')->willReturn($this->requestMock); - $this->contextMock->expects($this->once())->method('getResponse')->willReturn($this->responseMock); - $this->contextMock->expects($this->once())->method('getObjectManager')->willReturn($this->objectManagerMock); - $this->contextMock->expects($this->once())->method('getSession')->willReturn($this->sessionMock); - $this->contextMock->expects($this->once())->method('getActionFlag')->willReturn($this->actionFlagMock); - $this->contextMock->expects($this->once())->method('getHelper')->willReturn($this->helperMock); - $this->contextMock - ->expects($this->once()) - ->method('getResultRedirectFactory') - ->willReturn($resultRedirectFactory); - $this->contextMock->expects($this->any()) - ->method('getResultFactory') - ->willReturn($resultFactoryMock); - - $this->filterMock = $this->createMock(\Magento\Ui\Component\MassAction\Filter::class); - $this->filterMock->expects($this->once()) - ->method('getCollection') - ->with($this->orderCollectionMock) - ->willReturn($this->orderCollectionMock); - $this->orderCollectionFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($this->orderCollectionMock); - - $this->massAction = $objectManagerHelper->getObject( - \Magento\Sales\Controller\Adminhtml\Order\MassHold::class, - [ - 'context' => $this->contextMock, - 'filter' => $this->filterMock, - 'collectionFactory' => $this->orderCollectionFactoryMock, - 'orderManagement' => $this->orderManagementMock - ] - ); - } - - /** - * @throws \Magento\Framework\Exception\LocalizedException - */ - public function testExecuteOneOrderPutOnHold() - { - $order1 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - $order2 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - - $orders = [$order1, $order2]; - $countOrders = count($orders); - - $this->orderCollectionMock->expects($this->any()) - ->method('getItems') - ->willReturn($orders); - - $order1->expects($this->once()) - ->method('canHold') - ->willReturn(true); - $this->orderManagementMock->expects($this->once()) - ->method('hold'); - $this->orderCollectionMock->expects($this->once()) - ->method('count') - ->willReturn($countOrders); - - $order2->expects($this->once()) - ->method('canHold') - ->willReturn(false); - - $this->messageManagerMock->expects($this->once()) - ->method('addErrorMessage') - ->with('1 order(s) were not put on hold.'); - - $this->messageManagerMock->expects($this->once()) - ->method('addSuccessMessage') - ->with('You have put 1 order(s) on hold.'); - - $this->resultRedirectMock->expects($this->once()) - ->method('setPath') - ->with('sales/*/') - ->willReturnSelf(); - - $this->massAction->execute(); - } - - /** - * @throws \Magento\Framework\Exception\LocalizedException - */ - public function testExecuteNoOrdersPutOnHold() - { - $order1 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - $order2 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - - $orders = [$order1, $order2]; - $countOrders = count($orders); - - $this->orderCollectionMock->expects($this->any()) - ->method('getItems') - ->willReturn($orders); - - $order1->expects($this->once()) - ->method('canHold') - ->willReturn(false); - - $this->orderCollectionMock->expects($this->once()) - ->method('count') - ->willReturn($countOrders); - - $order2->expects($this->once()) - ->method('canHold') - ->willReturn(false); - - $this->messageManagerMock->expects($this->once()) - ->method('addErrorMessage') - ->with('No order(s) were put on hold.'); - - $this->resultRedirectMock->expects($this->once()) - ->method('setPath') - ->with('sales/*/') - ->willReturnSelf(); - - $this->massAction->execute(); - } -} diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassUnholdTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassUnholdTest.php deleted file mode 100644 index 56473ec948f27..0000000000000 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassUnholdTest.php +++ /dev/null @@ -1,261 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Sales\Test\Unit\Controller\Adminhtml\Order; - -use Magento\Framework\App\Action\Context; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; - -/** - * Class MassHoldTest - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class MassUnholdTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Sales\Controller\Adminhtml\Order\MassUnhold - */ - protected $massAction; - - /** - * @var Context|\PHPUnit_Framework_MockObject_MockObject - */ - protected $contextMock; - - /** - * @var \Magento\Backend\Model\View\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject - */ - protected $resultRedirectMock; - - /** - * @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject - */ - protected $requestMock; - - /** - * @var \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $responseMock; - - /** - * @var \Magento\Framework\Message\Manager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $messageManagerMock; - - /** - * @var \Magento\Framework\ObjectManager\ObjectManager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $objectManagerMock; - - /** - * @var \Magento\Backend\Model\Session|\PHPUnit_Framework_MockObject_MockObject - */ - protected $sessionMock; - - /** - * @var \Magento\Framework\App\ActionFlag|\PHPUnit_Framework_MockObject_MockObject - */ - protected $actionFlagMock; - - /** - * @var \Magento\Backend\Helper\Data|\PHPUnit_Framework_MockObject_MockObject - */ - protected $helperMock; - - /** - * @var \Magento\Sales\Model\Order|\PHPUnit_Framework_MockObject_MockObject - */ - protected $orderMock; - - /** - * @var \Magento\Sales\Model\ResourceModel\Order\Collection|\PHPUnit_Framework_MockObject_MockObject - */ - protected $orderCollectionMock; - - /** - * @var \Magento\Sales\Model\ResourceModel\Order\CollectionFactory|\PHPUnit_Framework_MockObject_MockObject - */ - protected $orderCollectionFactoryMock; - - /** - * @var \Magento\Ui\Component\MassAction\Filter|\PHPUnit_Framework_MockObject_MockObject - */ - protected $filterMock; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $orderManagementMock; - - /** - * Test setup - */ - protected function setUp() - { - $objectManagerHelper = new ObjectManagerHelper($this); - $this->contextMock = $this->createMock(\Magento\Backend\App\Action\Context::class); - $resultRedirectFactory = $this->createMock(\Magento\Backend\Model\View\Result\RedirectFactory::class); - $this->responseMock = $this->createMock(\Magento\Framework\App\ResponseInterface::class); - $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) - ->disableOriginalConstructor()->getMock(); - $this->objectManagerMock = $this->createMock(\Magento\Framework\ObjectManager\ObjectManager::class); - $this->messageManagerMock = $this->createMock(\Magento\Framework\Message\Manager::class); - - $this->orderCollectionMock = $this->getMockBuilder(\Magento\Sales\Model\ResourceModel\Order\Collection::class) - ->disableOriginalConstructor() - ->getMock(); - $orderCollection = \Magento\Sales\Model\ResourceModel\Order\CollectionFactory::class; - $this->orderCollectionFactoryMock = $this->getMockBuilder($orderCollection) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->sessionMock = $this->createPartialMock(\Magento\Backend\Model\Session::class, ['setIsUrlNotice']); - $this->actionFlagMock = $this->createPartialMock(\Magento\Framework\App\ActionFlag::class, ['get', 'set']); - $this->helperMock = $this->createPartialMock(\Magento\Backend\Helper\Data::class, ['getUrl']); - $this->resultRedirectMock = $this->createMock(\Magento\Backend\Model\View\Result\Redirect::class); - $resultRedirectFactory->expects($this->any())->method('create')->willReturn($this->resultRedirectMock); - - $redirectMock = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $resultFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\ResultFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $resultFactoryMock->expects($this->any()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT) - ->willReturn($redirectMock); - - $this->contextMock->expects($this->once())->method('getMessageManager')->willReturn($this->messageManagerMock); - $this->contextMock->expects($this->once())->method('getRequest')->willReturn($this->requestMock); - $this->contextMock->expects($this->once())->method('getResponse')->willReturn($this->responseMock); - $this->contextMock->expects($this->once())->method('getObjectManager')->willReturn($this->objectManagerMock); - $this->contextMock->expects($this->once())->method('getSession')->willReturn($this->sessionMock); - $this->contextMock->expects($this->once())->method('getActionFlag')->willReturn($this->actionFlagMock); - $this->contextMock->expects($this->once())->method('getHelper')->willReturn($this->helperMock); - $this->contextMock - ->expects($this->once()) - ->method('getResultRedirectFactory') - ->willReturn($resultRedirectFactory); - $this->contextMock->expects($this->any()) - ->method('getResultFactory') - ->willReturn($resultFactoryMock); - - $this->filterMock = $this->createMock(\Magento\Ui\Component\MassAction\Filter::class); - $this->filterMock->expects($this->once()) - ->method('getCollection') - ->with($this->orderCollectionMock) - ->willReturn($this->orderCollectionMock); - $this->orderCollectionFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($this->orderCollectionMock); - - $this->orderManagementMock = $this->createMock(\Magento\Sales\Api\OrderManagementInterface::class); - - $this->massAction = $objectManagerHelper->getObject( - \Magento\Sales\Controller\Adminhtml\Order\MassUnhold::class, - [ - 'context' => $this->contextMock, - 'filter' => $this->filterMock, - 'collectionFactory' => $this->orderCollectionFactoryMock, - 'orderManagement' => $this->orderManagementMock - ] - ); - } - - /** - * @throws \Magento\Framework\Exception\LocalizedException - */ - public function testExecuteOneOrdersReleasedFromHold() - { - $order1 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - $order2 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - - $orders = [$order1, $order2]; - - $this->orderCollectionMock->expects($this->any()) - ->method('getItems') - ->willReturn($orders); - - $order1->expects($this->once()) - ->method('canUnhold') - ->willReturn(true); - $order1->expects($this->once()) - ->method('getEntityId'); - - $this->orderCollectionMock->expects($this->once()) - ->method('count') - ->willReturn(count($orders)); - - $order2->expects($this->once()) - ->method('canUnhold') - ->willReturn(false); - - $this->orderManagementMock->expects($this->atLeastOnce())->method('unHold')->willReturn(true); - - $this->messageManagerMock->expects($this->once()) - ->method('addErrorMessage') - ->with('1 order(s) were not released from on hold status.'); - - $this->messageManagerMock->expects($this->once()) - ->method('addSuccessMessage') - ->with('1 order(s) have been released from on hold status.'); - - $this->resultRedirectMock->expects($this->once()) - ->method('setPath') - ->with('sales/*/') - ->willReturnSelf(); - - $this->massAction->execute(); - } - - /** - * @throws \Magento\Framework\Exception\LocalizedException - */ - public function testExecuteNoReleasedOrderFromHold() - { - $order1 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - $order2 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - - $orders = [$order1, $order2]; - - $this->orderCollectionMock->expects($this->any()) - ->method('getItems') - ->willReturn($orders); - - $order1->expects($this->once()) - ->method('canUnhold') - ->willReturn(false); - - $this->orderCollectionMock->expects($this->once()) - ->method('count') - ->willReturn(count($orders)); - - $order2->expects($this->once()) - ->method('canUnhold') - ->willReturn(false); - - $this->messageManagerMock->expects($this->once()) - ->method('addErrorMessage') - ->with('No order(s) were released from on hold status.'); - - $this->resultRedirectMock->expects($this->once()) - ->method('setPath') - ->with('sales/*/') - ->willReturnSelf(); - - $this->massAction->execute(); - } -} diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/ItemTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/ItemTest.php index 76bfd62a7b889..3d4a5785d6254 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/ItemTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/ItemTest.php @@ -295,14 +295,14 @@ public function getItemQtyVariants() 'qty_ordered' => 12, 'qty_invoiced' => 5, 'qty_refunded' => 5, 'qty_shipped' => 0, 'qty_canceled' => 0, ], - 'expectedResult' => ['to_ship' => 7.0, 'to_invoice' => 7.0] + 'expectedResult' => ['to_ship' => 12.0, 'to_invoice' => 7.0] ], 'partially_refunded' => [ 'options' => [ 'qty_ordered' => 12, 'qty_invoiced' => 12, 'qty_refunded' => 5, 'qty_shipped' => 0, 'qty_canceled' => 0, ], - 'expectedResult' => ['to_ship' => 7.0, 'to_invoice' => 0.0] + 'expectedResult' => ['to_ship' => 12.0, 'to_invoice' => 0.0] ], 'partially_shipped' => [ 'options' => [ @@ -316,7 +316,7 @@ public function getItemQtyVariants() 'qty_ordered' => 12, 'qty_invoiced' => 12, 'qty_refunded' => 5, 'qty_shipped' => 4, 'qty_canceled' => 0 ], - 'expectedResult' => ['to_ship' => 3.0, 'to_invoice' => 0.0] + 'expectedResult' => ['to_ship' => 8.0, 'to_invoice' => 0.0] ], 'complete' => [ 'options' => [ @@ -337,7 +337,7 @@ public function getItemQtyVariants() 'qty_ordered' => 4.4, 'qty_invoiced' => 0.4, 'qty_refunded' => 0.4, 'qty_shipped' => 4, 'qty_canceled' => 0, ], - 'expectedResult' => ['to_ship' => 0.0, 'to_invoice' => 4.0] + 'expectedResult' => ['to_ship' => 0.4, 'to_invoice' => 4.0] ], 'completely_invoiced_using_decimals' => [ 'options' => [ diff --git a/app/code/Magento/Sales/Test/Unit/Model/OrderTest.php b/app/code/Magento/Sales/Test/Unit/Model/OrderTest.php index 7f6363346872c..873cd64e9c364 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/OrderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/OrderTest.php @@ -17,6 +17,8 @@ * Test class for \Magento\Sales\Model\Order * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.TooManyPublicMethods) + * @SuppressWarnings(PHPMD.ExcessivePublicCount) */ class OrderTest extends \PHPUnit\Framework\TestCase { @@ -114,7 +116,9 @@ protected function setUp() 'getParentItemId', 'getQuoteItemId', 'getLockedDoInvoice', - 'getProductId' + 'getProductId', + 'getQtyRefunded', + 'getQtyInvoiced', ]); $this->salesOrderCollectionMock = $this->getMockBuilder( \Magento\Sales\Model\ResourceModel\Order\Collection::class @@ -619,6 +623,163 @@ public function testCanCancelAllInvoiced() $this->item->expects($this->any()) ->method('getQtyToInvoice') ->willReturn(0); + $this->item->expects($this->any()) + ->method('getQtyRefunded') + ->willReturn(0); + $this->item->expects($this->any()) + ->method('getQtyInvoiced') + ->willReturn(1); + + $this->assertFalse($this->order->canCancel()); + } + + public function testCanCancelAllRefunded() + { + $paymentMock = $this->getMockBuilder(\Magento\Sales\Model\ResourceModel\Order\Payment::class) + ->disableOriginalConstructor() + ->setMethods(['isDeleted', 'canReviewPayment', 'canFetchTransactionInfo', '__wakeUp']) + ->getMock(); + $paymentMock->expects($this->any()) + ->method('canReviewPayment') + ->will($this->returnValue(false)); + $paymentMock->expects($this->any()) + ->method('canFetchTransactionInfo') + ->will($this->returnValue(false)); + $collectionMock = $this->createPartialMock( + \Magento\Sales\Model\ResourceModel\Order\Item\Collection::class, + ['getItems', 'setOrderFilter'] + ); + $this->orderItemCollectionFactoryMock->expects($this->any()) + ->method('create') + ->will($this->returnValue($collectionMock)); + $collectionMock->expects($this->any()) + ->method('setOrderFilter') + ->willReturnSelf(); + $this->preparePaymentMock($paymentMock); + + $this->prepareItemMock(0); + + $this->order->setActionFlag(\Magento\Sales\Model\Order::ACTION_FLAG_UNHOLD, false); + $this->order->setState(\Magento\Sales\Model\Order::STATE_NEW); + + $this->item->expects($this->any()) + ->method('isDeleted') + ->willReturn(false); + $this->item->expects($this->any()) + ->method('getQtyToInvoice') + ->willReturn(0); + $this->item->expects($this->any()) + ->method('getQtyRefunded') + ->willReturn(10); + $this->item->expects($this->any()) + ->method('getQtyInvoiced') + ->willReturn(10); + + $this->assertTrue($this->order->canCancel()); + } + + /** + * Test that order can be canceled if some items were partially invoiced with certain qty + * and then refunded for this qty. + * Sample: + * - ordered qty = 20 + * - invoiced = 10 + * - refunded = 10 + */ + public function testCanCancelPartiallyInvoicedAndRefunded() + { + $paymentMock = $this->getMockBuilder(\Magento\Sales\Model\ResourceModel\Order\Payment::class) + ->disableOriginalConstructor() + ->setMethods(['isDeleted', 'canReviewPayment', 'canFetchTransactionInfo', '__wakeUp']) + ->getMock(); + $paymentMock->expects($this->any()) + ->method('canReviewPayment') + ->will($this->returnValue(false)); + $paymentMock->expects($this->any()) + ->method('canFetchTransactionInfo') + ->will($this->returnValue(false)); + $collectionMock = $this->createPartialMock( + \Magento\Sales\Model\ResourceModel\Order\Item\Collection::class, + ['getItems', 'setOrderFilter'] + ); + $this->orderItemCollectionFactoryMock->expects($this->any()) + ->method('create') + ->will($this->returnValue($collectionMock)); + $collectionMock->expects($this->any()) + ->method('setOrderFilter') + ->willReturnSelf(); + $this->preparePaymentMock($paymentMock); + + $this->prepareItemMock(0); + + $this->order->setActionFlag(\Magento\Sales\Model\Order::ACTION_FLAG_UNHOLD, false); + $this->order->setState(\Magento\Sales\Model\Order::STATE_NEW); + + $this->item->expects($this->any()) + ->method('isDeleted') + ->willReturn(false); + $this->item->expects($this->any()) + ->method('getQtyToInvoice') + ->willReturn(10); + $this->item->expects($this->any()) + ->method('getQtyRefunded') + ->willReturn(10); + $this->item->expects($this->any()) + ->method('getQtyInvoiced') + ->willReturn(10); + + $this->assertTrue($this->order->canCancel()); + } + + /** + * Test that order CAN NOT be canceled if some items were partially invoiced with certain qty + * and then refunded for less than that qty. + * Sample: + * - ordered qty = 10 + * - invoiced = 10 + * - refunded = 5 + */ + public function testCanCancelPartiallyInvoicedAndNotFullyRefunded() + { + $paymentMock = $this->getMockBuilder(\Magento\Sales\Model\ResourceModel\Order\Payment::class) + ->disableOriginalConstructor() + ->setMethods(['isDeleted', 'canReviewPayment', 'canFetchTransactionInfo', '__wakeUp']) + ->getMock(); + $paymentMock->expects($this->any()) + ->method('canReviewPayment') + ->will($this->returnValue(false)); + $paymentMock->expects($this->any()) + ->method('canFetchTransactionInfo') + ->will($this->returnValue(false)); + $collectionMock = $this->createPartialMock( + \Magento\Sales\Model\ResourceModel\Order\Item\Collection::class, + ['getItems', 'setOrderFilter'] + ); + $this->orderItemCollectionFactoryMock->expects($this->any()) + ->method('create') + ->will($this->returnValue($collectionMock)); + $collectionMock->expects($this->any()) + ->method('setOrderFilter') + ->willReturnSelf(); + $this->preparePaymentMock($paymentMock); + + $this->prepareItemMock(0); + + $this->order->setActionFlag(\Magento\Sales\Model\Order::ACTION_FLAG_UNHOLD, false); + $this->order->setState(\Magento\Sales\Model\Order::STATE_NEW); + + $this->item->expects($this->any()) + ->method('isDeleted') + ->willReturn(false); + $this->item->expects($this->any()) + ->method('getQtyToInvoice') + ->willReturn(0); + $this->item->expects($this->any()) + ->method('getQtyRefunded') + ->willReturn(5); + $this->item->expects($this->any()) + ->method('getQtyInvoiced') + ->willReturn(10); $this->assertFalse($this->order->canCancel()); } diff --git a/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Widget/Chooser.php b/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Widget/Chooser.php index b4be904223aac..901866d52f73f 100644 --- a/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Widget/Chooser.php +++ b/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Widget/Chooser.php @@ -3,8 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\SalesRule\Block\Adminhtml\Promo\Widget; +/** + * Widget that allows to select a sales rule. + */ class Chooser extends \Magento\Backend\Block\Widget\Grid\Extended { /** @@ -87,7 +91,7 @@ public function prepareElementHtml(\Magento\Framework\Data\Form\Element\Abstract if ($element->getValue()) { $rule = $this->ruleFactory->create()->load((int)$element->getValue()); if ($rule->getId()) { - $chooser->setLabel($rule->getName()); + $chooser->setLabel($this->escapeHtml($rule->getName())); } } diff --git a/app/code/Magento/SalesRule/etc/db_schema.xml b/app/code/Magento/SalesRule/etc/db_schema.xml index 145ed0c96d95f..a81a0b0a50f23 100644 --- a/app/code/Magento/SalesRule/etc/db_schema.xml +++ b/app/code/Magento/SalesRule/etc/db_schema.xml @@ -199,7 +199,7 @@ <column name="attribute_id"/> </index> </table> - <table name="salesrule_coupon_aggregated" resource="default" engine="innodb" comment="Coupon Aggregated"> + <table name="salesrule_coupon_aggregated" resource="sales" engine="innodb" comment="Coupon Aggregated"> <column xsi:type="int" name="id" padding="10" unsigned="true" nullable="false" identity="true" comment="Id"/> <column xsi:type="date" name="period" nullable="false" comment="Period"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="true" identity="false" @@ -240,7 +240,7 @@ <column name="rule_name"/> </index> </table> - <table name="salesrule_coupon_aggregated_updated" resource="default" engine="innodb" + <table name="salesrule_coupon_aggregated_updated" resource="sales" engine="innodb" comment="Salesrule Coupon Aggregated Updated"> <column xsi:type="int" name="id" padding="10" unsigned="true" nullable="false" identity="true" comment="Id"/> <column xsi:type="date" name="period" nullable="false" comment="Period"/> diff --git a/app/code/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Plugin/Save.php b/app/code/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Plugin/Save.php index 383c97a166d34..72d27152d639a 100644 --- a/app/code/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Plugin/Save.php +++ b/app/code/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Plugin/Save.php @@ -16,6 +16,8 @@ class Save { /** + * Performs the conversion of the frontend input value. + * * @param Attribute\Save $subject * @param RequestInterface $request * @return array @@ -26,15 +28,6 @@ public function beforeDispatch(Attribute\Save $subject, RequestInterface $reques $data = $request->getPostValue(); if (isset($data['frontend_input'])) { - //Data is serialized to overcome issues caused by max_input_vars value if it's modification is unavailable. - //See subject controller code and comments for more info. - if (isset($data['serialized_swatch_values']) - && in_array($data['frontend_input'], ['swatch_visual', 'swatch_text']) - ) { - $data['serialized_options'] = $data['serialized_swatch_values']; - unset($data['serialized_swatch_values']); - } - switch ($data['frontend_input']) { case 'swatch_visual': $data[Swatch::SWATCH_INPUT_TYPE_KEY] = Swatch::SWATCH_INPUT_TYPE_VISUAL; diff --git a/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml b/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml index 08e24cfeb38fe..d7ee6dabce7ed 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml @@ -11,5 +11,6 @@ <entity name="visualSwatchAttribute" type="SwatchAttribute"> <data key="default_label" unique="suffix">VisualSwatchAttr</data> <data key="input_type">Visual Swatch</data> + <data key="attribute_code" unique="suffix">visual_swatch_attr</data> </entity> </entities> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateVisualSwatchWithNonValidOptionsTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateVisualSwatchWithNonValidOptionsTest.xml new file mode 100644 index 0000000000000..50dcfbf3da090 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateVisualSwatchWithNonValidOptionsTest.xml @@ -0,0 +1,79 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateVisualSwatchWithNonValidOptionsTest"> + <annotations> + <features value="Swatches"/> + <stories value="Create/configure swatches product attribute"/> + <title value="Admin should be able to create swatch product attribute"/> + <description value="Admin should be able to create swatch product attribute"/> + <severity value="BLOCKER"/> + <testCaseId value="MC-4140"/> + <group value="Swatches"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + </before> + <after> + <!-- Remove attribute --> + <actionGroup ref="navigateToCreatedProductAttribute" stepKey="navigateToAttribute"> + <argument name="ProductAttribute" value="visualSwatchAttribute"/> + </actionGroup> + <click selector="{{AttributePropertiesSection.DeleteAttribute}}" stepKey="deleteAttribute"/> + + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <amOnPage url="{{ProductAttributePage.url}}" stepKey="navigateToNewProductAttributePage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + + <!-- Set attribute properties --> + <fillField selector="{{AttributePropertiesSection.DefaultLabel}}" + userInput="{{visualSwatchAttribute.default_label}}" stepKey="fillDefaultLabel"/> + <selectOption selector="{{AttributePropertiesSection.InputType}}" + userInput="{{visualSwatchAttribute.input_type}}" stepKey="fillInputType"/> + + <!-- Set advanced attribute properties --> + <click selector="{{AdvancedAttributePropertiesSection.AdvancedAttributePropertiesSectionToggle}}" + stepKey="showAdvancedAttributePropertiesSection"/> + <waitForElementVisible selector="{{AdvancedAttributePropertiesSection.AttributeCode}}" + stepKey="waitForSlideOut"/> + <fillField selector="{{AdvancedAttributePropertiesSection.AttributeCode}}" + userInput="{{visualSwatchAttribute.attribute_code}}" + stepKey="fillAttributeCode"/> + + <!-- Add new swatch option without label --> + <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSwatch1"/> + <actionGroup ref="openSwatchMenuByIndex" stepKey="clickSwatch2"> + <argument name="index" value="0"/> + </actionGroup> + <click selector="{{AdminManageSwatchSection.nthChooseColor('1')}}" stepKey="clickChooseColor1"/> + <actionGroup ref="setColorPickerByHex" stepKey="fillHex1"> + <argument name="nthColorPicker" value="1"/> + <argument name="hexColor" value="ff0000"/> + </actionGroup> + + <!-- Save the new product attribute --> + <click selector="{{AttributePropertiesSection.Save}}" stepKey="clickSave1"/> + <waitForElementVisible selector="{{AdminProductMessagesSection.errorMessage}}" stepKey="waitForError"/> + + <!-- Fill options data --> + <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('0')}}" + userInput="red" stepKey="fillAdmin1"/> + + <!-- Go to Storefront Properties tab --> + <scrollToTopOfPage stepKey="scrollToTop"/> + <click selector="{{StorefrontPropertiesSection.StoreFrontPropertiesTab}}" stepKey="goToStorefrontPropertiesTab"/> + <waitForElementVisible selector="{{StorefrontPropertiesSection.PageTitle}}" stepKey="waitTabLoad"/> + + <!-- Save the new product attribute --> + <click selector="{{AttributePropertiesSection.Save}}" stepKey="clickSave2"/> + <waitForElementVisible selector="{{AdminProductMessagesSection.successMessage}}" + stepKey="waitForSuccessMessage"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js b/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js index 46083cd9fb4cc..0fc7b2ec48b5e 100644 --- a/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js +++ b/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js @@ -16,7 +16,8 @@ define([ 'use strict'; return function (optionConfig) { - var swatchProductAttributes = { + var activePanelClass = 'selected-type-options', + swatchProductAttributes = { frontendInput: $('#frontend_input'), isFilterable: $('#is_filterable'), isFilterableInSearch: $('#is_filterable_in_search'), @@ -338,6 +339,7 @@ define([ */ _showPanel: function (el) { el.closest('.fieldset').show(); + el.addClass(activePanelClass); this._render(el.attr('id')); }, @@ -347,6 +349,7 @@ define([ */ _hidePanel: function (el) { el.closest('.fieldset').hide(); + el.removeClass(activePanelClass); }, /** @@ -414,7 +417,11 @@ define([ }; $(function () { - var editForm = $('#edit_form'); + var editForm = $('#edit_form'), + swatchVisualPanel = $('#swatch-visual-options-panel'), + swatchTextPanel = $('#swatch-text-options-panel'), + tableBody = $(), + activePanel = $(); $('#frontend_input').bind('change', function () { swatchProductAttributes.bindAttributeInputType(); @@ -430,32 +437,37 @@ define([ .collapsable() .collapse('hide'); - editForm.on('submit', function () { - var activePanel, - swatchValues = [], - swatchVisualPanel = $('#swatch-visual-options-panel'), - swatchTextPanel = $('#swatch-text-options-panel'); + editForm.on('beforeSubmit', function () { + var swatchValues = [], + optionContainer; - activePanel = swatchTextPanel.is(':visible') ? swatchTextPanel : swatchVisualPanel; + activePanel = swatchTextPanel.hasClass(activePanelClass) ? swatchTextPanel : swatchVisualPanel; + optionContainer = activePanel.find('table tbody'); - activePanel - .find('table input') - .each(function () { - swatchValues.push(this.name + '=' + $(this).val()); - }); + if (activePanel.hasClass(activePanelClass)) { + optionContainer + .find('input') + .each(function () { + swatchValues.push(this.name + '=' + $(this).val()); + }); + + $('<input>') + .attr({ + type: 'hidden', + name: 'serialized_options' + }) + .val(JSON.stringify(swatchValues)) + .prependTo(editForm); + } - $('<input>') - .attr({ - type: 'hidden', - name: 'serialized_swatch_values' - }) - .val(JSON.stringify(swatchValues)) - .prependTo(editForm); - - [swatchVisualPanel, swatchTextPanel].forEach(function (el) { - $(el).find('table') - .replaceWith($('<div>').text($.mage.__('Sending swatch values as package.'))); - }); + tableBody = optionContainer.detach(); + }); + + editForm.on('afterValidate.error highlight.validate', function () { + if (activePanel.hasClass(activePanelClass)) { + activePanel.find('table').append(tableBody); + $('input[name="serialized_options"]').remove(); + } }); }); diff --git a/app/code/Magento/Tax/etc/db_schema.xml b/app/code/Magento/Tax/etc/db_schema.xml index 6cc4041f75a6d..6e83a64791969 100644 --- a/app/code/Magento/Tax/etc/db_schema.xml +++ b/app/code/Magento/Tax/etc/db_schema.xml @@ -139,7 +139,7 @@ <column name="store_id"/> </index> </table> - <table name="tax_order_aggregated_created" resource="default" engine="innodb" comment="Tax Order Aggregation"> + <table name="tax_order_aggregated_created" resource="sales" engine="innodb" comment="Tax Order Aggregation"> <column xsi:type="int" name="id" padding="10" unsigned="true" nullable="false" identity="true" comment="Id"/> <column xsi:type="date" name="period" comment="Period"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="true" identity="false" @@ -168,7 +168,7 @@ <column name="store_id"/> </index> </table> - <table name="tax_order_aggregated_updated" resource="default" engine="innodb" + <table name="tax_order_aggregated_updated" resource="sales" engine="innodb" comment="Tax Order Aggregated Updated"> <column xsi:type="int" name="id" padding="10" unsigned="true" nullable="false" identity="true" comment="Id"/> <column xsi:type="date" name="period" comment="Period"/> diff --git a/app/code/Magento/Theme/view/base/requirejs-config.js b/app/code/Magento/Theme/view/base/requirejs-config.js index d6006c21c8a15..52e9270952a95 100644 --- a/app/code/Magento/Theme/view/base/requirejs-config.js +++ b/app/code/Magento/Theme/view/base/requirejs-config.js @@ -64,3 +64,9 @@ var config = { } } }; + +require(['jquery'], function ($) { + 'use strict'; + + $.noConflict(); +}); diff --git a/app/code/Magento/Analytics/Test/Mftf/Metadata/user-meta.xml b/app/code/Magento/User/Test/Mftf/Metadata/user-meta.xml similarity index 100% rename from app/code/Magento/Analytics/Test/Mftf/Metadata/user-meta.xml rename to app/code/Magento/User/Test/Mftf/Metadata/user-meta.xml diff --git a/app/code/Magento/Analytics/Test/Mftf/Metadata/user_role-meta.xml b/app/code/Magento/User/Test/Mftf/Metadata/user_role-meta.xml similarity index 100% rename from app/code/Magento/Analytics/Test/Mftf/Metadata/user_role-meta.xml rename to app/code/Magento/User/Test/Mftf/Metadata/user_role-meta.xml diff --git a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php index e74db459808ce..93bddd09faef8 100644 --- a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php +++ b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php @@ -80,12 +80,14 @@ public function __construct( /** * Process and resolve input parameters + * * Return array with validated input params * or throw \Exception if at least one request entity params is not valid * * @return array * @throws \Magento\Framework\Exception\InputException if no value is provided for required parameters * @throws \Magento\Framework\Webapi\Exception + * @throws \Magento\Framework\Exception\AuthorizationException */ public function resolve() { @@ -120,7 +122,10 @@ public function getRoute() } /** - * Convert the input array from key-value format to a list of parameters suitable for the specified class / method. + * Resolve parameters for service + * + * Convert the input array from key-value format to a list of parameters + * suitable for the specified class / method. * * Instead of \Magento\Webapi\Controller\Rest\InputParamsResolver * we don't need to merge body params with url params and use only body params diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml index c9a0c0cc0f8bc..b91f796e6a18f 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml @@ -15,9 +15,7 @@ <description value="All products added to wishlist should be visible on any store. Even if product visibility was set to 'Not Visible Individually' for this store"/> <group value="wishlist"/> <severity value="AVERAGE"/> - <skip> - <issueId value="MAGETWO-93980"/> - </skip> + <testCaseId value="MAGETWO-95678"/> </annotations> <before> <createData entity="customStoreGroup" stepKey="storeGroup"/> diff --git a/dev/tests/acceptance/.gitignore b/dev/tests/acceptance/.gitignore index e3ef78df82b9f..87ce9d4ff35d1 100755 --- a/dev/tests/acceptance/.gitignore +++ b/dev/tests/acceptance/.gitignore @@ -7,4 +7,5 @@ tests/functional.suite.yml tests/functional/Magento/FunctionalTest/_generated vendor/* mftf.log +/.credentials.example /utils/ \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Framework/LICENSE.txt b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Framework/LICENSE.txt deleted file mode 100644 index 49525fd99da9c..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Framework/LICENSE.txt +++ /dev/null @@ -1,48 +0,0 @@ - -Open Software License ("OSL") v. 3.0 - -This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: - -Licensed under the Open Software License version 3.0 - - 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: - - 1. to reproduce the Original Work in copies, either alone or as part of a collective work; - - 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; - - 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; - - 4. to perform the Original Work publicly; and - - 5. to display the Original Work publicly. - - 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. - - 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. - - 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. - - 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). - - 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. - - 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. - - 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. - - 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). - - 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. - - 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. - - 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. - - 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. - - 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. - - 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Framework/LICENSE_AFL.txt b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Framework/LICENSE_AFL.txt deleted file mode 100644 index f39d641b18a19..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Framework/LICENSE_AFL.txt +++ /dev/null @@ -1,48 +0,0 @@ - -Academic Free License ("AFL") v. 3.0 - -This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: - -Licensed under the Academic Free License version 3.0 - - 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: - - 1. to reproduce the Original Work in copies, either alone or as part of a collective work; - - 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; - - 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; - - 4. to perform the Original Work publicly; and - - 5. to display the Original Work publicly. - - 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. - - 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. - - 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. - - 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). - - 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. - - 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. - - 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. - - 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). - - 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. - - 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. - - 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. - - 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. - - 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. - - 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Framework/README.md b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Framework/README.md deleted file mode 100644 index 7fbe454f709c2..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Framework/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Magento 2 Functional Tests - -The Functional Tests Module for **Magento_Framework**. diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/ActionGroup/TemplateActionGroupFile.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/ActionGroup/TemplateActionGroupFile.xml deleted file mode 100644 index d1bc251f26321..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/ActionGroup/TemplateActionGroupFile.xml +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> - <actionGroup name=""> - <!-- ADD TEST STEPS HERE --> - </actionGroup> -</actionGroups> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Data/TemplateDataFile.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Data/TemplateDataFile.xml deleted file mode 100644 index 6f13f04aef31d..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Data/TemplateDataFile.xml +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> - <entity name="" type=""> - <data key=""></data> - </entity> -</entities> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/LICENSE.txt b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/LICENSE.txt deleted file mode 100644 index 49525fd99da9c..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/LICENSE.txt +++ /dev/null @@ -1,48 +0,0 @@ - -Open Software License ("OSL") v. 3.0 - -This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: - -Licensed under the Open Software License version 3.0 - - 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: - - 1. to reproduce the Original Work in copies, either alone or as part of a collective work; - - 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; - - 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; - - 4. to perform the Original Work publicly; and - - 5. to display the Original Work publicly. - - 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. - - 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. - - 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. - - 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). - - 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. - - 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. - - 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. - - 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). - - 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. - - 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. - - 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. - - 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. - - 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. - - 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/LICENSE_AFL.txt b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/LICENSE_AFL.txt deleted file mode 100644 index f39d641b18a19..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/LICENSE_AFL.txt +++ /dev/null @@ -1,48 +0,0 @@ - -Academic Free License ("AFL") v. 3.0 - -This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: - -Licensed under the Academic Free License version 3.0 - - 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: - - 1. to reproduce the Original Work in copies, either alone or as part of a collective work; - - 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; - - 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; - - 4. to perform the Original Work publicly; and - - 5. to display the Original Work publicly. - - 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. - - 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. - - 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. - - 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). - - 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. - - 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. - - 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. - - 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). - - 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. - - 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. - - 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. - - 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. - - 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. - - 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Metadata/TemplateMetaFile.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Metadata/TemplateMetaFile.xml deleted file mode 100644 index 7293cea9a9b83..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Metadata/TemplateMetaFile.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> - <operation name="" dataType="" type="" auth="adminOauth" url="" method=""> - <contentType>application/json</contentType> - <param key=""></param> - <object dataType="" key=""> - <field key=""></field> - <array key=""> - <value></value> - </array> - </object> - <field key=""></field> - </operation> -</operations> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Page/TemplatePageFile.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Page/TemplatePageFile.xml deleted file mode 100644 index 27559a48bef06..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Page/TemplatePageFile.xml +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> - <page name="" url="" area="" module=""> - <section name=""/> - </page> -</pages> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/README.md b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/README.md deleted file mode 100644 index 986c2af6164e9..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Magento 2 Functional Tests - -The Functional Tests Module for **Magento_SampleTemplates** Module. diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Section/TemplateSectionFile.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Section/TemplateSectionFile.xml deleted file mode 100644 index 4c1cd7300cf2f..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Section/TemplateSectionFile.xml +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> - <section name=""> - <element name="" type="" selector=""/> - </section> -</sections> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Test/TemplateTestFile.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Test/TemplateTestFile.xml deleted file mode 100644 index d9a98d4baa6cb..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Test/TemplateTestFile.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="TemplateTestFile"> - <annotations> - <features value=""/> - <stories value=""/> - </annotations> - <before> - - </before> - <after> - - </after> - <!-- ADD TEST STEPS HERE --> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/ActionGroup/SampleActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/ActionGroup/SampleActionGroup.xml deleted file mode 100644 index 45ec5f3adf218..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/ActionGroup/SampleActionGroup.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> - <actionGroup name="SampleActionGroup"> - <arguments> - <argument name="person" defaultValue="SamplePerson"/> - </arguments> - <fillField selector="#foo" userInput="{{person.foo}}" stepKey="fillField1"/> - <fillField selector="#bar" userInput="{{person.bar}}" stepKey="fillField2"/> - </actionGroup> - <actionGroup name="ValidateSlideOutPanelField"> - <arguments> - <argument name="property" defaultValue=""/> - </arguments> - <see userInput="{{property.name}}" selector="{{ColumnSection.panelFieldLabel(property.section, property.fieldName, property.section, property.name)}}" stepKey="seePropertyLabel"/> - </actionGroup> -</actionGroups> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Data/SampleData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Data/SampleData.xml deleted file mode 100644 index 361f06a6ff333..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Data/SampleData.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> - <entity name="SamplePerson" type="samplePerson"> - <data key="foo">foo</data> - <data key="bar">bar</data> - <data key="lastName">Doe</data> - <data key="email" unique="prefix">.email@gmail.com</data> - </entity> - <entity name="OverrideDefaultPerson" type="samplePerson"> - <data key="foo">fizz</data> - <data key="bar">buzz</data> - </entity> - <entity name="AppearanceMinHeightProperty" type="min_height_property"> - <data key="name">Minimum Height</data> - <data key="section">appearance</data> - <data key="fieldName">min_height</data> - </entity> - <entity name="AssertThis" type="samplePerson"> - <data key="firstname">Well</data> - <data key="lastname">Done</data> - <data key="email" unique="prefix">.email@gmail.com</data> - </entity> -</entities> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/LICENSE.txt b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/LICENSE.txt deleted file mode 100644 index 49525fd99da9c..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/LICENSE.txt +++ /dev/null @@ -1,48 +0,0 @@ - -Open Software License ("OSL") v. 3.0 - -This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: - -Licensed under the Open Software License version 3.0 - - 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: - - 1. to reproduce the Original Work in copies, either alone or as part of a collective work; - - 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; - - 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; - - 4. to perform the Original Work publicly; and - - 5. to display the Original Work publicly. - - 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. - - 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. - - 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. - - 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). - - 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. - - 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. - - 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. - - 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). - - 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. - - 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. - - 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. - - 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. - - 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. - - 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/LICENSE_AFL.txt b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/LICENSE_AFL.txt deleted file mode 100644 index f39d641b18a19..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/LICENSE_AFL.txt +++ /dev/null @@ -1,48 +0,0 @@ - -Academic Free License ("AFL") v. 3.0 - -This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: - -Licensed under the Academic Free License version 3.0 - - 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: - - 1. to reproduce the Original Work in copies, either alone or as part of a collective work; - - 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; - - 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; - - 4. to perform the Original Work publicly; and - - 5. to display the Original Work publicly. - - 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. - - 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. - - 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. - - 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). - - 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. - - 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. - - 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. - - 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). - - 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. - - 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. - - 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. - - 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. - - 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. - - 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Page/SamplePage.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Page/SamplePage.xml deleted file mode 100644 index 6fd2a54c6014e..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Page/SamplePage.xml +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> - <page name="SamplePage" url="/{{var1}}/{{var2}}.html" area="storefront" module="SampleTests" parameterized="true"> - <section name="SampleSection"/> - </page> -</pages> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/README.md b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/README.md deleted file mode 100644 index f3340b205f1a3..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Magento 2 Functional Tests - -The Functional Tests Module for **Magento_SampleTests** Module. diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Section/SampleSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Section/SampleSection.xml deleted file mode 100644 index 6aa54d2fb9b83..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Section/SampleSection.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> - <section name="SampleSection"> - <element name="oneParamElement" type="button" selector="#element .{{var1}}" parameterized="true"/> - <element name="twoParamElement" type="button" selector="#{{var1}} .{{var2}}" parameterized="true"/> - <element name="threeParamElement" type="button" selector="#{{var1}}-{{var2}} .{{var3}}" parameterized="true"/> - <element name="timeoutElement" type="button" selector="#foo" timeout="30"/> - </section> - <section name="ColumnSection"> - <element name="panelFieldLabel" type="text" selector='//div[@data-index="{{arg1}}"]/descendant::div[@data-index="{{arg2}}"]/label | //div[@data-index="{{arg3}}"]/descendant::*[@class="admin__field-label"]/span[text()="{{arg4}}"]' parameterized="true" /> - </section> -</sections> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AdvancedSampleTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AdvancedSampleTest.xml deleted file mode 100644 index 4fead0e51497e..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AdvancedSampleTest.xml +++ /dev/null @@ -1,54 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="AdvancedSampleTest"> - <annotations> - <features value="SampleTests"/> - <title value=""/> - <description value=""/> - <severity value="CRITICAL"/> - <testCaseId value="#"/> - <skip> - <issueId value="SAMPLE"/> - </skip> - </annotations> - <before> - <createData entity="SamplePerson" stepKey="beforeData"/> - </before> - <after> - - </after> - - <!-- Create an entity that depends on another entity --> - <createData entity="_defaultCategory" stepKey="createCategory"/> - <createData entity="_defaultProduct" stepKey="createProduct"> - <requiredEntity createDataKey="createCategory"/> - </createData> - - <!-- Parameterized url --> - <amOnPage url="{{SamplePage.url('foo', SamplePerson.bar)}}" stepKey="amOnPage"/> - - <!-- Parameterized selector --> - <grabTextFrom selector="{{SampleSection.twoParamElement(SamplePerson.foo, 'bar')}}" stepKey="grabTextFrom"/> - - <!-- Element with a timeout --> - <click selector="{{SampleSection.timeoutElement}}" stepKey="click"/> - - <!-- ActionGroup --> - <actionGroup ref="SampleActionGroup" stepKey="actionGroup"> - <argument name="person" value="OverrideDefaultPerson"/> - </actionGroup> - - <actionGroup ref="ValidateSlideOutPanelField" stepKey="seeAppearanceMinHeightProperty"> - <argument name="property" value="AppearanceMinHeightProperty"/> - </actionGroup> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AssertsTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AssertsTest.xml deleted file mode 100644 index 200c52cab797d..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AssertsTest.xml +++ /dev/null @@ -1,79 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<!-- Test XML Example --> -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="AssertsTest"> - <annotations> - <features value="SampleTests"/> - <skip> - <issueId value="SAMPLE"/> - </skip> - </annotations> - - <createData entity="Simple_US_Customer" stepKey="createData2"/> - <amOnUrl url="https://www.yahoo.com" stepKey="amOnPage"/> - <waitForElementVisible stepKey="wait1" selector="#uh-logo" time="10"/> - <grabTextFrom selector="#uh-logo" stepKey="grabTextFrom1"/> - - <!-- asserts without variable replacement --> - <assertArrayHasKey stepKey="assertArrayHasKey" message="pass"> - <expectedResult type="string">apple</expectedResult> - <actualResult type="const">['orange' => 2, 'apple' => 1]</actualResult> - </assertArrayHasKey> - <assertEquals stepKey="assertEquals1" message="pass"> - <expectedResult type="variable">grabTextFrom1</expectedResult> - <actualResult type="string">Copyright © 2013-2017 Magento, Inc. All rights reserved.</actualResult> - </assertEquals> - <assertFalse stepKey="assertFalse1" message="pass"> - <actualResult type="bool">0</actualResult> - </assertFalse> - <assertGreaterOrEquals stepKey="assertGreaterOrEquals" message="pass"> - <expectedResult type="int">2</expectedResult> - <actualResult type="int">5</actualResult> - </assertGreaterOrEquals> - <assertElementContainsAttribute stepKey="assertElementContainsAttribute1" selector="#username" attribute="class"> - <expectedResult type="string">admin__control-text</expectedResult> - </assertElementContainsAttribute> - - <!-- string type that use created data --> - <assertStringStartsWith stepKey="assert1" message="fail"> - <expectedResult type="string">D</expectedResult> - <actualResult type="string">$$createData1.lastname$$, $$createData1.firstname$$</actualResult> - </assertStringStartsWith> - <assertStringStartsNotWith stepKey="assert2" message="pass"> - <expectedResult type="string">W</expectedResult> - <actualResult type="string">$createData2.firstname$, $createData2.lastname$</actualResult> - </assertStringStartsNotWith> - <assertEquals stepKey="assert5" message="pass"> - <expectedResult type="string">$$createData1.lastname$$</expectedResult> - <actualResult type="string">$$createData1.lastname$$</actualResult> - </assertEquals> - <assertElementContainsAttribute stepKey="assertElementContainsAttribute7" selector="#username" attribute="value"> - <expectedResult type="const">$createData2.firstname$</expectedResult> - </assertElementContainsAttribute> - - <!-- array type that use created data --> - <assertArraySubset stepKey="assert9" message="pass"> - <expectedResult type="array">[$$createData1.lastname$$, $$createData1.firstname$$]</expectedResult> - <actualResult type="array">[$$createData1.lastname$$, $$createData1.firstname$$, 1]</actualResult> - </assertArraySubset> - <assertArraySubset stepKey="assert10" message="pass"> - <expectedResult type="array">[$createData2.firstname$, $createData2.lastname$]</expectedResult> - <actualResult type="array">[$createData2.firstname$, $createData2.lastname$, 1]</actualResult> - </assertArraySubset> - <assertArrayHasKey stepKey="assert3" message="pass"> - <expectedResult type="string">lastname</expectedResult> - <actualResult type="array">['lastname' => $$createData1.lastname$$, 'firstname' => $$createData1.firstname$$]</actualResult> - </assertArrayHasKey> - <assertArrayHasKey stepKey="assert4" message="pass"> - <expectedResult type="string">lastname</expectedResult> - <actualResult type="array">['lastname' => $createData2.lastname$, 'firstname' => $createData2.firstname$]</actualResult> - </assertArrayHasKey> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateConfigurableProductByApiTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateConfigurableProductByApiTest.xml deleted file mode 100644 index d759d1ab27498..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateConfigurableProductByApiTest.xml +++ /dev/null @@ -1,77 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="CreateConfigurableProductByApiTest"> - <annotations> - <features value="SampleTests"/> - <stories value="Create a Configurable Product By API"/> - <skip> - <issueId value="SAMPLE"/> - </skip> - </annotations> - <before> - <createData stepKey="categoryHandle" entity="SimpleSubCategory" /> - <createData stepKey="baseConfigProductHandle" entity="BaseConfigurableProduct" > - <requiredEntity createDataKey="categoryHandle"/> - </createData> - <createData stepKey="productAttributeHandle" entity="productDropDownAttribute"/> - - <createData stepKey="productAttributeOption1Handle" entity="productAttributeOption1"> - <requiredEntity createDataKey="productAttributeHandle"/> - </createData> - <createData stepKey="productAttributeOption2Handle" entity="productAttributeOption2"> - <requiredEntity createDataKey="productAttributeHandle"/> - </createData> - - <createData stepKey="addToAttributeSetHandle" entity="AddToDefaultSet"> - <requiredEntity createDataKey="productAttributeHandle"/> - </createData> - - <getData stepKey="getAttributeOption1Handle" entity="ProductAttributeOptionGetter" index="1"> - <requiredEntity createDataKey="productAttributeHandle"/> - </getData> - <getData stepKey="getAttributeOption2Handle" entity="ProductAttributeOptionGetter" index="2"> - <requiredEntity createDataKey="productAttributeHandle"/> - </getData> - - <createData stepKey="childProductHandle1" entity="SimpleOne"> - <requiredEntity createDataKey="productAttributeHandle"/> - <requiredEntity createDataKey="getAttributeOption1Handle"/> - </createData> - <createData stepKey="childProductHandle2" entity="SimpleOne"> - <requiredEntity createDataKey="productAttributeHandle"/> - <requiredEntity createDataKey="getAttributeOption2Handle"/> - </createData> - - <createData stepKey="configProductOptionHandle" entity="ConfigurableProductTwoOptions"> - <requiredEntity createDataKey="baseConfigProductHandle"/> - <requiredEntity createDataKey="productAttributeHandle"/> - <requiredEntity createDataKey="getAttributeOption1Handle"/> - <requiredEntity createDataKey="getAttributeOption2Handle"/> - </createData> - - <createData stepKey="configProductHandle1" entity="ConfigurableProductAddChild"> - <requiredEntity createDataKey="childProductHandle1"/> - <requiredEntity createDataKey="baseConfigProductHandle"/> - </createData> - <createData stepKey="configProductHandle2" entity="ConfigurableProductAddChild"> - <requiredEntity createDataKey="childProductHandle2"/> - <requiredEntity createDataKey="baseConfigProductHandle"/> - </createData> - </before> - <after> - <deleteData stepKey="d2" createDataKey="childProductHandle1"/> - <deleteData stepKey="d3" createDataKey="childProductHandle2"/> - <deleteData stepKey="d7" createDataKey="baseConfigProductHandle"/> - <deleteData stepKey="d8" createDataKey="categoryHandle"/> - <deleteData stepKey="d6" createDataKey="productAttributeHandle"/> - </after> - </test> -</tests> \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateSalesRuleByApiTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateSalesRuleByApiTest.xml deleted file mode 100644 index 8cd83fd62067f..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateSalesRuleByApiTest.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="CreateSalesRuleByApiTest"> - <annotations> - <features value="SampleTests"/> - <stories value="Create a Sales Rule By API"/> - <skip> - <issueId value="SAMPLE"/> - </skip> - </annotations> - <before> - <createData stepKey="saleRule" entity="SimpleSalesRule" /> - </before> - <!--see stepKey="test" userInput="$$saleRule.store_labels[0][store_id]$$" selector="test"/--> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/MinimumTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/MinimumTest.xml deleted file mode 100644 index f8702eeaf5e0c..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/MinimumTest.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<!-- Test XML Example --> -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="MinimumTest"> - <annotations> - <features value="SampleTests"/> - <title value="Minimum Test"/> - <description value="Minimum Test"/> - <group value="example"/> - </annotations> - <after> - <seeInCurrentUrl url="/admin/admin/" stepKey="seeInCurrentUrl"/> - </after> - <amOnPage url="{{AdminLoginPage.url}}" stepKey="navigateToAdmin"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="{{_ENV.MAGENTO_ADMIN_USERNAME}}" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}" stepKey="fillPassword"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/PersistMultipleEntitiesTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/PersistMultipleEntitiesTest.xml deleted file mode 100644 index 6a196c2c32c02..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/PersistMultipleEntitiesTest.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="PersistMultipleEntitiesTest"> - <annotations> - <features value="SampleTests"/> - <skip> - <issueId value="SAMPLE"/> - </skip> - </annotations> - <before> - <createData entity="simplesubcategory" stepKey="simplecategory"/> - <createData entity="simpleproduct" stepKey="simpleproduct1"> - <requiredEntity createDataKey="simplecategory"/> - </createData> - <createData entity="simpleproduct" stepKey="simpleproduct2"> - <requiredEntity createDataKey="categoryLink"/> - </createData> - </before> - <after> - <deleteData createDataKey="simpleproduct1" stepKey="deleteProduct1"/> - <deleteData createDataKey="simpleproduct2" stepKey="deleteProduct2"/> - <deleteData createDataKey="simplecategory" stepKey="deleteCategory"/> - </after> - <amOnPage stepKey="s11" url="/$$simplecategory.name$$.html" /> - <waitForPageLoad stepKey="s33"/> - <see stepKey="s35" selector="{{StorefrontCategoryMainSection.productCount}}" userInput="2"/> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/SampleTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/SampleTest.xml deleted file mode 100644 index c107debc1aa62..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/SampleTest.xml +++ /dev/null @@ -1,298 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<!-- Test XML Example --> -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="PreAndPostHooksTest"> - <before> - <amOnUrl url="http://127.0.0.1:32772/admin/" stepKey="amOnPage"/> - <createData entity="CustomerEntity1" stepKey="createData1"/> - <createData entity="AssertThis" stepKey="createData2"/> - </before> - <after> - <amOnUrl url="http://127.0.0.1:32772/admin/admin/auth/logout" stepKey="amOnPage"/> - <deleteData createDataKey="createData1" stepKey="deleteData1"/> - <deleteData createDataKey="createData2" stepKey="deleteData2"/> - </after> - </test> - <test name="AllCodeceptionMethodsTest"> - <annotations> - <features value="SampleTests"/> - <title value="Create all Codeception methods"/> - <description value="Exercises the Generator to make sure it creates every Codeception method correctly."/> - <severity value="CRITICAL"/> - <testCaseId value="#"/> - </annotations> - <acceptPopup stepKey="acceptPopup"/> - <amOnPage url="/admin" stepKey="amOnPage"/> - <amOnSubdomain url="admin" stepKey="amOnSubdomain"/> - <amOnUrl url="http://www.google.com/" stepKey="amOnUrl"/> - <appendField userInput="More Words" selector=".stuff" stepKey="appendField"/> - <attachFile userInput="filename.php" selector="#stuff" stepKey="attachFile"/> - <cancelPopup stepKey="cancelPopup"/> - <checkOption selector="#checkbox" stepKey="checkOption"/> - <clearField selector="#field" stepKey="clearField"/> - <click selector="#button" userInput="Context" stepKey="click1"/> - <click selectorArray="['link' => 'Login']" stepKey="click2"/> - <click selectorArray="['link' => 'Login']" userInput="stuff" stepKey="click3"/> - <clickWithLeftButton selector="#clickHere" stepKey="clickWithLeftButton1" x="23" y="324"/> - <clickWithLeftButton selectorArray="['css' => '.checkout']" stepKey="clickWithLeftButton2" x="23" y="324"/> - <clickWithLeftButton stepKey="clickWithLeftButton3" x="23" y="324"/> - <clickWithRightButton selector="#clickHere" stepKey="clickWithRightButton1" x="23" y="324"/> - <clickWithRightButton selectorArray="['css' => '.checkout']" stepKey="clickWithRightButton2" x="23" y="324"/> - <clickWithRightButton stepKey="clickWithRightButton3" x="23" y="324"/> - <closeTab stepKey="closeTab"/> - <comment userInput="This is a Comment." stepKey="comment"/> - <createData entity="CustomerEntity1" stepKey="createData1"/> - <deleteData createDataKey="createData1" stepKey="deleteData1"/> - <dontSee userInput="Text" stepKey="dontSee1"/> - <dontSee userInput="Text" selector=".title" stepKey="dontSee2"/> - <dontSee userInput="Text" selectorArray="['css' => 'body h1']" stepKey="dontSee3"/> - <dontSeeCheckboxIsChecked selector="#checkbox" stepKey="dontSeeCheckboxIsChecked"/> - <dontSeeCookie userInput="cookieName" stepKey="dontSeeCookie1"/> - <dontSeeCookie userInput="cookieName" parameterArray="['domainName' => 'stuff']" stepKey="dontSeeCookie2"/> - <dontSeeCurrentUrlEquals url="/stuff" stepKey="dontSeeCurrentUrlEquals"/> - <dontSeeCurrentUrlMatches regex="~$/users/(\d+)~" stepKey="dontSeeCurrentUrlMatches"/> - <dontSeeElement selector=".error" stepKey="dontSeeElement1"/> - <dontSeeElement selector="input" parameterArray="['name' => 'login']" stepKey="dontSeeElement2"/> - <dontSeeElementInDOM selector="#stuff" stepKey="dontSeeElementInDOM1"/> - <dontSeeElementInDOM selector="#stuff" parameterArray="['name' => 'login']" stepKey="dontSeeElementInDOM2"/> - <dontSeeInCurrentUrl url="/users/" stepKey="dontSeeInCurrentUrl"/> - <dontSeeInField selector=".field" userInput="stuff" stepKey="dontSeeInField1"/> - <dontSeeInField selectorArray="['name' => 'search']" userInput="Comment Here" stepKey="dontSeeInField2"/> - <dontSeeInFormFields selector="form[name=myform]" parameterArray="['input1' => 'non-existent value', 'input2' => 'other non-existent value']" stepKey="dontSeeInFormFields"/> - <dontSeeInPageSource userInput="Stuff in Page Source" stepKey="dontSeeInPageSource"/> - <!--<dontSeeInSource html="<h1></h1>" stepKey="dontSeeInSource"/>--> - <dontSeeInTitle userInput="Title" stepKey="dontSeeInTitle"/> - <dontSeeLink userInput="Logout" stepKey="dontSeeLink1"/> - <dontSeeLink userInput="Checkout" url="/store/cart.php" stepKey="dontSeeLink2"/> - <dontSeeOptionIsSelected selector="#form .stuff" userInput="Option Name" stepKey="dontSeeOptionIsSelected"/> - <doubleClick selector="#click .here" stepKey="doubleClick"/> - <dragAndDrop selector1="#number1" selector2="#number2" stepKey="dragAndDrop"/> - <executeInSelenium function="function(\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) {$webdriver->get('http://google.com');}" stepKey="executeInSelenium"/> - <executeJS function="return $('#myField').val()" stepKey="executeJS"/> - <fillField selector="#field" userInput="stuff" stepKey="fillField1"/> - <fillField selectorArray="['name' => 'email']" userInput="stuff" stepKey="fillField2"/> - <grabAttributeFrom selector="#target" userInput="title" stepKey="grabAttributeFrom"/> - <grabCookie userInput="cookie" parameterArray="['domain' => 'www.google.com']" stepKey="grabCookie"/> - <grabFromCurrentUrl regex="~$/user/(\d+)/~" stepKey="grabFromCurrentUrl"/> - <grabMultiple selector="a" userInput="href" stepKey="grabMultiple"/> - <grabPageSource stepKey="grabPageSource1"/> - <grabTextFrom selector="h1" stepKey="grabTextFrom1"/> - <grabValueFrom selector=".form" stepKey="grabValueFrom1"/> - <grabValueFrom selectorArray="['name' => 'username']" stepKey="grabValueFrom2"/> - <loadSessionSnapshot userInput="stuff" stepKey="loadSessionSnapshot1"/> - <loadSessionSnapshot userInput="stuff" stepKey="loadSessionSnapshot2"/> - <makeScreenshot userInput="ScreenshotName" stepKey="makeScreenshot"/> - <maximizeWindow stepKey="maximizeWindow"/> - <moveBack stepKey="moveBack"/> - <moveForward stepKey="moveForward"/> - <moveMouseOver selector="#stuff" stepKey="moveMouseOver1"/> - <moveMouseOver selectorArray="['css' => '.checkout']" stepKey="moveMouseOver2"/> - <moveMouseOver x="5" y="5" stepKey="moveMouseOver3"/> - <moveMouseOver selector="#stuff" x="5" y="5" stepKey="moveMouseOver4"/> - <moveMouseOver selectorArray="['css' => '.checkout']" x="5" y="5" stepKey="moveMouseOver5"/> - <openNewTab stepKey="openNewTab"/> - <pauseExecution stepKey="pauseExecution"/> - <performOn selector=".rememberMe" function="function (WebDriver $I) { $I->see('Remember me next time'); $I->seeElement('#LoginForm_rememberMe'); $I->dontSee('Login'); }" stepKey="performOn1"/> - <performOn selector=".rememberMe" function="ActionSequence::build()->see('Warning')->see('Are you sure you want to delete this?')->click('Yes')" stepKey="performOn2"/> - <pressKey selector="#page" userInput="a" stepKey="pressKey1"/> - <pressKey selector="#page" parameterArray="[['ctrl','a'],'new']" stepKey="pressKey2"/> - <pressKey selector="#page" parameterArray="[['shift','111'],'1','x']" stepKey="pressKey3"/> - <pressKey selector="#page" parameterArray="[['ctrl', 'a'], \Facebook\WebDriver\WebDriverKeys::DELETE]" stepKey="pressKey4"/> - <!--pressKey selector="descendant-or-self::*[@id='page']" userInput="u" stepKey="pressKey5"/--> - <reloadPage stepKey="reloadPage"/> - <resetCookie userInput="cookie" stepKey="resetCookie1"/> - <resetCookie userInput="cookie" parameterArray="['domainName' => 'www.google.com']" stepKey="resetCookie2"/> - <resizeWindow width="800" height="600" stepKey="resizeWindow"/> - <saveSessionSnapshot userInput="stuff" stepKey="saveSessionSnapshot"/> - <scrollTo selector="#place" x="20" y="50" stepKey="scrollTo1"/> - <scrollTo selectorArray="['css' => '.checkout']" x="20" y="50" stepKey="scrollTo2"/> - <see userInput="Stuff" stepKey="see1"/> - <see userInput="More Stuff" selector=".stuff" stepKey="see2"/> - <see userInput="More More Stuff" selectorArray="['css' => 'body h1']" stepKey="see3"/> - <seeCheckboxIsChecked selector="#checkbox" stepKey="seeCheckboxIsChecked"/> - <seeCookie userInput="PHPSESSID" stepKey="seeCookie1"/> - <seeCookie userInput="PHPSESSID" parameterArray="['domainName' => 'www.google.com']" stepKey="seeCookie2"/> - <seeCurrentUrlEquals url="/" stepKey="seeCurrentUrlEquals"/> - <seeCurrentUrlMatches regex="~$/users/(\d+)~" stepKey="seeCurrentUrlMatches"/> - <seeElement selector=".error" stepKey="seeElement1"/> - <seeElement selectorArray="['css' => 'form input']" stepKey="seeElement2"/> - <seeElement selector=".error" parameterArray="['name' => 'login']" stepKey="seeElement3"/> - <seeElement selectorArray="['css' => 'form input']" parameterArray="['name' => 'login']" stepKey="seeElement4"/> - <seeElementInDOM selector="//form/input[type=hidden]" stepKey="seeElementInDOM1"/> - <seeElementInDOM selector="//form/input[type=hidden]" parameterArray="['name' => 'form']" stepKey="seeElementInDOM2"/> - <seeInCurrentUrl url="home" stepKey="seeInCurrentUrl1"/> - <seeInCurrentUrl url="/home/" stepKey="seeInCurrentUrl2"/> - <seeInField userInput="Stuff" selector="#field" stepKey="seeInField1"/> - <seeInField userInput="Stuff" selectorArray="['name' => 'search']" stepKey="seeInField2"/> - <seeInFormFields selector="form[name=myform]" parameterArray="['input1' => 'value','input2' => 'other value']" stepKey="seeInFormFields1"/> - <seeInFormFields selector=".form-class" parameterArray="[['multiselect' => ['value1','value2'],'checkbox[]]' => ['a checked value','another checked value',]]" stepKey="seeInFormFields2"/> - <!--<seeInPageSource html="<h1></h1>" stepKey="seeInPageSource"/>--> - <seeInPopup userInput="Yes in Popup" stepKey="seeInPopup"/> - <!--<seeInSource html="<h1></h1>" stepKey="seeInSource"/>--> - <seeInTitle userInput="In Title" stepKey="seeInTitle"/> - <seeLink userInput="Logout" stepKey="seeLink1"/> - <seeLink userInput="Logout" url="/logout" stepKey="seeLink2"/> - <seeNumberOfElements selector="tr" userInput="10" stepKey="seeNumberOfElements1"/> - <seeNumberOfElements selector="tr" userInput="[0, 10]" stepKey="seeNumberOfElements2"/> - <seeOptionIsSelected selector=".option" userInput="Visa" stepKey="seeOptionIsSelected"/> - <selectOption selector=".dropDown" userInput="Option Name" stepKey="selectOption1"/> - <selectOption selector="//form/select[@name=account]" parameterArray="['Windows','Linux']" stepKey="selectOption2"/> - <selectOption selector="Which OS do you use?" parameterArray="['text' => 'Windows']" stepKey="selectOption3"/> - <setCookie userInput="PHPSESSID" value="stuff" stepKey="setCookie1"/> - <setCookie userInput="PHPSESSID" value="stuff" parameterArray="['domainName' => 'www.google.com']" stepKey="setCookie2"/> - <submitForm selector="#my-form" parameterArray="['field' => ['value','another value',]]" button="#submit" stepKey="submitForm2"/> - <switchToIFrame stepKey="switchToIFrame1"/> - <switchToIFrame userInput="another_frame" stepKey="switchToIFrame2"/> - <switchToNextTab stepKey="switchToNextTab1"/> - <switchToNextTab userInput="2" stepKey="switchToNextTab2"/> - <switchToPreviousTab stepKey="switchToPreviewTab1"/> - <switchToPreviousTab userInput="1" stepKey="switchToPreviewTab2"/> - <switchToWindow stepKey="switchToWindow1"/> - <switchToWindow userInput="another_window" stepKey="switchToWindow2"/> - <typeInPopup userInput="Stuff for popup" stepKey="typeInPopup"/> - <uncheckOption selector="#option" stepKey="uncheckOption"/> - <unselectOption selector="#dropDown" userInput="Option" stepKey="unselectOption"/> - <wait time="15" stepKey="wait"/> - <waitForElement selector="#button" time="10" stepKey="waitForElement"/> - <waitForElementChange selector="#menu" function="function(\WebDriverElement $el) {return $el->isDisplayed();}" time="100" stepKey="waitForElementChange"/> - <waitForElementNotVisible selector="#a_thing .className" time="30" stepKey="waitForElementNotVisible"/> - <waitForElementVisible selector="#a_thing .className" time="15" stepKey="waitForElementVisible"/> - <waitForJS function="return $.active == 0;" time="30" stepKey="waitForJS"/> - <waitForText userInput="foo" time="30" stepKey="waitForText1"/> - <waitForText userInput="foo" selector=".title" time="30" stepKey="waitForText2"/> - </test> - <test name="AllCustomMethodsTest"> - <annotations> - <title value="Create all Custom methods"/> - <description value="Exercises the Generator to make sure it creates every Custom method correctly."/> - <severity value="CRITICAL"/> - <testCaseId value="#"/> - </annotations> - <assertElementContainsAttribute selector="#username" attribute="class" expectedValue="admin__control-text" stepKey="assertElementContainsAttribute1"/> - <assertElementContainsAttribute selector="#username" attribute="type" expectedValue="text" stepKey="assertElementContainsAttribute2"/> - <assertElementContainsAttribute selector="#username" attribute="name" expectedValue="login[username]" stepKey="assertElementContainsAttribute3"/> - <assertElementContainsAttribute selector="#username" attribute="autofocus" expectedValue="" stepKey="assertElementContainsAttribute4"/> - <assertElementContainsAttribute selector="#username" attribute="data-validate" expectedValue="{required:true}" stepKey="assertElementContainsAttribute5"/> - <assertElementContainsAttribute selector="#username" attribute="placeholder" expectedValue="user name" stepKey="assertElementContainsAttribute6"/> - <assertElementContainsAttribute selector="#username" attribute="autocomplete" expectedValue="off" stepKey="assertElementContainsAttribute7"/> - <assertElementContainsAttribute selector=".admin__menu-overlay" attribute="style" expectedValue="display: none;" stepKey="assertElementContainsAttribute8"/> - <assertElementContainsAttribute selector=".admin__menu-overlay" attribute="border" expectedValue="0" stepKey="assertElementContainsAttribute9"/> - <closeAdminNotification stepKey="closeAdminNotification1"/> - <searchAndMultiSelectOption selector="#stuff" parameterArray="['Item 1', 'Item 2']" stepKey="searchAndMultiSelect1"/> - <searchAndMultiSelectOption selector="#stuff" parameterArray="['Item 1', 'Item 2']" requiredAction="true" stepKey="searchAndMultiSelect2"/> - <waitForPageLoad stepKey="waitForPageLoad1"/> - <waitForPageLoad time="15" stepKey="waitForPageLoad2"/> - <waitForAjaxLoad stepKey="waitForAjax1"/> - <waitForAjaxLoad time="15" stepKey="waitForAjax2"/> - <dontSeeJsError stepKey="dontSeeJsError"/> - <formatMoney userInput="$300,000" stepKey="formatMoney1"/> - <formatMoney userInput="$300,000" locale="en_US.UTF-8" stepKey="formatMoney2"/> - <mSetLocale userInput="300" locale="en_US.UTF-8" stepKey="mSetLocale1"/> - <mResetLocale stepKey="mResetLocale1"/> - <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear1"/> - <scrollToTopOfPage stepKey="scrollToTopOfPage"/> - <parseFloat userInput="300,000.2325" stepKey="parseFloat1"/> - <magentoCLI stepKey="enableMaintenance1" command="maintenance:enable"/> - </test> - <test name="AllVariableMethodsTest"> - <annotations> - <features value="SampleTests"/> - <title value="Create all Methods that support Variables."/> - <description value="Exercises the Generator to make sure it creates every Method that supports a Variable."/> - <severity value="CRITICAL"/> - <testCaseId value="#"/> - </annotations> - <grabFromCurrentUrl stepKey="grabFromCurrentUrl1"/> - <amOnPage url="{$randomStuff}" stepKey="amOnPage1"/> - <amOnSubdomain url="{$randomStuff}" stepKey="amOnSubdomain1"/> - <amOnUrl url="{$randomStuff}" stepKey="amOnUrl1"/> - <appendField userInput="{$randomStuff}" selector="#randomField" stepKey="appendField1"/> - <attachFile userInput="{$randomStuff}" selector="#filePathField" stepKey="attachFile1"/> - <click userInput="{$randomStuff}" stepKey="click1"/> - <dontSee userInput="{$randomStuff}" stepKey="dontSee1"/> - <dontSeeCookie userInput="{$randomStuff}" stepKey="dontSeeCookie1"/> - <dontSeeCurrentUrlEquals url="{$randomStuff}" stepKey="dontSeeCurrentUrlEquals1"/> - <dontSeeCurrentUrlMatches regex="{$randomStuff}" stepKey="dontSeeCurrentUrlMatches1"/> - <dontSeeInCurrentUrl url="{$randomStuff}" stepKey="dontSeeInCurrentUrl1"/> - <dontSeeInField selector="#stuff" userInput="{$randomStuff}" stepKey="dontSeeInField1"/> - <dontSeeInPageSource userInput="{$randomStuff}" stepKey="dontSeeInPageSource1"/> - <dontSeeInTitle userInput="{$randomStuff}" stepKey="dontSeeInTitle1"/> - <dontSeeLink userInput="{$randomStuff}" stepKey="dontSeeLink1"/> - <dontSeeOptionIsSelected selector="#dropdown" userInput="{$randomStuff}" stepKey="dontSeeOptionIsSelected1"/> - <fillField userInput="{$randomStuff}" selector="#field" stepKey="fillField1"/> - <grabAttributeFrom selector="#stuff" userInput="{$randomStuff}" stepKey="grabAttributeFrom1"/> - <grabCookie userInput="{$randomStuff}" stepKey="grabValueFrom1"/> - <grabFromCurrentUrl regex="{$randomStuff}" stepKey="grabFromCurrentUrl"/> - <grabMultiple selector="a" userInput="{$randomStuff}" stepKey="grabMultiple1"/> - <loadSessionSnapshot userInput="{$randomStuff}" stepKey="loadSessionSnapshot"/> - <pressKey selector="a" userInput="{$randomStuff}" stepKey="pressKey1"/> - <saveSessionSnapshot userInput="{$randomStuff}" stepKey="saveSessionSnapshot1"/> - <see userInput="{$randomStuff}" stepKey="see1"/> - <seeCookie userInput="{$randomStuff}" stepKey="seeCookie1"/> - <seeCurrentUrlEquals url="{$randomStuff}" stepKey="seeCurrentUrlEquals1"/> - <seeCurrentUrlMatches regex="{$randomStuff}" stepKey="seeCurrentUrlMatches1"/> - <seeInCurrentUrl url="{$randomStuff}" stepKey="seeInCurrentUrl1"/> - <seeInField selector="a" userInput="{$randomStuff}" stepKey="seeInField1"/> - <seeInPopup userInput="{$randomStuff}" stepKey="seeInPopup"/> - <seeInTitle userInput="{$randomStuff}" stepKey="seeInTitle1"/> - <seeLink userInput="{$randomStuff}" stepKey="seeLink1"/> - <seeNumberOfElements selector="#stuff" userInput="{$randomStuff}" stepKey="seeNumberOfElements1"/> - <seeOptionIsSelected selector="#stuff" userInput="{$randomStuff}" stepKey="seeOptionIsSelected1"/> - <selectOption selector="#stuff" userInput="{$randomStuff}" stepKey="selectOption1"/> - <switchToIFrame userInput="{$randomStuff}" stepKey="switchToIFrame1"/> - <switchToNextTab userInput="{$randomStuff}" stepKey="switchToNextTab2"/> - <switchToPreviousTab userInput="{$randomStuff}" stepKey="switchToPreviousTab1"/> - <switchToNextTab userInput="{$randomStuff}" stepKey="switchToNextTab3"/> - <switchToWindow userInput="{$randomStuff}" stepKey="switchToWindow1"/> - <typeInPopup userInput="{$randomStuff}" stepKey="typeInPopup"/> - <unselectOption selector="#option" userInput="{$randomStuff}" stepKey="unselectOption1"/> - <waitForText userInput="{$randomStuff}" time="5" stepKey="waitForText1"/> - </test> - <test name="AllReplacementTest"> - <annotations> - <features value="SampleTests"/> - <title value="Exercise reference replacement."/> - <description value="Exercises {{foo}}, $foo$, and $$foo$$ replacement."/> - <severity value="CRITICAL"/> - <testCaseId value="#"/> - </annotations> - - <createData entity="CustomerEntity1" stepKey="testScopeData"/> - <createData entity="AssertThis" stepKey="testScopeData2"/> - - <!-- parameterized url that uses literal params --> - <amOnPage url="{{SamplePage.url('success','success2')}}" stepKey="a0"/> - - <!-- url referencing data that was created in this <test> --> - <amOnPage url="$testScopeData.firstname$.html" stepKey="a1"/> - - <!-- url referencing data that was created in a <before> --> - <amOnPage url="$$createData1.firstname$$.html" stepKey="a2"/> - - <!-- parameterized url that uses created data params --> - <amOnPage url="{{SamplePage.url($testScopeData.firstname$,$testScopeData.lastname$)}}" stepKey="a3"/> - <amOnPage url="{{SamplePage.url($$createData1.firstname$$,$$createData1.lastname$$)}}" stepKey="a4"/> - - <!-- parameterized selector that uses literal params --> - <click selector="{{SampleSection.oneParamElement('success')}}" stepKey="c1"/> - <click selector="{{SampleSection.twoParamElement('success','success2')}}" stepKey="c2"/> - - <!-- parameterized selector with literal, static data, and created data --> - <click selector="{{SampleSection.threeParamElement('John', SamplePerson.lastname, $testScopeData.lastname$)}}" stepKey="c3"/> - - <!-- selector that uses created data --> - <click selector="#$testScopeData.firstname$ .$testScopeData.lastname$" stepKey="c4"/> - <click selector="#$$createData1.firstname$$ .$$createData1.lastname$$" stepKey="c5"/> - - <!-- userInput that uses created data --> - <fillField selector="#sample" userInput="Hello $testScopeData.firstname$ $testScopeData.lastname$" stepKey="f1"/> - <fillField selector="#sample" userInput="Hello $$createData1.firstname$$ $$createData1.lastname$$" stepKey="f2"/> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/SetPaymentConfigurationTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/SetPaymentConfigurationTest.xml deleted file mode 100644 index f908c5cb4572e..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/SetPaymentConfigurationTest.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="SetPaypalConfigurationTest"> - <annotations> - <features value="SampleTests"/> - <skip> - <issueId value="SAMPLE"/> - </skip> - </annotations> - <createData entity="SamplePaypalConfig" stepKey="createSamplePaypalConfig"/> - <createData entity="DefaultPayPalConfig" stepKey="restoreDefaultPaypalConfig"/> - </test> - <test name="SetBraintreeConfigurationTest"> - <annotations> - <features value="SampleTests"/> - <skip> - <issueId value="SAMPLE"/> - </skip> - </annotations> - <createData entity="SampleBraintreeConfig" stepKey="createSampleBraintreeConfig"/> - <createData entity="DefaultBraintreeConfig" stepKey="restoreDefaultBraintreeConfig"/> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/UpdateSimpleProductByApiTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/UpdateSimpleProductByApiTest.xml deleted file mode 100644 index b18c24050d3a6..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/UpdateSimpleProductByApiTest.xml +++ /dev/null @@ -1,29 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="UpdateSimpleProductByApiTest"> - <annotations> - <features value="SampleTests"/> - <stories value="Update simple product by api test."/> - <skip> - <issueId value="SAMPLE"/> - </skip> - </annotations> - <before> - <createData stepKey="categoryHandle" entity="SimpleSubCategory"/> - <createData stepKey="productHandle" entity="SimpleProduct" > - <requiredEntity createDataKey="categoryHandle"/> - </createData> - <updateData stepKey="updateProduct" entity="NewSimpleProduct" createDataKey="productHandle"/> - </before> - <after> - <deleteData stepKey="delete" createDataKey="updateProduct"/> - </after> - </test> -</tests> \ No newline at end of file diff --git a/dev/tests/api-functional/testsuite/Magento/Customer/Api/AccountManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Customer/Api/AccountManagementTest.php index b0d1647d8b837..b2276d79f5ecf 100644 --- a/dev/tests/api-functional/testsuite/Magento/Customer/Api/AccountManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Customer/Api/AccountManagementTest.php @@ -213,6 +213,34 @@ public function testCreateCustomerWithErrors() } } + public function testCreateCustomerWithoutOptionalFields() + { + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'CreateAccount', + ], + ]; + + $customerDataArray = $this->dataObjectProcessor->buildOutputDataArray( + $this->customerHelper->createSampleCustomerDataObject(), + \Magento\Customer\Api\Data\CustomerInterface::class + ); + unset($customerDataArray['store_id']); + unset($customerDataArray['website_id']); + $requestData = ['customer' => $customerDataArray, 'password' => CustomerHelper::PASSWORD]; + try { + $customerData = $this->_webApiCall($serviceInfo, $requestData, null, 'all'); + $this->assertNotNull($customerData['id']); + } catch (\Exception $e) { + $this->fail('Customer should be created without optional fields.'); + } + } + /** * Test customer activation when it is required * diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditMemoCreateRefundTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditMemoCreateRefundTest.php index 8262a7e41543e..eae0e600434a6 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditMemoCreateRefundTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditMemoCreateRefundTest.php @@ -115,7 +115,8 @@ public function testInvoke() ); $this->assertNotEmpty($result); $order = $this->objectManager->get(OrderRepositoryInterface::class)->get($order->getId()); - $this->assertEquals(Order::STATE_CLOSED, $order->getState()); + //Totally refunded orders still can be processed and shipped. + $this->assertEquals(Order::STATE_PROCESSING, $order->getState()); } private function getItemsForRest($order) diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderInvoiceCreateTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderInvoiceCreateTest.php index 49e05832f2ef3..f6194db6d8ebb 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderInvoiceCreateTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderInvoiceCreateTest.php @@ -90,4 +90,96 @@ public function testInvoiceCreate() 'Failed asserting that Order status was changed' ); } + + /** + * Tests that MAGETWO-95346 was fixed for bundled products + * + * @expectedException \Exception + * @codingStandardsIgnoreStart + * @expectedExceptionMessageRegExp /Invoice Document Validation Error\(s\):(?:\n|\\n)The invoice can't be created without products. Add products and try again./ + * @codingStandardsIgnoreEnd + * @magentoApiDataFixture Magento/Sales/_files/order_with_bundle.php + */ + public function testOrderWithBundleInvoicedWithInvalidQuantitiesReturnsError() + { + /** @var \Magento\Sales\Model\Order $existingOrder */ + $existingOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class) + ->loadByIncrementId('100000001'); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => '/V1/order/' . $existingOrder->getId() . '/invoice', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => self::SERVICE_READ_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_READ_NAME . 'execute', + ], + ]; + + $requestData = [ + 'orderId' => $existingOrder->getId(), + 'notify' => true, + 'appendComment' => true, + 'items' => [ + [ + 'order_item_id' => -1, + 'qty' => 1 + ] + ], + 'comment' => [ + 'comment' => 'Test offline', + 'isVisibleOnFront' => 1, + ], + ]; + + $this->_webApiCall($serviceInfo, $requestData); + } + + /** + * Tests that MAGETWO-95346 was fixed for configurable products + * + * @expectedException \Exception + * @codingStandardsIgnoreStart + * @expectedExceptionMessageRegExp /Invoice Document Validation Error\(s\):(?:\n|\\n)The invoice can't be created without products. Add products and try again./ + * @codingStandardsIgnoreEnd + * @magentoApiDataFixture Magento/Sales/_files/order_configurable_product.php + */ + public function testOrderWithConfigurableProductInvoicedWithInvalidQuantitiesReturnsError() + { + /** @var \Magento\Sales\Model\Order $existingOrder */ + $existingOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class) + ->loadByIncrementId('100000001'); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => '/V1/order/' . $existingOrder->getId() . '/invoice', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => self::SERVICE_READ_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_READ_NAME . 'execute', + ], + ]; + + $requestData = [ + 'orderId' => $existingOrder->getId(), + 'notify' => true, + 'appendComment' => true, + 'items' => [ + [ + 'order_item_id' => -1, + 'qty' => 1 + ] + ], + 'comment' => [ + 'comment' => 'Test offline', + 'isVisibleOnFront' => 1, + ], + ]; + + $this->_webApiCall($serviceInfo, $requestData); + } } diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/RefundOrderTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/RefundOrderTest.php index 12cbe1ca0e5e2..aacda763ca2aa 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/RefundOrderTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/RefundOrderTest.php @@ -86,10 +86,11 @@ public function testShortRequest() 'Failed asserting that proper shipping amount of the Order was refunded' ); - $this->assertNotEquals( + //Totally refunded orders can be processed. + $this->assertEquals( $existingOrder->getStatus(), $updatedOrder->getStatus(), - 'Failed asserting that order status was changed' + 'Failed asserting that order status has not changed' ); } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { $this->fail('Failed asserting that Creditmemo was created'); diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipOrderTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipOrderTest.php index 1d854331731ab..e2a156cc6fbfc 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipOrderTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipOrderTest.php @@ -25,7 +25,6 @@ class ShipOrderTest extends \Magento\TestFramework\TestCase\WebapiAbstract protected function setUp() { - $this->markTestIncomplete('https://github.com/magento-engcom/msi/issues/1335'); $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); $this->shipmentRepository = $this->objectManager->get( @@ -38,6 +37,7 @@ protected function setUp() */ public function testConfigurableShipOrder() { + $this->markTestIncomplete('https://github.com/magento-engcom/msi/issues/1335'); $productsQuantity = 1; /** @var \Magento\Sales\Model\Order $existingOrder */ @@ -132,6 +132,39 @@ public function testShipOrder() ); } + /** + * Tests that not providing a tracking number produces the correct error. See MAGETWO-95429 + * @expectedException \Exception + * @codingStandardsIgnoreStart + * @expectedExceptionMessageRegExp /Shipment Document Validation Error\(s\):(?:\n|\\n)Please enter a tracking number./ + * @codingStandardsIgnoreEnd + * @magentoApiDataFixture Magento/Sales/_files/order_new.php + */ + public function testShipOrderWithoutTrackingNumberReturnsError() + { + $this->_markTestAsRestOnly('SOAP requires an tracking number to be provided so this case is not possible.'); + + /** @var \Magento\Sales\Model\Order $existingOrder */ + $existingOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class) + ->loadByIncrementId('100000001'); + + $requestData = [ + 'orderId' => $existingOrder->getId(), + 'comment' => [ + 'comment' => 'Test Comment', + 'is_visible_on_front' => 1, + ], + 'tracks' => [ + [ + 'title' => 'Simple shipment track', + 'carrier_code' => 'UPS' + ] + ] + ]; + + $this->_webApiCall($this->getServiceInfo($existingOrder), $requestData); + } + /** * @magentoApiDataFixture Magento/Bundle/_files/order_with_bundle_shipped_separately.php */ diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductAttribute/Curl.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductAttribute/Curl.php index d78095f05fe45..57d775cdeb736 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductAttribute/Curl.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductAttribute/Curl.php @@ -134,11 +134,72 @@ public function persist(FixtureInterface $fixture = null) } /** + * Additional data handling. + * * @param array $data * @return array */ protected function changeStructureOfTheData(array $data) { + $serializedOptions = $this->getSerializeOptions($data, ['option']); + if ($serializedOptions) { + $data['serialized_options'] = $serializedOptions; + unset($data['option']); + } + return $data; } + + /** + * Provides serialized product attribute options. + * + * @param array $data + * @param array $optionKeys + * @return array + */ + protected function getSerializeOptions(array $data, array $optionKeys): string + { + $options = []; + foreach ($optionKeys as $optionKey) { + if (!empty($data[$optionKey])) { + $options = array_merge( + $options, + $this->getEncodedOptions([$optionKey => $data[$optionKey]]) + ); + } + } + + return json_encode($options); + } + + /** + * Provides encoded attribute values. + * + * @param array $data + * @return array + */ + private function getEncodedOptions(array $data): array + { + $optionsData = []; + $iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($data)); + foreach ($iterator as $value) { + $depth = $iterator->getDepth(); + $option = ''; + + $level = 0; + $option .= $iterator->getSubIterator($level)->key(); + $level++; + + while ($level <= $depth) { + $option .= '[' . $iterator->getSubIterator($level)->key() . ']'; + $level++; + } + + $option .= '=' . $value; + + $optionsData[] = $option; + } + + return $optionsData; + } } diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderCancelMassActionFailMessage.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderCancelMassActionPartialFailMessage.php similarity index 59% rename from dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderCancelMassActionFailMessage.php rename to dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderCancelMassActionPartialFailMessage.php index fffb0746d4967..ecc9af83580f3 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderCancelMassActionFailMessage.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderCancelMassActionPartialFailMessage.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Sales\Test\Constraint; @@ -10,15 +11,20 @@ use Magento\Mtf\Constraint\AbstractConstraint; /** - * Class AssertOrderCancelMassActionFailMessage + * Class AssertOrderCancelAndSuccessMassActionFailMessage * Assert cancel fail message is displayed on order index page */ -class AssertOrderCancelMassActionFailMessage extends AbstractConstraint +class AssertOrderCancelMassActionPartialFailMessage extends AbstractConstraint { + /** + * Message displayed after cancel order from archive + */ + const SUCCESS_MESSAGE = 'We canceled 1 order(s).'; + /** * Text value to be checked */ - const FAIL_CANCEL_MESSAGE = 'You cannot cancel the order(s).'; + const FAIL_CANCEL_MESSAGE = '1 order(s) cannot be canceled.'; /** * Assert cancel fail message is displayed on order index page @@ -32,6 +38,10 @@ public function processAssert(OrderIndex $orderIndex) self::FAIL_CANCEL_MESSAGE, $orderIndex->getMessagesBlock()->getErrorMessage() ); + \PHPUnit\Framework\Assert::assertEquals( + self::SUCCESS_MESSAGE, + $orderIndex->getMessagesBlock()->getSuccessMessage() + ); } /** @@ -41,6 +51,6 @@ public function processAssert(OrderIndex $orderIndex) */ public function toString() { - return 'Cancel fail message is displayed on order index page.'; + return 'Cancel and success fail message is displayed on order index page.'; } } diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/MassOrdersUpdateTest.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/MassOrdersUpdateTest.xml index d4b9856c5e319..31d5c30d25fc7 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/MassOrdersUpdateTest.xml +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/MassOrdersUpdateTest.xml @@ -18,12 +18,12 @@ <constraint name="Magento\Sales\Test\Constraint\AssertOrdersInOrdersGrid" /> </variation> <variation name="MassOrdersUpdateTestVariation2"> - <data name="description" xsi:type="string">try to cancel orders in status Complete, Closed</data> + <data name="description" xsi:type="string">try to cancel orders in status Complete, Canceled</data> <data name="steps" xsi:type="string">invoice, shipment|invoice, credit memo</data> <data name="action" xsi:type="string">Cancel</data> <data name="ordersCount" xsi:type="string">2</data> - <data name="resultStatuses" xsi:type="string">Complete,Closed</data> - <constraint name="Magento\Sales\Test\Constraint\AssertOrderCancelMassActionFailMessage" /> + <data name="resultStatuses" xsi:type="string">Complete,Canceled</data> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderCancelMassActionPartialFailMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrdersInOrdersGrid" /> </variation> <variation name="MassOrdersUpdateTestVariation3"> @@ -31,8 +31,8 @@ <data name="steps" xsi:type="string">invoice|invoice, credit memo</data> <data name="action" xsi:type="string">Cancel</data> <data name="ordersCount" xsi:type="string">2</data> - <data name="resultStatuses" xsi:type="string">Processing,Closed</data> - <constraint name="Magento\Sales\Test\Constraint\AssertOrderCancelMassActionFailMessage" /> + <data name="resultStatuses" xsi:type="string">Processing,Canceled</data> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderCancelMassActionPartialFailMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrdersInOrdersGrid" /> </variation> <variation name="MassOrdersUpdateTestVariation4"> @@ -55,7 +55,7 @@ <constraint name="Magento\Sales\Test\Constraint\AssertOrdersInOrdersGrid" /> </variation> <variation name="MassOrdersUpdateTestVariation6"> - <data name="description" xsi:type="string">Release order in statuse On Hold</data> + <data name="description" xsi:type="string">Release order in status On Hold</data> <data name="steps" xsi:type="string">on hold</data> <data name="action" xsi:type="string">Unhold</data> <data name="ordersCount" xsi:type="string">1</data> diff --git a/dev/tests/functional/tests/app/Magento/Swatches/Test/Handler/SwatchProductAttribute/Curl.php b/dev/tests/functional/tests/app/Magento/Swatches/Test/Handler/SwatchProductAttribute/Curl.php index 083fa246c96ef..5e4a5cc45fe69 100644 --- a/dev/tests/functional/tests/app/Magento/Swatches/Test/Handler/SwatchProductAttribute/Curl.php +++ b/dev/tests/functional/tests/app/Magento/Swatches/Test/Handler/SwatchProductAttribute/Curl.php @@ -38,12 +38,14 @@ public function __construct(DataInterface $configuration, EventManagerInterface */ protected function changeStructureOfTheData(array $data) { - $data = parent::changeStructureOfTheData($data); $data['optiontext'] = $data['option']; $data['swatchtext'] = [ 'value' => $data['option']['value'] ]; + $data['serialized_options'] = $this->getSerializeOptions($data, ['optiontext', 'swatchtext']); unset($data['option']); + $data = parent::changeStructureOfTheData($data); + return $data; } } diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserRoleEntityTest.xml b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserRoleEntityTest.xml index 1f1530b08b28a..224ccbce10f96 100644 --- a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserRoleEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserRoleEntityTest.xml @@ -17,7 +17,6 @@ <constraint name="Magento\User\Test\Constraint\AssertUserSuccessLogin" /> </variation> <variation name="UpdateAdminUserRoleEntityTestVariation2"> - <data name="issue" xsi:type="string">MAGETWO-65658: [FT] User with restricted access can't log in to Admin in UpdateAdminUserEntityTest</data> <data name="user/dataset" xsi:type="string">default</data> <data name="role/data/resource_access" xsi:type="string">Custom</data> <data name="role/data/roles_resources" xsi:type="string">Sales</data> diff --git a/dev/tests/integration/testsuite/Magento/AsynchronousOperations/_files/bulk.php b/dev/tests/integration/testsuite/Magento/AsynchronousOperations/_files/bulk.php index c6c58d47e5af4..9e215667903d3 100644 --- a/dev/tests/integration/testsuite/Magento/AsynchronousOperations/_files/bulk.php +++ b/dev/tests/integration/testsuite/Magento/AsynchronousOperations/_files/bulk.php @@ -18,30 +18,35 @@ 'not_started' => [ 'uuid' => 'bulk-uuid-1', 'user_id' => 1, + 'user_type' => \Magento\Authorization\Model\UserContextInterface::USER_TYPE_ADMIN, 'description' => 'Bulk Description', 'operation_count' => 1, ], 'in_progress_success' => [ 'uuid' => 'bulk-uuid-2', 'user_id' => 1, + 'user_type' => \Magento\Authorization\Model\UserContextInterface::USER_TYPE_ADMIN, 'description' => 'Bulk Description', 'operation_count' => 3, ], 'in_progress_failed' => [ 'uuid' => 'bulk-uuid-3', 'user_id' => 1, + 'user_type' => \Magento\Authorization\Model\UserContextInterface::USER_TYPE_ADMIN, 'description' => 'Bulk Description', 'operation_count' => 2, ], 'finish_success' => [ 'uuid' => 'bulk-uuid-4', 'user_id' => 1, + 'user_type' => \Magento\Authorization\Model\UserContextInterface::USER_TYPE_ADMIN, 'description' => 'Bulk Description', 'operation_count' => 1, ], 'finish_failed' => [ 'uuid' => 'bulk-uuid-5', 'user_id' => 1, + 'user_type' => \Magento\Authorization\Model\UserContextInterface::USER_TYPE_ADMIN, 'description' => 'Bulk Description', 'operation_count' => 2, ], @@ -90,8 +95,8 @@ ], ]; -$bulkQuery = "INSERT INTO {$bulkTable} (`uuid`, `user_id`, `description`, `operation_count`)" - . " VALUES (:uuid, :user_id, :description, :operation_count);"; +$bulkQuery = "INSERT INTO {$bulkTable} (`uuid`, `user_id`, `user_type`, `description`, `operation_count`)" + . " VALUES (:uuid, :user_id, :user_type, :description, :operation_count);"; foreach ($bulks as $bulk) { $connection->query($bulkQuery, $bulk); } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php index 98eb9d3ad4cc4..1f70ad0f7df6b 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php @@ -391,7 +391,7 @@ protected function _getAttributeData() 'used_in_product_listing' => '1', 'used_for_sort_by' => '0', 'apply_to' => ['simple'], - 'frontend_label' => [\Magento\Store\Model\Store::DEFAULT_STORE_ID => 'string to translate'] + 'frontend_label' => [\Magento\Store\Model\Store::DEFAULT_STORE_ID => 'string to translate'], ]; } } diff --git a/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/ProductListTest.php b/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/ProductListTest.php index ba428dd00ddb4..2695948e78314 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/ProductListTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/ProductListTest.php @@ -131,4 +131,45 @@ private function performAssertions(int $count) "Product collection was not filtered according to the widget condition." ); } + + /** + * Check that collection returns correct result if use not contains operator for string attribute + * + * @magentoDbIsolation disabled + * @magentoDataFixture Magento/Catalog/_files/product_simple_xss.php + * @magentoDataFixture Magento/Catalog/_files/product_virtual.php + * @dataProvider createCollectionForSkuDataProvider + * @param string $encodedConditions + * @param string $sku + * @return void + */ + public function testCreateCollectionForSku($encodedConditions, $sku) + { + $this->block->setData('conditions_encoded', $encodedConditions); + $productCollection = $this->block->createCollection(); + $productCollection->load(); + $this->assertEquals( + 1, + $productCollection->count(), + "Product collection was not filtered according to the widget condition." + ); + $this->assertEquals($sku, $productCollection->getFirstItem()->getSku()); + } + + /** + * @return array + */ + public function createCollectionForSkuDataProvider() + { + return [ + 'contains' => ['^[`1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Combine`,' + . '`aggregator`:`all`,`value`:`1`,`new_child`:``^],' + . '`1--1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Product`,' + . '`attribute`:`sku`,`operator`:`^[^]`,`value`:`virtual`^]^]' , 'virtual-product'], + 'not contains' => ['^[`1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Combine`,' + . '`aggregator`:`all`,`value`:`1`,`new_child`:``^],' + . '`1--1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Product`,' + . '`attribute`:`sku`,`operator`:`!^[^]`,`value`:`virtual`^]^]', 'product-with-xss'] + ]; + } } diff --git a/dev/tests/integration/testsuite/Magento/Cms/Model/BlockTest.php b/dev/tests/integration/testsuite/Magento/Cms/Model/BlockTest.php index 16331ccde245c..3925975a0f70e 100644 --- a/dev/tests/integration/testsuite/Magento/Cms/Model/BlockTest.php +++ b/dev/tests/integration/testsuite/Magento/Cms/Model/BlockTest.php @@ -7,6 +7,7 @@ use Magento\Cms\Model\ResourceModel\Block; use Magento\Cms\Model\BlockFactory; +use Magento\Framework\App\ResourceConnection; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Stdlib\DateTime\DateTime; use Magento\Framework\Stdlib\DateTime\Timezone; @@ -82,23 +83,26 @@ public function testGetByIdentifier(array $blockData) */ public function testUpdateTime(array $blockData) { + /** + * @var $db \Magento\Framework\DB\Adapter\AdapterInterface + */ + $db = $this->objectManager->get(\Magento\Framework\App\ResourceConnection::class) + ->getConnection(ResourceConnection::DEFAULT_CONNECTION); # Prepare and save the temporary block - $beforeTimestamp = $this->objectManager->get(DateTime::class)->timestamp(); $tempBlock = $this->blockFactory->create(); $tempBlock->setData($blockData); + $beforeTimestamp = $db->fetchCol('SELECT UNIX_TIMESTAMP()')[0]; $this->blockResource->save($tempBlock); + $afterTimestamp = $db->fetchCol('SELECT UNIX_TIMESTAMP()')[0]; # Load previously created block and compare identifiers $storeId = reset($blockData['stores']); $block = $this->blockIdentifier->execute($blockData['identifier'], $storeId); - $afterTimestamp = $this->objectManager->get(DateTime::class)->timestamp(); $blockTimestamp = strtotime($block->getUpdateTime()); /* - * This test used to fail due to a race condition @see MAGETWO-87353 - * The DB time would be one second older than the check time. The new check allows the DB time - * to be between the test start time and right before the assertion. + * These checks prevent a race condition MAGETWO-87353 */ $this->assertGreaterThanOrEqual($beforeTimestamp, $blockTimestamp); $this->assertLessThanOrEqual($afterTimestamp, $blockTimestamp); diff --git a/dev/tests/integration/testsuite/Magento/Cms/Model/PageTest.php b/dev/tests/integration/testsuite/Magento/Cms/Model/PageTest.php index 42697e1b66b7f..83e7c678ffc51 100644 --- a/dev/tests/integration/testsuite/Magento/Cms/Model/PageTest.php +++ b/dev/tests/integration/testsuite/Magento/Cms/Model/PageTest.php @@ -6,6 +6,7 @@ namespace Magento\Cms\Model; use Magento\Cms\Api\PageRepositoryInterface; +use Magento\Framework\App\ResourceConnection; use Magento\Framework\Stdlib\DateTime\DateTime; /** @@ -79,13 +80,27 @@ public function testGenerateIdentifierFromTitle($data, $expectedIdentifier) public function testUpdateTime() { $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + + /** + * @var $db \Magento\Framework\DB\Adapter\AdapterInterface + */ + $db = $objectManager->get(\Magento\Framework\App\ResourceConnection::class) + ->getConnection(ResourceConnection::DEFAULT_CONNECTION); + /** @var \Magento\Cms\Model\Page $page */ $page = $objectManager->create(\Magento\Cms\Model\Page::class); $page->setData(['title' => 'Test', 'stores' => [1]]); + $beforeTimestamp = $db->fetchCol('SELECT UNIX_TIMESTAMP()')[0]; $page->save(); + $afterTimestamp = $db->fetchCol('SELECT UNIX_TIMESTAMP()')[0]; $page = $objectManager->get(PageRepositoryInterface::class)->getById($page->getId()); - $date = $objectManager->get(DateTime::class)->date(); - $this->assertEquals($date, $page->getUpdateTime()); + $pageTimestamp = strtotime($page->getUpdateTime()); + + /* + * These checks prevent a race condition MAGETWO-95534 + */ + $this->assertGreaterThanOrEqual($beforeTimestamp, $pageTimestamp); + $this->assertLessThanOrEqual($afterTimestamp, $pageTimestamp); } public function generateIdentifierFromTitleDataProvider() : array diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php index 921fa81fa6f23..ed40abd26c0b6 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php @@ -130,7 +130,6 @@ public function testCreatepasswordActionWithDirectLink() $customer->save(); $this->getRequest()->setParam('token', $token); - $this->getRequest()->setParam('id', $customer->getId()); $this->dispatch('customer/account/createPassword'); @@ -138,12 +137,13 @@ public function testCreatepasswordActionWithDirectLink() $this->assertEquals(302, $response->getHttpResponseCode()); $text = $response->getBody(); $this->assertFalse((bool)preg_match('/' . $token . '/m', $text)); - $this->assertRedirect($this->stringContains('customer/account/createpassword')); + $this->assertRedirect( + $this->stringContains('customer/account/createpassword') + ); - /** @var \Magento\Customer\Model\Session $customer */ - $session = Bootstrap::getObjectManager()->get(\Magento\Customer\Model\Session::class); + /** @var Session $customer */ + $session = Bootstrap::getObjectManager()->get(Session::class); $this->assertEquals($token, $session->getRpToken()); - $this->assertEquals($customer->getId(), $session->getRpCustomerId()); $this->assertNotContains($token, $response->getHeader('Location')->getFieldValue()); } @@ -419,6 +419,7 @@ public function testResetPasswordPostNoTokenAction() $this->getRequest() ->setParam('id', 1) ->setParam('token', '8ed8677e6c79e68b94e61658bd756ea5') + ->setMethod('POST') ->setPostValue([ 'password' => 'new-password', 'password_confirmation' => 'new-password', @@ -441,6 +442,7 @@ public function testResetPasswordPostAction() $this->getRequest() ->setQueryValue('id', 1) ->setQueryValue('token', '8ed8677e6c79e68b94e61658bd756ea5') + ->setMethod('POST') ->setPostValue([ 'password' => 'new-Password1', 'password_confirmation' => 'new-Password1', @@ -606,7 +608,7 @@ public function testMissingDataEditPostAction() $this->assertRedirect($this->stringContains('customer/account/edit/')); $this->assertSessionMessages( - $this->equalTo(['"Email" is not a valid email address.']), + $this->equalTo(['"Email" is not a valid email address.']), MessageInterface::TYPE_ERROR ); } @@ -637,7 +639,7 @@ public function testWrongPasswordEditPostAction() $this->assertRedirect($this->stringContains('customer/account/edit/')); // Not sure if its the most secure message. Not changing the behavior for now in the new AccountManagement APIs. $this->assertSessionMessages( - $this->equalTo(["The password doesn't match this account. Verify the password and try again."]), + $this->equalTo(["The password doesn't match this account. Verify the password and try again."]), MessageInterface::TYPE_ERROR ); } @@ -665,7 +667,7 @@ public function testWrongConfirmationEditPostAction() $this->assertRedirect($this->stringContains('customer/account/edit/')); $this->assertSessionMessages( - $this->equalTo(['Password confirmation doesn\'t match entered password.']), + $this->equalTo(['Password confirmation doesn't match entered password.']), MessageInterface::TYPE_ERROR ); } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ResetPasswordTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ResetPasswordTest.php index eaaba639d49a8..8fa77a7388be2 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ResetPasswordTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ResetPasswordTest.php @@ -43,6 +43,23 @@ public function testResetPasswordSuccess() $this->assertRedirect($this->stringStartsWith($this->baseControllerUrl . 'edit')); } + /** + * Checks reset password functionality cannot be performed with GET request + * + * @magentoConfigFixture current_store customer/password/limit_password_reset_requests_method 0 + * @magentoConfigFixture current_store customer/password/min_time_between_password_reset_requests 0 + * @magentoDataFixture Magento/Customer/_files/customer.php + */ + public function testResetPasswordWithGet() + { + $this->passwordResetRequestEventCreate( + \Magento\Security\Model\PasswordResetRequestEvent::CUSTOMER_PASSWORD_RESET_REQUEST + ); + $this->getRequest()->setPostValue(['customer_id' => '1'])->setMethod(HttpRequest::METHOD_GET); + $this->dispatch('backend/customer/index/resetPassword'); + $this->assertEquals('noroute', $this->getRequest()->getControllerName()); + } + /** * Checks reset password functionality with default restrictive min time between * password reset requests and customer reset request event. diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php index f5b6fdc93d32f..4810b6c28caef 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php @@ -357,6 +357,29 @@ public function testValidateResetPasswordLinkTokenNull() } } + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + */ + public function testValidateResetPasswordLinkTokenWithoutId() + { + $token = 'randomStr123'; + $this->setResetPasswordData($token, 'Y-m-d H:i:s'); + $this->assertTrue( + $this->accountManagement->validateResetPasswordLinkToken(null, $token) + ); + } + /** + * @magentoDataFixture Magento/Customer/_files/two_customers.php + * @expectedException \Magento\Framework\Exception\State\ExpiredException + */ + public function testValidateResetPasswordLinkTokenAmbiguous() + { + $token = 'randomStr123'; + $this->setResetPasswordData($token, 'Y-m-d H:i:s', 1); + $this->setResetPasswordData($token, 'Y-m-d H:i:s', 2); + $this->accountManagement->validateResetPasswordLinkToken(null, $token); + } + /** * @magentoAppArea frontend * @magentoDataFixture Magento/Customer/_files/customer.php @@ -512,6 +535,31 @@ public function testResetPasswordTokenInvalidUserEmail() } } + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + */ + public function testResetPasswordWithoutEmail() + { + $resetToken = 'lsdj579slkj5987slkj595lkj'; + $password = 'new_Password123'; + $this->setResetPasswordData($resetToken, 'Y-m-d H:i:s'); + $this->assertTrue( + $this->accountManagement->resetPassword(null, $resetToken, $password) + ); + } + /** + * @magentoDataFixture Magento/Customer/_files/two_customers.php + * @expectedException \Magento\Framework\Exception\State\ExpiredException + */ + public function testResetPasswordAmbiguousToken() + { + $resetToken = 'lsdj579slkj5987slkj595lkj'; + $password = 'new_Password123'; + $this->setResetPasswordData($resetToken, 'Y-m-d H:i:s', 1); + $this->setResetPasswordData($resetToken, 'Y-m-d H:i:s', 2); + $this->accountManagement->resetPassword(null, $resetToken, $password); + } + /** * @magentoAppArea frontend * @magentoAppIsolation enabled @@ -960,10 +1008,14 @@ public function testGetDefaultAddressesForNonExistentAddress() * * @param $resetToken * @param $date + * @param int $customerIdFromFixture Which customer to use. + * @throws \Exception */ - protected function setResetPasswordData($resetToken, $date) - { - $customerIdFromFixture = 1; + protected function setResetPasswordData( + $resetToken, + $date, + int $customerIdFromFixture = 1 + ) { /** @var \Magento\Customer\Model\Customer $customerModel */ $customerModel = $this->objectManager->create(\Magento\Customer\Model\Customer::class); $customerModel->load($customerIdFromFixture); diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Controller/ExpressTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Controller/ExpressTest.php index 3d30f8366598a..157999224d7b8 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Controller/ExpressTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Controller/ExpressTest.php @@ -140,12 +140,11 @@ public function testStartActionCustomerToQuote() /** * Test return action with configurable product. + * + * @magentoDataFixture Magento/Paypal/_files/quote_express_configurable.php */ public function testReturnAction() { - // Skipped due to MAGETWO-87333 - //@magentoDataFixture Magento/Paypal/_files/quote_express_configurable.php - $this->markTestSkipped('MAGETWO-87333'); $quote = $this->_objectManager->create(Quote::class); $quote->load('test_cart_with_configurable', 'reserved_order_id'); diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/IpnTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Model/IpnTest.php index 1a22ea947f85a..f2cd745e96d83 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/IpnTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/IpnTest.php @@ -64,7 +64,8 @@ public function testProcessIpnRequestFullRefund() $creditmemoItems = $order->getCreditmemosCollection()->getItems(); $creditmemo = current($creditmemoItems); - $this->assertEquals(Order::STATE_CLOSED, $order->getState()) ; + //Totally refunded orders still can be shipped + $this->assertEquals(Order::STATE_PROCESSING, $order->getState()) ; $this->assertEquals(1, count($creditmemoItems)); $this->assertEquals(Creditmemo::STATE_REFUNDED, $creditmemo->getState()); $this->assertEquals(10, $order->getSubtotalRefunded()); @@ -146,7 +147,8 @@ public function testProcessIpnRequestRestRefund() $creditmemoItems = $order->getCreditmemosCollection()->getItems(); - $this->assertEquals(Order::STATE_CLOSED, $order->getState()) ; + //Totally refunded orders still can be shipped + $this->assertEquals(Order::STATE_PROCESSING, $order->getState()) ; $this->assertEquals(1, count($creditmemoItems)); $this->assertEquals(10, $order->getSubtotalRefunded()); $this->assertEquals(10, $order->getBaseSubtotalRefunded()); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_bundle.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_bundle.php new file mode 100644 index 0000000000000..6b6d6d6e6ec04 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_bundle.php @@ -0,0 +1,84 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Sales\Api\Data\OrderItemInterface; +use Magento\Sales\Api\OrderItemRepositoryInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Item; +use Magento\TestFramework\ObjectManager; + +$objectManager = ObjectManager::getInstance(); + +require 'order.php'; +/** @var Order $order */ + +$orderItems = [ + [ + OrderItemInterface::PRODUCT_ID => 2, + OrderItemInterface::BASE_PRICE => 100, + OrderItemInterface::ORDER_ID => $order->getId(), + OrderItemInterface::QTY_ORDERED => 2, + OrderItemInterface::QTY_INVOICED => 2, + OrderItemInterface::PRICE => 100, + OrderItemInterface::ROW_TOTAL => 102, + OrderItemInterface::PRODUCT_TYPE => 'bundle', + 'product_options' => [ + 'product_calculations' => 0, + ], + 'children' => [ + [ + OrderItemInterface::PRODUCT_ID => 13, + OrderItemInterface::ORDER_ID => $order->getId(), + OrderItemInterface::QTY_ORDERED => 10, + OrderItemInterface::QTY_INVOICED => 10, + OrderItemInterface::BASE_PRICE => 90, + OrderItemInterface::PRICE => 90, + OrderItemInterface::ROW_TOTAL => 92, + OrderItemInterface::PRODUCT_TYPE => 'simple', + 'product_options' => [ + 'bundle_selection_attributes' => [ + 'qty' => 2, + ], + ], + ], + ], + ], +]; + +if (!function_exists('saveOrderItems')) { + /** + * Save Order Items. + * + * @param array $orderItems + * @param Item|null $parentOrderItem [optional] + * @return void + */ + function saveOrderItems(array $orderItems, Order $order, $parentOrderItem = null) + { + $objectManager = ObjectManager::getInstance(); + + foreach ($orderItems as $orderItemData) { + /** @var Item $orderItem */ + $orderItem = $objectManager->create(Item::class); + if (null !== $parentOrderItem) { + $orderItemData['parent_item'] = $parentOrderItem; + } + $orderItem->setData($orderItemData); + $order->addItem($orderItem); + + if (isset($orderItemData['children'])) { + saveOrderItems($orderItemData['children'], $order, $orderItem); + } + } + } +} + +saveOrderItems($orderItems, $order); +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->get(OrderRepositoryInterface::class); +$order = $orderRepository->save($order); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_bundle_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_bundle_rollback.php new file mode 100644 index 0000000000000..dd52deab825cb --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_bundle_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require 'order_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php index 00f1b1f451815..70fa11fc78c87 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php @@ -36,15 +36,14 @@ protected function setUp() * @magentoAppIsolation enabled * @param int $categoryId * @param bool $expectedResult + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/quote_with_configurable_product.php + * @magentoDataFixture Magento/SalesRule/_files/rules_category.php * @dataProvider validateProductConditionDataProvider * @magentoDbIsolation disabled */ public function testValidateCategorySalesRuleIncludesChildren($categoryId, $expectedResult) { - //* @magentoDataFixture Magento/ConfigurableProduct/_files/quote_with_configurable_product.php - //* @magentoDataFixture Magento/SalesRule/_files/rules_category.php - $this->markTestSkipped('MAGETWO-87436'); - // Load the quote that contains a child of a configurable product /** @var \Magento\Quote\Model\Quote $quote */ $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class) diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/AttributeTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/AttributeTest.php index e222e6fda48d7..5b7a80bb38dfd 100644 --- a/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/AttributeTest.php +++ b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/AttributeTest.php @@ -65,12 +65,14 @@ private function getSwatchVisualDataSet(int $optionsCount) : array $optionsData []= "optionvisual[value][option_{$i}][1]={$expectedOptionLabelOnStoreView}"; $optionsData []= "optionvisual[delete][option_{$i}]="; } - $optionsData []= "visual_swatch_validation="; - $optionsData []= "visual_swatch_validation_unique="; return [ 'attribute_data' => array_merge_recursive( [ - 'serialized_swatch_values' => json_encode($optionsData), + 'serialized_options' => json_encode($optionsData), + ], + [ + 'visual_swatch_validation' => '', + 'visual_swatch_validation_unique' => '', ], $this->getAttributePreset(), [ @@ -103,12 +105,14 @@ private function getSwatchTextDataSet(int $optionsCount) : array $optionsData []= "optiontext[value][option_{$i}][1]={$expectedOptionLabelOnStoreView}"; $optionsData []= "optiontext[delete][option_{$i}]="; } - $optionsData []= "text_swatch_validation="; - $optionsData []= "text_swatch_validation_unique="; return [ 'attribute_data' => array_merge_recursive( [ - 'serialized_swatch_values' => json_encode($optionsData), + 'serialized_options' => json_encode($optionsData), + ], + [ + 'text_swatch_validation' => '', + 'text_swatch_validation_unique' => '', ], $this->getAttributePreset(), [ @@ -128,7 +132,7 @@ private function getSwatchTextDataSet(int $optionsCount) : array private function getAttributePreset() : array { return [ - 'serialized_options' => '[]', + 'form_key' => 'XxtpPYjm2YPYUlAt', 'frontend_label' => [ 0 => 'asdasd', 1 => '', diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/data-storage.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/data-storage.test.js index fdd0b70997f35..0ab6b5fdf2b99 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/data-storage.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/data-storage.test.js @@ -49,6 +49,7 @@ define([ injector.clean(); injector.remove(); } catch (e) {} + window.localStorage.clear(); }); describe('Magento_Catalog/js/product/storage/data-storage', function () { @@ -88,18 +89,20 @@ define([ }; }); + afterEach(function () { + window.localStorage.clear(); + }); + it('check calls "dataHandler" method with data', function () { var data = { property: 'value' }; obj.dataHandler(data); - expect(obj.localStorage.set).toHaveBeenCalledWith(data); - expect(obj.localStorage.removeAll).not.toHaveBeenCalled(); + expect(window.localStorage.getItem(obj.namespace)).toBe(JSON.stringify(data)); }); it('check calls "dataHandler" method with empty data', function () { obj.dataHandler({}); - expect(obj.localStorage.set).not.toHaveBeenCalled(); expect(obj.localStorage.removeAll).toHaveBeenCalled(); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage.test.js index c394a2463f5e2..624b159163bcb 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage.test.js @@ -27,6 +27,7 @@ define([ injector.clean(); injector.remove(); } catch (e) {} + window.localStorage.clear(); }); describe('Magento_Catalog/js/product/storage/ids-storage', function () { @@ -40,13 +41,6 @@ define([ expect(obj.localStorage.get).toHaveBeenCalled(); }); }); - describe('"cachesDataFromLocalStorage" method', function () { - it('check calls localStorage get method', function () { - obj.getDataFromLocalStorage = jasmine.createSpy().and.returnValue({}); - - expect(obj.localStorage.get).toHaveBeenCalled(); - }); - }); describe('"initLocalStorage" method', function () { it('check returned value', function () { obj.namespace = 'test'; @@ -80,17 +74,16 @@ define([ it('check calls with data that equal with data in localStorage', function () { obj.internalDataHandler(data); - expect(obj.localStorage.get).toHaveBeenCalled(); - expect(obj.localStorage.set).not.toHaveBeenCalled(); + expect(window.localStorage.getItem(obj.namespace)).toBe(JSON.stringify(data)); }); it('check calls with data that not equal with data in localStorage', function () { var emptyData = {}; + obj.internalDataHandler(data); obj.internalDataHandler(emptyData); - expect(obj.localStorage.get).toHaveBeenCalled(); - expect(obj.localStorage.set).toHaveBeenCalledWith(emptyData); + expect(window.localStorage.getItem(obj.namespace)).toBe(JSON.stringify(emptyData)); }); }); describe('"externalDataHandler" method', function () { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/AvoidIdSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/AvoidIdSniff.php index f9cec6404dc69..49339054f8766 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/AvoidIdSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/AvoidIdSniff.php @@ -14,7 +14,6 @@ * Ensure that id selector is not used * * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#types - * */ class AvoidIdSniff implements Sniff { @@ -26,15 +25,36 @@ class AvoidIdSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** + * Tokens that can appear in a selector + * * @var array */ - private $symbolsBeforeId = [ - TokenizerSymbolsInterface::INDENT_SPACES, - TokenizerSymbolsInterface::NEW_LINE, + private $selectorTokens = [ + T_HASH, + T_WHITESPACE, + T_STRING_CONCAT, + T_OPEN_PARENTHESIS, + T_CLOSE_PARENTHESIS, + T_OPEN_SQUARE_BRACKET, + T_CLOSE_SQUARE_BRACKET, + T_DOUBLE_QUOTED_STRING, + T_CONSTANT_ENCAPSED_STRING, + T_DOUBLE_COLON, + T_COLON, + T_EQUAL, + T_MUL_EQUAL, + T_OR_EQUAL, + T_STRING, + T_NONE, + T_DOLLAR, + T_GREATER_THAN, + T_PLUS, + T_NS_SEPARATOR, + T_LNUMBER, ]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -42,15 +62,38 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc + * + * Will flag any selector that looks like the following: + * #foo[bar], + * #foo[bar=bash], + * #foo[bar~=bash], + * #foo[bar$=bash], + * #foo[bar*=bash], + * #foo[bar|=bash], + * #foo[bar='bash'], + * #foo:hover, + * #foo:nth-last-of-type(n), + * #foo::before, + * #foo + div, + * #foo > div, + * #foo ~ div, + * #foo\3Abar ~ div, + * #foo\:bar ~ div, + * #foo.bar .baz, + * div#foo { + * blah: 'abc'; + * } */ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); - if (T_WHITESPACE === $tokens[$stackPtr - 1]['code'] - && in_array($tokens[$stackPtr - 1]['content'], $this->symbolsBeforeId) - ) { + // Find the next non-selector token + $nextToken = $phpcsFile->findNext($this->selectorTokens, $stackPtr + 1, null, true); + + // Anything except a { or a , means this is not a selector + if ($nextToken !== false && in_array($tokens[$nextToken]['code'], [T_OPEN_CURLY_BRACKET, T_COMMA])) { $phpcsFile->addError('Id selector is used', $stackPtr, 'IdSelectorUsage'); } } diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Less/AvoidIdSniffTest.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Less/AvoidIdSniffTest.php new file mode 100644 index 0000000000000..ad9e8edd4d1b3 --- /dev/null +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Less/AvoidIdSniffTest.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Sniffs\Less; + +use Magento\TestFramework\CodingStandard\Tool\CodeSniffer\LessWrapper; + +class AvoidIdSniffTest extends \PHPUnit\Framework\TestCase +{ + /** + * @return array + */ + public function processDataProvider() + { + return [ + [ + 'avoid-ids.less', + 'avoid-ids-errors.txt' + ] + ]; + } + + /** + * @param string $fileUnderTest + * @param string $expectedReportFile + * @dataProvider processDataProvider + */ + public function testProcess($fileUnderTest, $expectedReportFile) + { + $reportFile = __DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'phpcs_report.txt'; + $wrapper = new LessWrapper(); + $codeSniffer = new \Magento\TestFramework\CodingStandard\Tool\CodeSniffer( + 'Magento', + $reportFile, + $wrapper + ); + $codeSniffer->setExtensions([LessWrapper::LESS_FILE_EXTENSION]); + $result = $codeSniffer->run( + [__DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . $fileUnderTest] + ); + // Remove the absolute path to the file from the output + $actual = preg_replace('/^.+\n/', '', ltrim(file_get_contents($reportFile))); + $expected = file_get_contents( + __DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . $expectedReportFile + ); + unlink($reportFile); + $this->assertEquals(1, $result); + $this->assertEquals($expected, $actual); + } +} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Less/_files/avoid-ids-errors.txt b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Less/_files/avoid-ids-errors.txt new file mode 100644 index 0000000000000..ebc01c4e0852e --- /dev/null +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Less/_files/avoid-ids-errors.txt @@ -0,0 +1,26 @@ +--------------------------------------------------------------------------------------------------------------------------------- +FOUND 20 ERRORS AFFECTING 20 LINES +--------------------------------------------------------------------------------------------------------------------------------- + 6 | ERROR | Id selector is used + 7 | ERROR | Id selector is used + 8 | ERROR | Id selector is used + 9 | ERROR | Id selector is used + 10 | ERROR | Id selector is used + 11 | ERROR | Id selector is used + 12 | ERROR | Id selector is used + 13 | ERROR | Id selector is used + 14 | ERROR | Id selector is used + 15 | ERROR | Id selector is used + 16 | ERROR | Id selector is used + 17 | ERROR | Id selector is used + 18 | ERROR | Id selector is used + 19 | ERROR | Id selector is used + 20 | ERROR | Id selector is used + 21 | ERROR | Id selector is used + 22 | ERROR | Id selector is used + 26 | ERROR | Id selector is used + 27 | ERROR | Id selector is used + 28 | ERROR | Id selector is used +--------------------------------------------------------------------------------------------------------------------------------- + + diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Less/_files/avoid-ids.less b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Less/_files/avoid-ids.less new file mode 100644 index 0000000000000..dd37677b5b53b --- /dev/null +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Less/_files/avoid-ids.less @@ -0,0 +1,39 @@ +// /** +// * Copyright © Magento, Inc. All rights reserved. +// * See COPYING.txt for license details. +// */ + +#foo[bar], +#foo[bar=bash], +#foo[bar~=bash], +#foo[bar$=bash], +#foo[bar*=bash], +#foo[bar|=bash], +#foo[bar='bash'], +#foo:hover, +#foo:nth-last-of-type(n), +#foo::before, +#foo + div, +#foo > div, +#foo ~ div, +#foo\3Abar ~ div, +#foo\:bar ~ div, +#foo.bar .baz, +div#foo { + blah: 'abc'; +} + +.my #foo, +#foo .blah, +.my #foo .blah { + some: 'stuff'; +} +.blah { + #bar .baz(); + .foo #bar .baz(); + #bar .baz(); + + #bar .baz; + .foo #bar .baz; + #bar .baz; +} diff --git a/lib/internal/Magento/Framework/GraphQl/Config.php b/lib/internal/Magento/Framework/GraphQl/Config.php index 8d40d7fa6bb49..ff4c920d2cd6a 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config.php +++ b/lib/internal/Magento/Framework/GraphQl/Config.php @@ -10,6 +10,7 @@ use Magento\Framework\Config\DataInterface; use Magento\Framework\GraphQl\Config\ConfigElementFactoryInterface; use Magento\Framework\GraphQl\Config\ConfigElementInterface; +use Magento\Framework\GraphQl\Query\Fields as QueryFields; /** * Provides access to typing information for a configured GraphQL schema. @@ -26,16 +27,24 @@ class Config implements ConfigInterface */ private $configElementFactory; + /** + * @var QueryFields + */ + private $queryFields; + /** * @param DataInterface $data * @param ConfigElementFactoryInterface $configElementFactory + * @param QueryFields $queryFields */ public function __construct( DataInterface $data, - ConfigElementFactoryInterface $configElementFactory + ConfigElementFactoryInterface $configElementFactory, + QueryFields $queryFields ) { $this->configData = $data; $this->configElementFactory = $configElementFactory; + $this->queryFields = $queryFields; } /** @@ -44,6 +53,7 @@ public function __construct( * @param string $configElementName * @return ConfigElementInterface * @throws \LogicException + * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ public function getConfigElement(string $configElementName) : ConfigElementInterface { @@ -53,6 +63,16 @@ public function getConfigElement(string $configElementName) : ConfigElementInter sprintf('Config element "%s" is not declared in GraphQL schema', $configElementName) ); } + + $fieldsInQuery = $this->queryFields->getFieldsUsedInQuery(); + if (isset($data['fields']) && !empty($fieldsInQuery)) { + foreach ($data['fields'] as $fieldName => $fieldConfig) { + if (!isset($fieldsInQuery[$fieldName])) { + unset($data['fields'][$fieldName]); + } + } + } + return $this->configElementFactory->createFromConfigData($data); } diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php index 1a022e92c829b..b9ec1dd87d122 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php @@ -42,10 +42,11 @@ public function createFromConfigData( array $fieldData, array $arguments = [] ) : Field { - $arraySign = '/^.*(\[\])$/'; + $fieldType = $fieldData['type']; $isList = false; - if (preg_match($arraySign, $fieldData['type'])) { + //check if type ends with [] + if ($fieldType{strlen($fieldType) - 2} == '[' && $fieldType{strlen($fieldType) - 1} == ']') { $isList = true; $fieldData['type'] = str_replace('[]', '', $fieldData['type']); $fieldData['itemType'] = str_replace('[]', '', $fieldData['type']); diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Fields.php b/lib/internal/Magento/Framework/GraphQl/Query/Fields.php new file mode 100644 index 0000000000000..d0bc9591265eb --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Query/Fields.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Query; + +use GraphQL\Language\AST\Node; +use GraphQL\Language\AST\NodeKind; + +/** + * This class holds a list of all queried fields and is used to enable performance optimization for schema loading. + */ +class Fields +{ + /** + * @var string[] + */ + private $fieldsUsedInQuery = []; + + /** + * Set Query for extracting list of fields. + * + * @param string $query + * @return void + */ + public function setQuery($query) + { + $queryFields = []; + try { + $queryAst = \GraphQL\Language\Parser::parse(new \GraphQL\Language\Source($query ?: '', 'GraphQL')); + \GraphQL\Language\Visitor::visit( + $queryAst, + [ + 'leave' => [ + NodeKind::NAME => function (Node $node) use (&$queryFields) { + $queryFields[$node->value] = $node->value; + } + ] + ] + ); + } catch (\Exception $e) { + // If a syntax error is encountered do not collect fields + } + if (isset($queryFields['IntrospectionQuery'])) { + // It must be possible to query any fields during introspection query + $queryFields = []; + } + $this->fieldsUsedInQuery = $queryFields; + } + + /** + * Get list of fields used in GraphQL query. + * + * This method is stateful and relies on the query being set with setQuery. + * + * @return string[] + */ + public function getFieldsUsedInQuery() + { + return $this->fieldsUsedInQuery; + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Query/ResolverInterface.php b/lib/internal/Magento/Framework/GraphQl/Query/ResolverInterface.php index 295113a98e465..c7b4c7688b9ab 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/ResolverInterface.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/ResolverInterface.php @@ -10,6 +10,7 @@ use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\Resolver\Value; +use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; /** * Resolver fetches the data and formats it according to the GraphQL schema. @@ -20,7 +21,7 @@ interface ResolverInterface * Fetches the data from persistence models and format it according to the GraphQL schema. * * @param \Magento\Framework\GraphQl\Config\Element\Field $field - * @param $context + * @param ContextInterface $context * @param ResolveInfo $info * @param array|null $value * @param array|null $args diff --git a/lib/internal/Magento/Framework/Math/Random.php b/lib/internal/Magento/Framework/Math/Random.php index 7cb70c9a822cc..c2059e1935a80 100644 --- a/lib/internal/Magento/Framework/Math/Random.php +++ b/lib/internal/Magento/Framework/Math/Random.php @@ -5,6 +5,9 @@ */ namespace Magento\Framework\Math; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Phrase; + /** * Random data generator * @@ -24,41 +27,24 @@ class Random /**#@-*/ /** - * Get random string + * Get random string. * * @param int $length * @param null|string $chars + * * @return string - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function getRandomString($length, $chars = null) { $str = ''; if (null === $chars) { - $chars = self::CHARS_LOWERS . self::CHARS_UPPERS . self::CHARS_DIGITS; + $chars = self::CHARS_LOWERS.self::CHARS_UPPERS.self::CHARS_DIGITS; } - if (function_exists('openssl_random_pseudo_bytes')) { - // use openssl lib if it is installed - for ($i = 0, $lc = strlen($chars) - 1; $i < $length; $i++) { - $bytes = openssl_random_pseudo_bytes(PHP_INT_SIZE); - $hex = bin2hex($bytes); // hex() doubles the length of the string - $rand = abs(hexdec($hex) % $lc); // random integer from 0 to $lc - $str .= $chars[$rand]; // random character in $chars - } - } elseif ($fp = @fopen('/dev/urandom', 'rb')) { - // attempt to use /dev/urandom if it exists but openssl isn't available - for ($i = 0, $lc = strlen($chars) - 1; $i < $length; $i++) { - $bytes = @fread($fp, PHP_INT_SIZE); - $hex = bin2hex($bytes); // hex() doubles the length of the string - $rand = abs(hexdec($hex) % $lc); // random integer from 0 to $lc - $str .= $chars[$rand]; // random character in $chars - } - fclose($fp); - } else { - throw new \Magento\Framework\Exception\LocalizedException( - new \Magento\Framework\Phrase("Please make sure you have 'openssl' extension installed") - ); + $charsMaxKey = mb_strlen($chars) - 1; + for ($i = 0; $i < $length; $i++) { + $str .= $chars[self::getRandomNumber(0, $charsMaxKey)]; } return $str; @@ -67,47 +53,33 @@ public function getRandomString($length, $chars = null) /** * Return a random number in the specified range * - * @param $min [optional] - * @param $max [optional] - * @return int A random integer value between min (or 0) and max - * @throws \Magento\Framework\Exception\LocalizedException + * @param int $min + * @param int $max + * @return int A random integer value between min (or 0) and max + * @throws LocalizedException */ public static function getRandomNumber($min = 0, $max = null) { if (null === $max) { $max = mt_getrandmax(); } - $range = $max - $min + 1; - $offset = 0; - if (function_exists('openssl_random_pseudo_bytes')) { - // use openssl lib if it is installed - $bytes = openssl_random_pseudo_bytes(PHP_INT_SIZE); - $hex = bin2hex($bytes); // hex() doubles the length of the string - $offset = abs(hexdec($hex) % $range); // random integer from 0 to $range - } elseif ($fp = @fopen('/dev/urandom', 'rb')) { - // attempt to use /dev/urandom if it exists but openssl isn't available - $bytes = @fread($fp, PHP_INT_SIZE); - $hex = bin2hex($bytes); // hex() doubles the length of the string - $offset = abs(hexdec($hex) % $range); // random integer from 0 to $range - fclose($fp); - } else { - throw new \Magento\Framework\Exception\LocalizedException( - new \Magento\Framework\Phrase("Please make sure you have 'openssl' extension installed") - ); + if ($max < $min) { + throw new LocalizedException(new Phrase('Invalid range given.')); } - return $min + $offset; // random integer from $min to $max + return random_int($min, $max); } /** - * Generate a hash from unique ID + * Generate a hash from unique ID. * * @param string $prefix * @return string + * @throws LocalizedException */ public function getUniqueHash($prefix = '') { - return $prefix . md5(uniqid(microtime() . self::getRandomNumber(), true)); + return $prefix . $this->getRandomString(32); } } diff --git a/lib/internal/Magento/Framework/Serialize/Serializer/FormData.php b/lib/internal/Magento/Framework/Serialize/Serializer/FormData.php new file mode 100644 index 0000000000000..a945822d92f97 --- /dev/null +++ b/lib/internal/Magento/Framework/Serialize/Serializer/FormData.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Serialize\Serializer; + +/** + * Class for processing of serialized form data. + */ +class FormData +{ + /** + * @var Json + */ + private $serializer; + + /** + * @param Json $serializer + */ + public function __construct(Json $serializer) + { + $this->serializer = $serializer; + } + + /** + * Provides form data from the serialized data. + * + * @param string $serializedData + * @return array + * @throws \InvalidArgumentException + */ + public function unserialize(string $serializedData): array + { + $encodedFields = $this->serializer->unserialize($serializedData); + + if (!is_array($encodedFields)) { + throw new \InvalidArgumentException('Unable to unserialize value.'); + } + + $formData = []; + foreach ($encodedFields as $item) { + $decodedFieldData = []; + parse_str($item, $decodedFieldData); + $formData = array_replace_recursive($formData, $decodedFieldData); + } + + return $formData; + } +} diff --git a/lib/web/jquery/patches/jquery.js b/lib/web/jquery/patches/jquery.js index 9d733a92159c6..5c741d0832b75 100644 --- a/lib/web/jquery/patches/jquery.js +++ b/lib/web/jquery/patches/jquery.js @@ -22,14 +22,12 @@ define([], function () { return function ($) { var majorVersion = $.fn.jquery.split('.')[0]; - $.noConflict(); - if (majorVersion >= 3) { console.warn('jQuery patch for CVE-2015-9251 is no longer necessary, and should be removed'); } - ajaxResponsePatch(jQuery); + ajaxResponsePatch($); - return jQuery; + return $; }; }); diff --git a/lib/web/mage/backend/validation.js b/lib/web/mage/backend/validation.js index d3ab7dd086a43..1043f45a7402e 100644 --- a/lib/web/mage/backend/validation.js +++ b/lib/web/mage/backend/validation.js @@ -171,6 +171,7 @@ this._submit(); } else { this._showErrors(response); + $(this.element[0]).trigger('afterValidate.error'); $('body').trigger('processStop'); } }, @@ -223,6 +224,7 @@ * @protected */ _onError: function () { + $(this.element[0]).trigger('afterValidate.error'); $('body').trigger('processStop'); if (this.options.errorUrl) {