diff --git a/CHANGELOG.md b/CHANGELOG.md index e17c4e14ffc7d..0999bee6ea415 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4136,7 +4136,7 @@ Tests: * Moved Multishipping functionality to newly created module Multishipping * Extracted Product duplication behavior from Product model to Product\Copier model * Replaced event "catalog_model_product_duplicate" with composite Product\Copier model - * Replaced event "catalog_product_prepare_save" with controller product initialization helper that can be customozed via plugins + * Replaced event "catalog_product_prepare_save" with controller product initialization helper that can be customized via plugins * Consolidated Authorize.Net functionality in single module Authorizenet * Eliminated dependency of Sales module on Shipping and Usa modules * Eliminated dependency of Shipping module on Customer module @@ -4335,7 +4335,7 @@ Tests: * Fixed order placing with virtual product using Express Checkout * Fixed the error during order placement with Recurring profile payment * Fixed wrong redirect after customer registration during multishipping checkout - * Fixed inability to crate shipping labels + * Fixed inability to create shipping labels * Fixed inability to switch language, if the default language is English * Fixed an issue with incorrect XML appearing in cache after some actions on the frontend * Fixed product export diff --git a/README.md b/README.md index 9e3cf448f99fb..ecd457a4f1aef 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Welcome to Magento 2 installation! We're glad you chose to install Magento 2, a cutting-edge, feature-rich eCommerce solution that gets results. ## Magento System Requirements -[Magento System Requirements](https://devdocs.magento.com/guides/v2.3/install-gde/system-requirements2.html). +[Magento System Requirements](https://devdocs.magento.com/guides/v2.3/install-gde/system-requirements.html). ## Install Magento @@ -45,7 +45,7 @@ Please review the [Code Contributions guide](https://devdocs.magento.com/guides/ ## Reporting Security Issues -To report security vulnerabilities in Magento software or web sites, please create a Bugcrowd researcher account [there](https://bugcrowd.com/magento) to submit and follow-up your issue. Learn more about reporting security issues [here](https://magento.com/security/reporting-magento-security-issue). +To report security vulnerabilities or learn more about reporting security issues in Magento software or web sites visit the [Magento Bug Bounty Program](https://hackerone.com/magento) on hackerone. Please create a hackerone account [there](https://hackerone.com/magento) to submit and follow-up your issue. Stay up-to-date on the latest security news and patches for Magento by signing up for [Security Alert Notifications](https://magento.com/security/sign-up). diff --git a/app/bootstrap.php b/app/bootstrap.php index ddbcaffd42962..4974acdf0fc80 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -8,7 +8,9 @@ * Environment initialization */ error_reporting(E_ALL); -stream_wrapper_unregister('phar'); +if (in_array('phar', \stream_get_wrappers())) { + stream_wrapper_unregister('phar'); +} #ini_set('display_errors', 1); /* PHP version validation */ diff --git a/app/code/Magento/AdminNotification/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/AdminNotification/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..3f0f251ce7ee4 --- /dev/null +++ b/app/code/Magento/AdminNotification/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,21 @@ + + + + + + System + Notifications + magento-backend-system + + + Notifications + Notifications + magento-adminnotification-system-adminnotification + + diff --git a/app/code/Magento/AdminNotification/Test/Mftf/Test/AdminSystemNotificationNavigateMenuTest.xml b/app/code/Magento/AdminNotification/Test/Mftf/Test/AdminSystemNotificationNavigateMenuTest.xml new file mode 100644 index 0000000000000..75dceb4028622 --- /dev/null +++ b/app/code/Magento/AdminNotification/Test/Mftf/Test/AdminSystemNotificationNavigateMenuTest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + <description value="Admin should be able to navigate to System > Notifications"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14125"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToSystemNotificationPage"> + <argument name="menuUiId" value="{{AdminMenuSystem.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSystemOtherSettingsNotifications.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSystemOtherSettingsNotifications.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/AdminNotification/view/adminhtml/layout/adminhtml_notification_block.xml b/app/code/Magento/AdminNotification/view/adminhtml/layout/adminhtml_notification_block.xml index c68313211c2e6..06fd380cb2a44 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/layout/adminhtml_notification_block.xml +++ b/app/code/Magento/AdminNotification/view/adminhtml/layout/adminhtml_notification_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.notification.container.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">notificationGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\AdminNotification\Model\ResourceModel\Grid\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\AdminNotification\Model\ResourceModel\Grid\Collection</argument> <argument name="default_dir" xsi:type="string">DESC</argument> <argument name="default_sort" xsi:type="string">date_added</argument> <argument name="save_parameters_in_session" xsi:type="string">1</argument> diff --git a/app/code/Magento/AdvancedSearch/view/adminhtml/layout/catalog_search_block.xml b/app/code/Magento/AdvancedSearch/view/adminhtml/layout/catalog_search_block.xml index b6ef596281e51..f3544863348ec 100644 --- a/app/code/Magento/AdvancedSearch/view/adminhtml/layout/catalog_search_block.xml +++ b/app/code/Magento/AdvancedSearch/view/adminhtml/layout/catalog_search_block.xml @@ -11,7 +11,7 @@ <block class="Magento\AdvancedSearch\Block\Adminhtml\Search\Grid" name="search.edit.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">catalog_search_grid</argument> - <argument name="dataSource" xsi:type="object">Magento\AdvancedSearch\Model\ResourceModel\Search\Grid\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\AdvancedSearch\Model\ResourceModel\Search\Grid\Collection</argument> <argument name="default_sort" xsi:type="string">name</argument> <argument name="default_dir" xsi:type="string">ASC</argument> <argument name="save_parameters_in_session" xsi:type="string">1</argument> diff --git a/app/code/Magento/Analytics/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Analytics/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..01b86101def28 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,21 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuReports"> + <data key="pageTitle">Reports</data> + <data key="title">Reports</data> + <data key="dataUiId">magento-reports-report</data> + </entity> + <entity name="AdminMenuReportsBusinessIntelligenceAdvancedReporting"> + <data key="pageTitle">AdvancedReporting</data> + <data key="title">AdvancedReporting</data> + <data key="dataUiId">magento-analytics-advanced-reporting</data> + </entity> +</entities> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingNavigateMenuTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingNavigateMenuTest.xml new file mode 100644 index 0000000000000..67d6715285697 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingNavigateMenuTest.xml @@ -0,0 +1,35 @@ +<?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="AdminAdvancedReportingNavigateMenuTest"> + <annotations> + <features value="Analytics"/> + <stories value="Menu Navigation"/> + <title value="Admin advanced reporting navigate menu test"/> + <description value="Admin should be able to navigate through advanced reporting admin menu to BI reports page"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14152"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateAdvancedReportingPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsBusinessIntelligenceAdvancedReporting.dataUiId}}"/> + </actionGroup> + <switchToNextTab stepKey="switchToNewTab"/> + <seeInCurrentUrl url="advancedreporting.rjmetrics.com/report" stepKey="seeAssertAdvancedReportingPageUrl"/> + </test> +</tests> diff --git a/app/code/Magento/Authorization/Model/Role.php b/app/code/Magento/Authorization/Model/Role.php index 2546df86d09dd..dcc46ee77ee12 100644 --- a/app/code/Magento/Authorization/Model/Role.php +++ b/app/code/Magento/Authorization/Model/Role.php @@ -51,19 +51,29 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritDoc + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $properties = parent::__sleep(); return array_diff($properties, ['_resource', '_resourceCollection']); } /** - * {@inheritdoc} + * @inheritDoc + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + parent::__wakeup(); $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); $this->_resource = $objectManager->get(\Magento\Authorization\Model\ResourceModel\Role::class); diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Validator/TransactionResponseValidator.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Validator/TransactionResponseValidator.php index 93b5f2bb62a7d..326f4fb29ac84 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Validator/TransactionResponseValidator.php +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Validator/TransactionResponseValidator.php @@ -54,7 +54,7 @@ public function validate(array $validationSubject): ResultInterface if (isset($transactionResponse['messages']['message']['code'])) { $errorCodes[] = $transactionResponse['messages']['message']['code']; $errorMessages[] = $transactionResponse['messages']['message']['text']; - } elseif ($transactionResponse['messages']['message']) { + } elseif (isset($transactionResponse['messages']['message'])) { foreach ($transactionResponse['messages']['message'] as $message) { $errorCodes[] = $message['code']; $errorMessages[] = $message['description']; @@ -62,7 +62,7 @@ public function validate(array $validationSubject): ResultInterface } elseif (isset($transactionResponse['errors'])) { foreach ($transactionResponse['errors'] as $message) { $errorCodes[] = $message['errorCode']; - $errorMessages[] = $message['errorCode']; + $errorMessages[] = $message['errorText']; } } @@ -85,8 +85,10 @@ private function isResponseCodeAnError(array $transactionResponse): bool ?? $transactionResponse['errors'][0]['errorCode'] ?? null; - return in_array($transactionResponse['responseCode'], [self::RESPONSE_CODE_APPROVED, self::RESPONSE_CODE_HELD]) - && $code + return !in_array($transactionResponse['responseCode'], [ + self::RESPONSE_CODE_APPROVED, self::RESPONSE_CODE_HELD + ]) + || $code && !in_array( $code, [ diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Validator/TransactionResponseValidatorTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Validator/TransactionResponseValidatorTest.php index cef7883bd5dbc..c59cf00899af2 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Validator/TransactionResponseValidatorTest.php +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Validator/TransactionResponseValidatorTest.php @@ -15,13 +15,18 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +/** + * Tests for the transaction response validator + */ class TransactionResponseValidatorTest extends TestCase { private const RESPONSE_CODE_APPROVED = 1; private const RESPONSE_CODE_HELD = 4; + private const RESPONSE_CODE_DENIED = 2; private const RESPONSE_REASON_CODE_APPROVED = 1; private const RESPONSE_REASON_CODE_PENDING_REVIEW_AUTHORIZED = 252; private const RESPONSE_REASON_CODE_PENDING_REVIEW = 253; + private const ERROR_CODE_AVS_MISMATCH = 27; /** * @var ResultInterfaceFactory|MockObject @@ -86,16 +91,6 @@ public function testValidateScenarios($transactionResponse, $isValid, $errorCode public function scenarioProvider() { return [ - // This validator only cares about successful edge cases so test for default behavior - [ - [ - 'responseCode' => 'foo', - ], - true, - [], - [] - ], - // Test for acceptable reason codes [ [ @@ -208,6 +203,29 @@ public function scenarioProvider() ['foo'], ['bar'] ], + [ + [ + 'responseCode' => self::RESPONSE_CODE_DENIED, + 'errors' => [ + [ + 'errorCode' => self::ERROR_CODE_AVS_MISMATCH, + 'errorText' => 'bar' + ] + ] + ], + false, + [self::ERROR_CODE_AVS_MISMATCH], + ['bar'] + ], + // This validator only cares about successful edge cases so test for default behavior + [ + [ + 'responseCode' => 'foo', + ], + false, + [], + [] + ], ]; } } diff --git a/app/code/Magento/Backend/Block/Dashboard/Graph.php b/app/code/Magento/Backend/Block/Dashboard/Graph.php index 8e238ccab44cb..b76421e4e6f67 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Graph.php +++ b/app/code/Magento/Backend/Block/Dashboard/Graph.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Backend\Block\Dashboard; /** @@ -15,7 +17,7 @@ class Graph extends \Magento\Backend\Block\Dashboard\AbstractDashboard /** * Api URL */ - const API_URL = 'http://chart.apis.google.com/chart'; + const API_URL = 'https://image-charts.com/chart'; /** * All series @@ -76,6 +78,7 @@ class Graph extends \Magento\Backend\Block\Dashboard\AbstractDashboard /** * Google chart api data encoding * + * @deprecated since the Google Image Charts API not accessible from March 14, 2019 * @var string */ protected $_encoding = 'e'; @@ -111,8 +114,8 @@ public function __construct( \Magento\Backend\Helper\Dashboard\Data $dashboardData, array $data = [] ) { - $this->_dashboardData = $dashboardData; parent::__construct($context, $collectionFactory, $data); + $this->_dashboardData = $dashboardData; } /** @@ -128,7 +131,7 @@ protected function _getTabTemplate() /** * Set data rows * - * @param array $rows + * @param string $rows * @return void */ public function setDataRows($rows) @@ -152,15 +155,14 @@ public function addSeries($seriesId, array $options) * Get series * * @param string $seriesId - * @return array|false + * @return array|bool */ public function getSeries($seriesId) { if (isset($this->_allSeries[$seriesId])) { return $this->_allSeries[$seriesId]; - } else { - return false; } + return false; } /** @@ -187,11 +189,12 @@ public function getChartUrl($directUrl = true) { $params = [ 'cht' => 'lc', - 'chf' => 'bg,s,ffffff', - 'chco' => 'ef672f', 'chls' => '7', - 'chxs' => '0,676056,15,0,l,676056|1,676056,15,0,l,676056', - 'chm' => 'h,f2ebde,0,0:1:.1,1,-1', + 'chf' => 'bg,s,f4f4f4|c,lg,90,ffffff,0.1,ededed,0', + 'chm' => 'B,f4d4b2,0,0,0', + 'chco' => 'db4814', + 'chxs' => '0,0,11|1,0,11', + 'chma' => '15,15,15,15' ]; $this->_allSeries = $this->getRowsData($this->_dataRows); @@ -279,20 +282,11 @@ public function getChartUrl($directUrl = true) $this->_axisLabels['x'] = $dates; $this->_allSeries = $datas; - //Google encoding values - if ($this->_encoding == "s") { - // simple encoding - $params['chd'] = "s:"; - $dataDelimiter = ""; - $dataSetdelimiter = ","; - $dataMissing = "_"; - } else { - // extended encoding - $params['chd'] = "e:"; - $dataDelimiter = ""; - $dataSetdelimiter = ","; - $dataMissing = "__"; - } + // Image-Charts Awesome data format values + $params['chd'] = "a:"; + $dataDelimiter = ","; + $dataSetdelimiter = "|"; + $dataMissing = "_"; // process each string in the array, and find the max length $localmaxvalue = [0]; @@ -306,7 +300,6 @@ public function getChartUrl($directUrl = true) $minvalue = min($localminvalue); // default values - $yrange = 0; $yLabels = []; $miny = 0; $maxy = 0; @@ -314,14 +307,13 @@ public function getChartUrl($directUrl = true) if ($minvalue >= 0 && $maxvalue >= 0) { if ($maxvalue > 10) { - $p = pow(10, $this->_getPow($maxvalue)); + $p = pow(10, $this->_getPow((int) $maxvalue)); $maxy = ceil($maxvalue / $p) * $p; $yLabels = range($miny, $maxy, $p); } else { $maxy = ceil($maxvalue + 1); $yLabels = range($miny, $maxy, 1); } - $yrange = $maxy; $yorigin = 0; } @@ -329,44 +321,14 @@ public function getChartUrl($directUrl = true) foreach ($this->getAllSeries() as $index => $serie) { $thisdataarray = $serie; - if ($this->_encoding == "s") { - // SIMPLE ENCODING - for ($j = 0; $j < sizeof($thisdataarray); $j++) { - $currentvalue = $thisdataarray[$j]; - if (is_numeric($currentvalue)) { - $ylocation = round( - (strlen($this->_simpleEncoding) - 1) * ($yorigin + $currentvalue) / $yrange - ); - $chartdata[] = substr($this->_simpleEncoding, $ylocation, 1) . $dataDelimiter; - } else { - $chartdata[] = $dataMissing . $dataDelimiter; - } - } - } else { - // EXTENDED ENCODING - for ($j = 0; $j < sizeof($thisdataarray); $j++) { - $currentvalue = $thisdataarray[$j]; - if (is_numeric($currentvalue)) { - if ($yrange) { - $ylocation = 4095 * ($yorigin + $currentvalue) / $yrange; - } else { - $ylocation = 0; - } - $firstchar = floor($ylocation / 64); - $secondchar = $ylocation % 64; - $mappedchar = substr( - $this->_extendedEncoding, - $firstchar, - 1 - ) . substr( - $this->_extendedEncoding, - $secondchar, - 1 - ); - $chartdata[] = $mappedchar . $dataDelimiter; - } else { - $chartdata[] = $dataMissing . $dataDelimiter; - } + $count = count($thisdataarray); + for ($j = 0; $j < $count; $j++) { + $currentvalue = $thisdataarray[$j]; + if (is_numeric($currentvalue)) { + $ylocation = $yorigin + $currentvalue; + $chartdata[] = $ylocation . $dataDelimiter; + } else { + $chartdata[] = $dataMissing . $dataDelimiter; } } $chartdata[] = $dataSetdelimiter; @@ -381,45 +343,13 @@ public function getChartUrl($directUrl = true) $valueBuffer = []; - if (sizeof($this->_axisLabels) > 0) { + if (count($this->_axisLabels) > 0) { $params['chxt'] = implode(',', array_keys($this->_axisLabels)); $indexid = 0; foreach ($this->_axisLabels as $idx => $labels) { if ($idx == 'x') { - /** - * Format date - */ - foreach ($this->_axisLabels[$idx] as $_index => $_label) { - if ($_label != '') { - $period = new \DateTime($_label, new \DateTimeZone($timezoneLocal)); - switch ($this->getDataHelper()->getParam('period')) { - case '24h': - $this->_axisLabels[$idx][$_index] = $this->_localeDate->formatDateTime( - $period->setTime($period->format('H'), 0, 0), - \IntlDateFormatter::NONE, - \IntlDateFormatter::SHORT - ); - break; - case '7d': - case '1m': - $this->_axisLabels[$idx][$_index] = $this->_localeDate->formatDateTime( - $period, - \IntlDateFormatter::SHORT, - \IntlDateFormatter::NONE - ); - break; - case '1y': - case '2y': - $this->_axisLabels[$idx][$_index] = date('m/Y', strtotime($_label)); - break; - } - } else { - $this->_axisLabels[$idx][$_index] = ''; - } - } - + $this->formatAxisLabelDate((string) $idx, (string) $timezoneLocal); $tmpstring = implode('|', $this->_axisLabels[$idx]); - $valueBuffer[] = $indexid . ":|" . $tmpstring; } elseif ($idx == 'y') { $valueBuffer[] = $indexid . ":|" . implode('|', $yLabels); @@ -438,12 +368,51 @@ public function getChartUrl($directUrl = true) foreach ($params as $name => $value) { $p[] = $name . '=' . urlencode($value); } - return self::API_URL . '?' . implode('&', $p); - } else { - $gaData = urlencode(base64_encode(json_encode($params))); - $gaHash = $this->_dashboardData->getChartDataHash($gaData); - $params = ['ga' => $gaData, 'h' => $gaHash]; - return $this->getUrl('adminhtml/*/tunnel', ['_query' => $params]); + return (string) self::API_URL . '?' . implode('&', $p); + } + $gaData = urlencode(base64_encode(json_encode($params))); + $gaHash = $this->_dashboardData->getChartDataHash($gaData); + $params = ['ga' => $gaData, 'h' => $gaHash]; + return $this->getUrl('adminhtml/*/tunnel', ['_query' => $params]); + } + + /** + * Format dates for axis labels + * + * @param string $idx + * @param string $timezoneLocal + * + * @return void + */ + private function formatAxisLabelDate($idx, $timezoneLocal) + { + foreach ($this->_axisLabels[$idx] as $_index => $_label) { + if ($_label != '') { + $period = new \DateTime($_label, new \DateTimeZone($timezoneLocal)); + switch ($this->getDataHelper()->getParam('period')) { + case '24h': + $this->_axisLabels[$idx][$_index] = $this->_localeDate->formatDateTime( + $period->setTime((int) $period->format('H'), 0, 0), + \IntlDateFormatter::NONE, + \IntlDateFormatter::SHORT + ); + break; + case '7d': + case '1m': + $this->_axisLabels[$idx][$_index] = $this->_localeDate->formatDateTime( + $period, + \IntlDateFormatter::SHORT, + \IntlDateFormatter::NONE + ); + break; + case '1y': + case '2y': + $this->_axisLabels[$idx][$_index] = date('m/Y', strtotime($_label)); + break; + } + } else { + $this->_axisLabels[$idx][$_index] = ''; + } } } @@ -540,6 +509,8 @@ protected function getHeight() } /** + * Sets data helper + * * @param \Magento\Backend\Helper\Dashboard\AbstractDashboard $dataHelper * @return void */ diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/ProductsViewed.php b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/ProductsViewed.php index 0de1111ffa722..a42a44814cb0c 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/ProductsViewed.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/ProductsViewed.php @@ -6,12 +6,12 @@ */ namespace Magento\Backend\Controller\Adminhtml\Dashboard; -use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; /** * Get most viewed products controller. */ -class ProductsViewed extends AjaxBlock implements HttpGetActionInterface +class ProductsViewed extends AjaxBlock implements HttpPostActionInterface { /** * Gets most viewed products list diff --git a/app/code/Magento/Backend/Model/Auth/Session.php b/app/code/Magento/Backend/Model/Auth/Session.php index 593b4219d45f0..61db71c1803e2 100644 --- a/app/code/Magento/Backend/Model/Auth/Session.php +++ b/app/code/Magento/Backend/Model/Auth/Session.php @@ -5,21 +5,25 @@ */ namespace Magento\Backend\Model\Auth; +use Magento\Framework\Acl; +use Magento\Framework\AclFactory; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Stdlib\Cookie\CookieMetadataFactory; use Magento\Framework\Stdlib\CookieManagerInterface; +use Magento\Backend\Spi\SessionUserHydratorInterface; +use Magento\Backend\Spi\SessionAclHydratorInterface; +use Magento\User\Model\User; +use Magento\User\Model\UserFactory; /** * Backend Auth session model * * @api - * @method \Magento\User\Model\User|null getUser() - * @method \Magento\Backend\Model\Auth\Session setUser(\Magento\User\Model\User $value) - * @method \Magento\Framework\Acl|null getAcl() - * @method \Magento\Backend\Model\Auth\Session setAcl(\Magento\Framework\Acl $value) * @method int getUpdatedAt() * @method \Magento\Backend\Model\Auth\Session setUpdatedAt(int $value) * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @todo implement solution that keeps is_first_visit flag in session during redirects * @api * @since 100.0.2 @@ -55,6 +59,36 @@ class Session extends \Magento\Framework\Session\SessionManager implements \Mage */ protected $_config; + /** + * @var SessionUserHydratorInterface + */ + private $userHydrator; + + /** + * @var SessionAclHydratorInterface + */ + private $aclHydrator; + + /** + * @var UserFactory + */ + private $userFactory; + + /** + * @var AclFactory + */ + private $aclFactory; + + /** + * @var User|null + */ + private $user; + + /** + * @var Acl|null + */ + private $acl; + /** * @param \Magento\Framework\App\Request\Http $request * @param \Magento\Framework\Session\SidResolverInterface $sidResolver @@ -69,6 +103,10 @@ class Session extends \Magento\Framework\Session\SessionManager implements \Mage * @param \Magento\Backend\Model\UrlInterface $backendUrl * @param \Magento\Backend\App\ConfigInterface $config * @throws \Magento\Framework\Exception\SessionException + * @param SessionUserHydratorInterface|null $userHydrator + * @param SessionAclHydratorInterface|null $aclHydrator + * @param UserFactory|null $userFactory + * @param AclFactory|null $aclFactory * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -83,11 +121,19 @@ public function __construct( \Magento\Framework\App\State $appState, \Magento\Framework\Acl\Builder $aclBuilder, \Magento\Backend\Model\UrlInterface $backendUrl, - \Magento\Backend\App\ConfigInterface $config + \Magento\Backend\App\ConfigInterface $config, + ?SessionUserHydratorInterface $userHydrator = null, + ?SessionAclHydratorInterface $aclHydrator = null, + ?UserFactory $userFactory = null, + ?AclFactory $aclFactory = null ) { $this->_config = $config; $this->_aclBuilder = $aclBuilder; $this->_backendUrl = $backendUrl; + $this->userHydrator = $userHydrator ?? ObjectManager::getInstance()->get(SessionUserHydratorInterface::class); + $this->aclHydrator = $aclHydrator ?? ObjectManager::getInstance()->get(SessionAclHydratorInterface::class); + $this->userFactory = $userFactory ?? ObjectManager::getInstance()->get(UserFactory::class); + $this->aclFactory = $aclFactory ?? ObjectManager::getInstance()->get(AclFactory::class); parent::__construct( $request, $sidResolver, @@ -230,6 +276,16 @@ public function processLogin() return $this; } + /** + * @inheritDoc + */ + public function destroy(array $options = null) + { + $this->user = null; + $this->acl = null; + parent::destroy($options); + } + /** * Process of configuring of current auth storage when logout was performed * @@ -253,4 +309,136 @@ public function isValidForPath($path) { return true; } + + /** + * Logged-in user. + * + * @return User|null + */ + public function getUser() + { + if (!$this->user) { + $userData = $this->getUserData(); + if ($userData) { + /** @var User $user */ + $user = $this->userFactory->create(); + $this->userHydrator->hydrate($user, $userData); + $this->user = $user; + } + } + + return $this->user; + } + + /** + * Set logged-in user instance. + * + * @param User|null $user + * @return Session + */ + public function setUser($user) + { + $this->setUserData(null); + if ($user) { + $this->setUserData($this->userHydrator->extract($user)); + } + $this->user = $user; + + return $this; + } + + /** + * Is user logged in? + * + * @return bool + */ + public function hasUser() + { + return $this->user || $this->hasUserData(); + } + + /** + * Remove logged-in user. + * + * @return Session + */ + public function unsUser() + { + $this->user = null; + return $this->unsUserData(); + } + + /** + * Logged-in user's ACL data. + * + * @return Acl|null + */ + public function getAcl() + { + if (!$this->acl) { + $aclData = $this->getUserAclData(); + if ($aclData) { + /** @var Acl $acl */ + $acl = $this->aclFactory->create(); + $this->aclHydrator->hydrate($acl, $aclData); + $this->acl = $acl; + } + } + + return $this->acl; + } + + /** + * Set logged-in user's ACL data instance. + * + * @param Acl|null $acl + * @return Session + */ + public function setAcl($acl) + { + $this->setUserAclData(null); + if ($acl) { + $this->setUserAclData($this->aclHydrator->extract($acl)); + } + $this->acl = $acl; + + return $this; + } + + /** + * Whether ACL data is present. + * + * @return bool + */ + public function hasAcl() + { + return $this->acl || $this->hasUserAclData(); + } + + /** + * Remove ACL data. + * + * @return Session + */ + public function unsAcl() + { + $this->acl = null; + return $this->unsUserAclData(); + } + + /** + * @inheritDoc + */ + public function writeClose() + { + //Updating data in session in case these objects has been changed. + if ($this->user) { + $this->setUser($this->user); + } + if ($this->acl) { + $this->setAcl($this->acl); + } + + parent::writeClose(); + } } diff --git a/app/code/Magento/Backend/Model/Auth/SessionAclHydrator.php b/app/code/Magento/Backend/Model/Auth/SessionAclHydrator.php new file mode 100644 index 0000000000000..34e01be696672 --- /dev/null +++ b/app/code/Magento/Backend/Model/Auth/SessionAclHydrator.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Backend\Model\Auth; + +use Magento\Backend\Spi\SessionAclHydratorInterface; +use Magento\Framework\Acl; + +/** + * @inheritDoc + */ +class SessionAclHydrator extends Acl implements SessionAclHydratorInterface +{ + /** + * @inheritDoc + */ + public function extract(Acl $acl): array + { + return ['rules' => $acl->_rules, 'resources' => $acl->_resources, 'roles' => $acl->_roleRegistry]; + } + + /** + * @inheritDoc + */ + public function hydrate(Acl $target, array $data): void + { + $target->_rules = $data['rules']; + $target->_resources = $data['resources']; + $target->_roleRegistry = $data['roles']; + } +} diff --git a/app/code/Magento/Backend/Model/Auth/SessionUserHydrator.php b/app/code/Magento/Backend/Model/Auth/SessionUserHydrator.php new file mode 100644 index 0000000000000..6dee8b7b302c8 --- /dev/null +++ b/app/code/Magento/Backend/Model/Auth/SessionUserHydrator.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Backend\Model\Auth; + +use Magento\Backend\Spi\SessionUserHydratorInterface; +use Magento\User\Model\User; +use Magento\Authorization\Model\Role; +use Magento\Authorization\Model\RoleFactory; + +/** + * @inheritDoc + */ +class SessionUserHydrator implements SessionUserHydratorInterface +{ + /** + * @var RoleFactory + */ + private $roleFactory; + + /** + * @param RoleFactory $roleFactory + */ + public function __construct(RoleFactory $roleFactory) + { + $this->roleFactory = $roleFactory; + } + + /** + * @inheritDoc + */ + public function extract(User $user): array + { + return ['data' => $user->getData(), 'role_data' => $user->getRole()->getData()]; + } + + /** + * @inheritDoc + */ + public function hydrate(User $target, array $data): void + { + $target->setData($data['data']); + /** @var Role $role */ + $role = $this->roleFactory->create(); + $role->setData($data['role_data']); + $target->setData('extracted_role', $role); + $target->getRole(); + } +} diff --git a/app/code/Magento/Backend/Spi/SessionAclHydratorInterface.php b/app/code/Magento/Backend/Spi/SessionAclHydratorInterface.php new file mode 100644 index 0000000000000..7227cc92fcc8e --- /dev/null +++ b/app/code/Magento/Backend/Spi/SessionAclHydratorInterface.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Backend\Spi; + +use Magento\Framework\Acl; + +/** + * Extract/hydrate user's ACL data to/from session. + */ +interface SessionAclHydratorInterface +{ + /** + * Extract ACL data to store in session. + * + * @param Acl $acl + * @return array Array of scalars. + */ + public function extract(Acl $acl): array; + + /** + * Fill ACL object with data from session. + * + * @param Acl $target + * @param array $data Data from session. + * @return void + */ + public function hydrate(Acl $target, array $data): void; +} diff --git a/app/code/Magento/Backend/Spi/SessionUserHydratorInterface.php b/app/code/Magento/Backend/Spi/SessionUserHydratorInterface.php new file mode 100644 index 0000000000000..211c7b01df3be --- /dev/null +++ b/app/code/Magento/Backend/Spi/SessionUserHydratorInterface.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Backend\Spi; + +use Magento\User\Model\User; + +/** + * Extract/hydrate user data to/from session. + */ +interface SessionUserHydratorInterface +{ + /** + * Extract user data to store in session. + * + * @param User $user + * @return array Array of scalars. + */ + public function extract(User $user): array; + + /** + * Fill User object with data from session. + * + * @param User $target + * @param array $data Data from session. + * @return void + */ + public function hydrate(User $target, array $data): void; +} diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminAssertPageTitleActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminAssertPageTitleActionGroup.xml new file mode 100644 index 0000000000000..42ffb4b7421ac --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminAssertPageTitleActionGroup.xml @@ -0,0 +1,17 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAssertPageTitleActionGroup"> + <arguments> + <argument name="title" type="string"/> + </arguments> + <see selector="{{AdminGridHeaders.title}}" userInput="{{title}}" stepKey="assertPageTitle"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminNavigateMenuActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminNavigateMenuActionGroup.xml new file mode 100644 index 0000000000000..8e0f5a067610d --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminNavigateMenuActionGroup.xml @@ -0,0 +1,19 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminNavigateMenuActionGroup"> + <arguments> + <argument name="menuUiId" type="string"/> + <argument name="submenuUiId" type="string"/> + </arguments> + <click selector="{{AdminMenuSection.menuItem(menuUiId)}}" stepKey="clickOnMenuItem"/> + <click selector="{{AdminMenuSection.menuItem(submenuUiId)}}" stepKey="clickOnSubmenuItem"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminDashboardPageIsVisibleActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminDashboardPageIsVisibleActionGroup.xml new file mode 100644 index 0000000000000..1c86a736ac2f1 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminDashboardPageIsVisibleActionGroup.xml @@ -0,0 +1,15 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminDashboardPageIsVisibleActionGroup"> + <seeInCurrentUrl url="{{AdminDashboardPage.url}}" stepKey="seeDashboardUrl"/> + <see userInput="Dashboard" selector="{{AdminHeaderSection.pageTitle}}" stepKey="seeDashboardTitle"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertMessageOnAdminLoginActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertMessageOnAdminLoginActionGroup.xml index ccc7cd24350c5..607fba3736c42 100644 --- a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertMessageOnAdminLoginActionGroup.xml +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertMessageOnAdminLoginActionGroup.xml @@ -13,6 +13,8 @@ <argument name="message" type="string" defaultValue="The account sign-in was incorrect or your account is disabled temporarily. Please wait and try again later." /> <argument name="messageType" type="string" defaultValue="error" /> </arguments> + + <waitForElementVisible selector="{{AdminLoginMessagesSection.messageByType(messageType)}}" stepKey="waitForAdminLoginFormMessage" /> <see userInput="{{message}}" selector="{{AdminLoginMessagesSection.messageByType(messageType)}}" stepKey="verifyMessage" /> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/SetWebsiteCountryOptionsToDefaultActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/SetWebsiteCountryOptionsToDefaultActionGroup.xml new file mode 100644 index 0000000000000..4519648eb1d1b --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/SetWebsiteCountryOptionsToDefaultActionGroup.xml @@ -0,0 +1,20 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="SetWebsiteCountryOptionsToDefaultActionGroup"> + <conditionalClick selector="{{CountryOptionsSection.countryOptions}}" dependentSelector="{{CountryOptionsSection.countryOptionsOpen}}" visible="false" stepKey="clickOnStoreInformation3"/> + <waitForElementVisible selector="{{CountryOptionsSection.topDestinations}}" stepKey="waitCheckboxToBeVisible3"/> + <checkOption selector="{{CountryOptionsSection.generalCountryAllowInherit}}" stepKey="setToDefault1"/> + <checkOption selector="{{CountryOptionsSection.generalCountryDefaultInherit}}" stepKey="setToDefault2"/> + <click selector="{{ContentManagementSection.Save}}" stepKey="saveDefaultConfig"/> + <waitForPageLoad stepKey="waitForSavingSystemConfiguration"/> + <see userInput="You saved the configuration." stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Backend/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..4fe600d194e61 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,46 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuContent"> + <data key="pageTitle">Content</data> + <data key="title">Content</data> + <data key="dataUiId">magento-backend-content</data> + </entity> + <entity name="AdminMenuContentDesignSchedule"> + <data key="pageTitle">Store Design Schedule</data> + <data key="title">Schedule</data> + <data key="dataUiId">magento-backend-system-design-schedule</data> + </entity> + <entity name="AdminMenuDashboard"> + <data key="pageTitle">Dashboard</data> + <data key="title">Dashboard</data> + <data key="dataUiId">magento-backend-dashboard</data> + </entity> + <entity name="AdminMenuStores"> + <data key="pageTitle">Stores</data> + <data key="title">Stores</data> + <data key="dataUiId">magento-backend-stores</data> + </entity> + <entity name="AdminMenuStoresSettingsAllStores"> + <data key="pageTitle">Stores</data> + <data key="title">All Stores</data> + <data key="dataUiId">magento-backend-system-store</data> + </entity> + <entity name="AdminMenuStoresSettingsConfiguration"> + <data key="pageTitle">Configuration</data> + <data key="title">Configuration</data> + <data key="dataUiId">magento-config-system-config</data> + </entity> + <entity name="AdminMenuSystemToolsCacheManagement"> + <data key="pageTitle">Cache Management</data> + <data key="title">Cache Management</data> + <data key="dataUiId">magento-backend-system-cache</data> + </entity> +</entities> diff --git a/app/code/Magento/Backend/Test/Mftf/Page/AdminForgotPasswordPage.xml b/app/code/Magento/Backend/Test/Mftf/Page/AdminForgotPasswordPage.xml new file mode 100644 index 0000000000000..84af56d102d84 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Page/AdminForgotPasswordPage.xml @@ -0,0 +1,14 @@ +<?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="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminForgotPasswordPage" url="admin/auth/forgotpassword/" area="admin" module="Magento_Backend"> + <section name="AdminForgotPasswordFormSection"/> + </page> +</pages> diff --git a/app/code/Magento/Backend/Test/Mftf/Page/AdminLoginPage.xml b/app/code/Magento/Backend/Test/Mftf/Page/AdminLoginPage.xml index b68b9914186f6..78226d79273d9 100644 --- a/app/code/Magento/Backend/Test/Mftf/Page/AdminLoginPage.xml +++ b/app/code/Magento/Backend/Test/Mftf/Page/AdminLoginPage.xml @@ -9,6 +9,7 @@ <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminLoginPage" url="admin" area="admin" module="Magento_Backend"> + <section name="AdminLoginMessagesSection"/> <section name="AdminLoginFormSection"/> </page> </pages> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminDashboardSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminDashboardSection.xml new file mode 100644 index 0000000000000..664c335a4cfc6 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminDashboardSection.xml @@ -0,0 +1,19 @@ +<?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="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminDashboardSection"> + <element name="dashboardDiagramContent" type="button" selector="#diagram_tab_content"/> + <element name="dashboardDiagramOrderContentTab" type="block" selector="#diagram_tab_orders_content"/> + <element name="dashboardDiagramAmounts" type="button" selector="#diagram_tab_amounts"/> + <element name="dashboardDiagramAmountsContentTab" type="block" selector="#diagram_tab_amounts_content"/> + <element name="dashboardDiagramTotals" type="text" selector="#diagram_tab_amounts_content"/> + <element name="dashboardTotals" type="text" selector="//*[@class='dashboard-totals-label' and contains(text(), '{{columnName}}')]/../*[@class='dashboard-totals-value']" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminForgotPasswordFormSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminForgotPasswordFormSection.xml new file mode 100644 index 0000000000000..efaca22123354 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminForgotPasswordFormSection.xml @@ -0,0 +1,15 @@ +<?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="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminForgotPasswordFormSection"> + <element name="email" type="input" selector="#login-form input[name='email']"/> + <element name="retrievePasswordButton" type="button" selector="#login-form button[type='submit']" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminLoginFormSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminLoginFormSection.xml index 3b10fac7bb9dc..bd65dea89abc2 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminLoginFormSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminLoginFormSection.xml @@ -12,5 +12,6 @@ <element name="username" type="input" selector="#username"/> <element name="password" type="input" selector="#login"/> <element name="signIn" type="button" selector=".actions .action-primary" timeout="30"/> + <element name="forgotPasswordLink" type="button" selector=".action-forgotpassword" timeout="10"/> </section> </sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminMenuSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminMenuSection.xml index 278a738b60f0f..8498ad8c52e41 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminMenuSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminMenuSection.xml @@ -21,5 +21,8 @@ <element name="marketing" type="button" selector="//li[@id='menu-magento-backend-marketing']"/> <element name="system" type="button" selector="//li[@id='menu-magento-backend-system']"/> <element name="findPartners" type="button" selector="//li[@id='menu-magento-marketplace-partners']"/> + + <!-- Navigate menu selectors --> + <element name="menuItem" type="button" selector="li[data-ui-id='menu-{{dataUiId}}']" parameterized="true" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/CountryOptionsSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/CountryOptionsSection.xml new file mode 100644 index 0000000000000..2e2e5aec35ecd --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/CountryOptionsSection.xml @@ -0,0 +1,18 @@ +<?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="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="CountryOptionsSection"> + <element name="allowedCountries" type="select" selector="#general_country_allow"/> + <element name="notAllowedCountry" type="button" selector="#general_country_allow option:not([selected])"/> + <element name="generalCountryAllowInherit" type="checkbox" selector="#general_country_allow_inherit"/> + <element name="generalCountryDefaultInherit" type="checkbox" selector="#general_country_default_inherit"/> + <element name="generalCountryDefault" type="select" selector="#general_country_default"/> + </section> +</sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminContentScheduleNavigateMenuTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminContentScheduleNavigateMenuTest.xml new file mode 100644 index 0000000000000..bead59653eee8 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminContentScheduleNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminContentScheduleNavigateMenuTest"> + <annotations> + <features value="Backend"/> + <stories value="Menu Navigation"/> + <title value="Admin content schedule navigate menu test"/> + <description value="Admin should be able to navigate to Content > Schedule"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14117"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToContentSchedulePage"> + <argument name="menuUiId" value="{{AdminMenuContent.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuContentDesignSchedule.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuContentDesignSchedule.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminDashboardNavigateMenuTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminDashboardNavigateMenuTest.xml new file mode 100644 index 0000000000000..33561d7c3b03e --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminDashboardNavigateMenuTest.xml @@ -0,0 +1,33 @@ +<?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="AdminDashboardNavigateMenuTest"> + <annotations> + <features value="Backend"/> + <stories value="Menu Navigation"/> + <title value="Admin dashboard navigate menu test"/> + <description value="Admin should be able to navigate to Dashboard"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14116"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <click selector="{{AdminMenuSection.menuItem(AdminMenuDashboard.dataUiId)}}" stepKey="clickOnMenuItem"/> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuDashboard.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminDashboardWithChartsChart.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminDashboardWithChartsChart.xml new file mode 100644 index 0000000000000..55cb5a71505a5 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminDashboardWithChartsChart.xml @@ -0,0 +1,122 @@ +<?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="AdminDashboardWithChartsTest"> + <annotations> + <features value="Backend"/> + <title value="Google chart on Magento dashboard"/> + <description value="Google chart on Magento dashboard page is not broken"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-98934"/> + <useCaseId value="MAGETWO-98584"/> + <group value="backend"/> + </annotations> + <before> + <magentoCLI command="config:set admin/dashboard/enable_charts 1" stepKey="setEnableCharts" /> + <createData entity="SimpleProduct2" stepKey="createProduct"> + <field key="price">150</field> + </createData> + <createData entity="Simple_US_Customer" stepKey="createCustomer"> + <field key="firstname">John1</field> + <field key="lastname">Doe1</field> + </createData> + </before> + <after> + <!-- Reset admin order filter --> + <comment userInput="Reset admin order filter" stepKey="resetAdminOrderFilter"/> + <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="clearOrderFilters"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingOrderGrid"/> + <magentoCLI command="config:set admin/dashboard/enable_charts 0" stepKey="setDisableChartsAsDefault" /> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Login as admin --> + <comment userInput="Login as admin" stepKey="adminLogin"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!-- Grab quantity value --> + <comment userInput="Grab quantity value from dashboard" stepKey="grabQuantityFromDashboard"/> + <grabTextFrom selector="{{AdminDashboardSection.dashboardTotals('Quantity')}}" stepKey="grabStartQuantity"/> + <!-- Login as customer --> + <comment userInput="Login as customer" stepKey="loginAsCustomer"/> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="customerLogin"> + <argument name="Customer" value="$$createCustomer$$" /> + </actionGroup> + <!-- Add Product to Shopping Cart--> + <comment userInput="Add product to the shopping cart" stepKey="addProductToCart"/> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToSimpleProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$$createProduct.name$$"/> + </actionGroup> + <!--Go to Checkout--> + <comment userInput="Go to checkout" stepKey="goToCheckout"/> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingCheckoutPageWithShippingMethod"/> + <click selector="{{CheckoutShippingMethodsSection.firstShippingMethod}}" stepKey="selectFirstShippingMethod"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask1"/> + <waitForElement selector="{{CheckoutShippingMethodsSection.next}}" time="30" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext"/> + <!-- Checkout select Check/Money Order payment --> + <comment userInput="Select Check/Money payment" stepKey="checkoutSelectCheckMoneyPayment"/> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment"/> + <!-- Place Order --> + <comment userInput="Place order" stepKey="placeOrder"/> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> + <see selector="{{CheckoutSuccessMainSection.successTitle}}" userInput="Thank you for your purchase!" stepKey="seeSuccessTitle"/> + <see selector="{{CheckoutSuccessMainSection.orderNumberText}}" userInput="Your order number is: " stepKey="seeOrderNumber"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + <!-- Search for Order in the order grid --> + <comment userInput="Search for Order in the order grid" stepKey="searchOrderInGrid"/> + <actionGroup ref="filterOrderGridById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$grabOrderNumber"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForSearchingOrder"/> + <!-- Create invoice --> + <comment userInput="Create invoice" stepKey="createInvoice"/> + <click selector="{{AdminOrdersGridSection.firstRow}}" stepKey="clickOrderRow"/> + <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceButton"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Invoice" stepKey="seeNewInvoiceInPageTitle" after="clickInvoiceButton"/> + <see selector="{{AdminInvoiceTotalSection.total('Subtotal')}}" userInput="$150.00" stepKey="seeCorrectGrandTotal"/> + <click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="clickSubmitInvoice"/> + <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="The invoice has been created." stepKey="seeSuccessInvoiceMessage"/> + <!--Create Shipment for the order--> + <comment userInput="Create Shipment for the order" stepKey="createShipmentForOrder"/> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="onOrdersPage2"/> + <waitForPageLoad time="30" stepKey="waitForOrderListPageLoading"/> + <click selector="{{AdminOrdersGridSection.firstRow}}" stepKey="openOrderPageForShip"/> + <waitForPageLoad stepKey="waitForOrderDetailsPage"/> + <click selector="{{AdminOrderDetailsMainActionsSection.ship}}" stepKey="clickShipAction"/> + <waitForPageLoad stepKey="waitForShipmentPagePage"/> + <seeInCurrentUrl url="{{AdminShipmentNewPage.url}}" stepKey="seeOrderShipmentUrl"/> + <!--Submit Shipment--> + <comment userInput="Submit Shipment" stepKey="submitShipment"/> + <click selector="{{AdminShipmentMainActionsSection.submitShipment}}" stepKey="clickSubmitShipment"/> + <waitForPageLoad stepKey="waitForShipmentSubmit"/> + <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="The shipment has been created." stepKey="seeShipmentCreateSuccess"/> + <!-- Go to dashboard page --> + <comment userInput="Go to dashboard page" stepKey="goToDashboardPage"/> + <amOnPage url="{{AdminDashboardPage.url}}" stepKey="amOnDashboardPage"/> + <waitForPageLoad stepKey="waitForDashboardPageLoad4"/> + <!-- Grab quantity value --> + <comment userInput="Grab quantity value from dashboard at the end" stepKey="grabQuantityFromDashboardAtTheEnd"/> + <grabTextFrom selector="{{AdminDashboardSection.dashboardTotals('Quantity')}}" stepKey="grabEndQuantity"/> + <!-- Assert that page is not broken --> + <comment userInput="Assert that dashboard page is not broken" stepKey="assertDashboardPageIsNotBroken"/> + <seeElement selector="{{AdminDashboardSection.dashboardDiagramOrderContentTab}}" stepKey="seeOrderContentTab"/> + <seeElement selector="{{AdminDashboardSection.dashboardDiagramContent}}" stepKey="seeDiagramContent"/> + <click selector="{{AdminDashboardSection.dashboardDiagramAmounts}}" stepKey="clickDashboardAmount"/> + <waitForLoadingMaskToDisappear stepKey="waitForDashboardAmountLoading"/> + <seeElement selector="{{AdminDashboardSection.dashboardDiagramAmountsContentTab}}" stepKey="seeDiagramAmountContent"/> + <seeElement selector="{{AdminDashboardSection.dashboardDiagramTotals}}" stepKey="seeAmountTotals"/> + <dontSeeJsError stepKey="dontSeeJsError"/> + <assertGreaterThan expected="$grabStartQuantity" actual="$grabEndQuantity" stepKey="checkQuantityWasChanged"/> + </test> +</tests> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminStoresAllStoresNavigateMenuTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminStoresAllStoresNavigateMenuTest.xml new file mode 100644 index 0000000000000..7758b387e393b --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminStoresAllStoresNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminStoresAllStoresNavigateMenuTest"> + <annotations> + <features value="Backend"/> + <stories value="Menu Navigation"/> + <title value="Admin stores all stores navigate menu test"/> + <description value="Admin should be able to navigate to Stores > All Stores"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14118"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToStoresAllStoresPage"> + <argument name="menuUiId" value="{{AdminMenuStores.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuStoresSettingsAllStores.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuStoresSettingsAllStores.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminStoresConfigurationNavigateMenuTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminStoresConfigurationNavigateMenuTest.xml new file mode 100644 index 0000000000000..a54269b186ba0 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminStoresConfigurationNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminStoresConfigurationNavigateMenuTest"> + <annotations> + <features value="Backend"/> + <stories value="Menu Navigation"/> + <title value="Admin stores configuration navigate menu test"/> + <description value="Admin should be able to navigate to Stores > Configuration"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14119"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToStoresConfigurationPage"> + <argument name="menuUiId" value="{{AdminMenuStores.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuStoresSettingsConfiguration.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuStoresSettingsConfiguration.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminSystemCacheManagementNavigateMenuTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminSystemCacheManagementNavigateMenuTest.xml new file mode 100644 index 0000000000000..516631c1bd166 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminSystemCacheManagementNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminSystemCacheManagementNavigateMenuTest"> + <annotations> + <features value="Backend"/> + <stories value="Menu Navigation"/> + <title value="Admin system cache management navigate menu test"/> + <description value="Admin should be able to navigate to System > Cache Management"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14120"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToSystemCacheManagementPage"> + <argument name="menuUiId" value="{{AdminMenuSystem.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSystemToolsCacheManagement.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSystemToolsCacheManagement.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminUserLoginWithStoreCodeInUrlTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminUserLoginWithStoreCodeInUrlTest.xml new file mode 100644 index 0000000000000..5485dcaea33ee --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminUserLoginWithStoreCodeInUrlTest.xml @@ -0,0 +1,30 @@ +<?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="AdminUserLoginWithStoreCodeInUrlTest"> + <annotations> + <features value="Backend"/> + <title value="Admin panel should be accessible with Add Store Code to URL setting enabled"/> + <description value="Admin panel should be accessible with Add Store Code to URL setting enabled"/> + <testCaseId value="MC-14279" /> + <group value="backend"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <magentoCLI command="config:set {{StorefrontEnableAddStoreCodeToUrls.path}} {{StorefrontEnableAddStoreCodeToUrls.value}}" stepKey="addStoreCodeToUrlEnable"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontDisableAddStoreCodeToUrls.path}} {{StorefrontDisableAddStoreCodeToUrls.value}}" stepKey="addStoreCodeToUrlDisable"/> + </after> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="AssertAdminDashboardPageIsVisibleActionGroup" stepKey="seeDashboardPage"/> + </test> +</tests> diff --git a/app/code/Magento/Backend/Test/Unit/Model/Auth/SessionTest.php b/app/code/Magento/Backend/Test/Unit/Model/Auth/SessionTest.php deleted file mode 100644 index f1a4bc355b08e..0000000000000 --- a/app/code/Magento/Backend/Test/Unit/Model/Auth/SessionTest.php +++ /dev/null @@ -1,273 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Backend\Test\Unit\Model\Auth; - -use Magento\Backend\Model\Auth\Session; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; - -/** - * Class SessionTest tests Magento\Backend\Model\Auth\Session - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class SessionTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Backend\App\Config | \PHPUnit_Framework_MockObject_MockObject - */ - protected $config; - - /** - * @var \Magento\Framework\Session\Config | \PHPUnit_Framework_MockObject_MockObject - */ - protected $sessionConfig; - - /** - * @var \Magento\Framework\Stdlib\CookieManagerInterface | \PHPUnit_Framework_MockObject_MockObject - */ - protected $cookieManager; - - /** - * @var \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory | \PHPUnit_Framework_MockObject_MockObject - */ - protected $cookieMetadataFactory; - - /** - * @var \Magento\Framework\Session\Storage | \PHPUnit_Framework_MockObject_MockObject - */ - protected $storage; - - /** - * @var \Magento\Framework\Acl\Builder | \PHPUnit_Framework_MockObject_MockObject - */ - protected $aclBuilder; - - /** - * @var Session - */ - protected $session; - - protected function setUp() - { - $this->cookieMetadataFactory = $this->createPartialMock( - \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory::class, - ['createPublicCookieMetadata'] - ); - - $this->config = $this->createPartialMock(\Magento\Backend\App\Config::class, ['getValue']); - $this->cookieManager = $this->createPartialMock( - \Magento\Framework\Stdlib\Cookie\PhpCookieManager::class, - ['getCookie', 'setPublicCookie'] - ); - $this->storage = $this->createPartialMock( - \Magento\Framework\Session\Storage::class, - ['getUser', 'getAcl', 'setAcl'] - ); - $this->sessionConfig = $this->createPartialMock( - \Magento\Framework\Session\Config::class, - ['getCookiePath', 'getCookieDomain', 'getCookieSecure', 'getCookieHttpOnly'] - ); - $this->aclBuilder = $this->getMockBuilder(\Magento\Framework\Acl\Builder::class) - ->disableOriginalConstructor() - ->getMock(); - $objectManager = new ObjectManager($this); - $this->session = $objectManager->getObject( - \Magento\Backend\Model\Auth\Session::class, - [ - 'config' => $this->config, - 'sessionConfig' => $this->sessionConfig, - 'cookieManager' => $this->cookieManager, - 'cookieMetadataFactory' => $this->cookieMetadataFactory, - 'storage' => $this->storage, - 'aclBuilder' => $this->aclBuilder - ] - ); - } - - protected function tearDown() - { - $this->config = null; - $this->sessionConfig = null; - $this->session = null; - } - - /** - * @dataProvider refreshAclDataProvider - * @param $isUserPassedViaParams - */ - public function testRefreshAcl($isUserPassedViaParams) - { - $aclMock = $this->getMockBuilder(\Magento\Framework\Acl::class)->disableOriginalConstructor()->getMock(); - $this->aclBuilder->expects($this->any())->method('getAcl')->willReturn($aclMock); - $userMock = $this->getMockBuilder(\Magento\User\Model\User::class) - ->setMethods(['getReloadAclFlag', 'setReloadAclFlag', 'unsetData', 'save']) - ->disableOriginalConstructor() - ->getMock(); - $userMock->expects($this->any())->method('getReloadAclFlag')->willReturn(true); - $userMock->expects($this->once())->method('setReloadAclFlag')->with('0')->willReturnSelf(); - $userMock->expects($this->once())->method('save'); - $this->storage->expects($this->once())->method('setAcl')->with($aclMock); - $this->storage->expects($this->any())->method('getAcl')->willReturn($aclMock); - if ($isUserPassedViaParams) { - $this->session->refreshAcl($userMock); - } else { - $this->storage->expects($this->once())->method('getUser')->willReturn($userMock); - $this->session->refreshAcl(); - } - $this->assertSame($aclMock, $this->session->getAcl()); - } - - /** - * @return array - */ - public function refreshAclDataProvider() - { - return [ - 'User set via params' => [true], - 'User set to session object' => [false] - ]; - } - - public function testIsLoggedInPositive() - { - $user = $this->createPartialMock(\Magento\User\Model\User::class, ['getId', '__wakeup']); - $user->expects($this->once()) - ->method('getId') - ->will($this->returnValue(1)); - - $this->storage->expects($this->any()) - ->method('getUser') - ->will($this->returnValue($user)); - - $this->assertTrue($this->session->isLoggedIn()); - } - - public function testProlong() - { - $name = session_name(); - $cookie = 'cookie'; - $lifetime = 900; - $path = '/'; - $domain = 'magento2'; - $secure = true; - $httpOnly = true; - - $this->config->expects($this->once()) - ->method('getValue') - ->with(\Magento\Backend\Model\Auth\Session::XML_PATH_SESSION_LIFETIME) - ->willReturn($lifetime); - $cookieMetadata = $this->createMock(\Magento\Framework\Stdlib\Cookie\PublicCookieMetadata::class); - $cookieMetadata->expects($this->once()) - ->method('setDuration') - ->with($lifetime) - ->will($this->returnSelf()); - $cookieMetadata->expects($this->once()) - ->method('setPath') - ->with($path) - ->will($this->returnSelf()); - $cookieMetadata->expects($this->once()) - ->method('setDomain') - ->with($domain) - ->will($this->returnSelf()); - $cookieMetadata->expects($this->once()) - ->method('setSecure') - ->with($secure) - ->will($this->returnSelf()); - $cookieMetadata->expects($this->once()) - ->method('setHttpOnly') - ->with($httpOnly) - ->will($this->returnSelf()); - - $this->cookieMetadataFactory->expects($this->once()) - ->method('createPublicCookieMetadata') - ->will($this->returnValue($cookieMetadata)); - - $this->cookieManager->expects($this->once()) - ->method('getCookie') - ->with($name) - ->will($this->returnValue($cookie)); - $this->cookieManager->expects($this->once()) - ->method('setPublicCookie') - ->with($name, $cookie, $cookieMetadata); - - $this->sessionConfig->expects($this->once()) - ->method('getCookiePath') - ->will($this->returnValue($path)); - $this->sessionConfig->expects($this->once()) - ->method('getCookieDomain') - ->will($this->returnValue($domain)); - $this->sessionConfig->expects($this->once()) - ->method('getCookieSecure') - ->will($this->returnValue($secure)); - $this->sessionConfig->expects($this->once()) - ->method('getCookieHttpOnly') - ->will($this->returnValue($httpOnly)); - - $this->session->prolong(); - - $this->assertLessThanOrEqual(time(), $this->session->getUpdatedAt()); - } - - /** - * @dataProvider isAllowedDataProvider - * @param bool $isUserDefined - * @param bool $isAclDefined - * @param bool $isAllowed - * @param true $expectedResult - */ - public function testIsAllowed($isUserDefined, $isAclDefined, $isAllowed, $expectedResult) - { - $userAclRole = 'userAclRole'; - if ($isAclDefined) { - $aclMock = $this->getMockBuilder(\Magento\Framework\Acl::class)->disableOriginalConstructor()->getMock(); - $this->storage->expects($this->any())->method('getAcl')->willReturn($aclMock); - } - if ($isUserDefined) { - $userMock = $this->getMockBuilder(\Magento\User\Model\User::class)->disableOriginalConstructor()->getMock(); - $this->storage->expects($this->once())->method('getUser')->willReturn($userMock); - } - if ($isAclDefined && $isUserDefined) { - $userMock->expects($this->any())->method('getAclRole')->willReturn($userAclRole); - $aclMock->expects($this->once())->method('isAllowed')->with($userAclRole)->willReturn($isAllowed); - } - - $this->assertEquals($expectedResult, $this->session->isAllowed('resource')); - } - - /** - * @return array - */ - public function isAllowedDataProvider() - { - return [ - "Negative: User not defined" => [false, true, true, false], - "Negative: Acl not defined" => [true, false, true, false], - "Negative: Permission denied" => [true, true, false, false], - "Positive: Permission granted" => [true, true, false, false], - ]; - } - - /** - * @dataProvider firstPageAfterLoginDataProvider - * @param bool $isFirstPageAfterLogin - */ - public function testFirstPageAfterLogin($isFirstPageAfterLogin) - { - $this->session->setIsFirstPageAfterLogin($isFirstPageAfterLogin); - $this->assertEquals($isFirstPageAfterLogin, $this->session->isFirstPageAfterLogin()); - } - - /** - * @return array - */ - public function firstPageAfterLoginDataProvider() - { - return [ - 'First page after login' => [true], - 'Not first page after login' => [false], - ]; - } -} diff --git a/app/code/Magento/Backend/Test/Unit/Model/Authorization/RoleLocatorTest.php b/app/code/Magento/Backend/Test/Unit/Model/Authorization/RoleLocatorTest.php deleted file mode 100644 index 5b3910e9445f8..0000000000000 --- a/app/code/Magento/Backend/Test/Unit/Model/Authorization/RoleLocatorTest.php +++ /dev/null @@ -1,36 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Backend\Test\Unit\Model\Authorization; - -class RoleLocatorTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Backend\Model\Authorization\RoleLocator - */ - protected $_model; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $_sessionMock = []; - - protected function setUp() - { - $this->_sessionMock = $this->createPartialMock( - \Magento\Backend\Model\Auth\Session::class, - ['getUser', 'getAclRole', 'hasUser'] - ); - $this->_model = new \Magento\Backend\Model\Authorization\RoleLocator($this->_sessionMock); - } - - public function testGetAclRoleIdReturnsCurrentUserAclRoleId() - { - $this->_sessionMock->expects($this->once())->method('hasUser')->will($this->returnValue(true)); - $this->_sessionMock->expects($this->once())->method('getUser')->will($this->returnSelf()); - $this->_sessionMock->expects($this->once())->method('getAclRole')->will($this->returnValue('some_role')); - $this->assertEquals('some_role', $this->_model->getAclRoleId()); - } -} diff --git a/app/code/Magento/Backend/Test/Unit/Model/Locale/ManagerTest.php b/app/code/Magento/Backend/Test/Unit/Model/Locale/ManagerTest.php deleted file mode 100644 index 77eb7cdb34d1f..0000000000000 --- a/app/code/Magento/Backend/Test/Unit/Model/Locale/ManagerTest.php +++ /dev/null @@ -1,127 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Backend\Test\Unit\Model\Locale; - -use Magento\Framework\Locale\Resolver; - -class ManagerTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Backend\Model\Locale\Manager - */ - protected $_model; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\TranslateInterface - */ - protected $_translator; - - /** - * @var \Magento\Backend\Model\Session - */ - protected $_session; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Backend\Model\Auth\Session - */ - protected $_authSession; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Backend\App\ConfigInterface - */ - protected $_backendConfig; - - protected function setUp() - { - $this->_session = $this->createMock(\Magento\Backend\Model\Session::class); - - $this->_authSession = $this->createPartialMock(\Magento\Backend\Model\Auth\Session::class, ['getUser']); - - $this->_backendConfig = $this->getMockForAbstractClass( - \Magento\Backend\App\ConfigInterface::class, - [], - '', - false - ); - - $userMock = new \Magento\Framework\DataObject(); - - $this->_authSession->expects($this->any())->method('getUser')->will($this->returnValue($userMock)); - - $this->_translator = $this->getMockBuilder(\Magento\Framework\TranslateInterface::class) - ->setMethods(['init', 'setLocale']) - ->getMockForAbstractClass(); - - $this->_translator->expects($this->any())->method('setLocale')->will($this->returnValue($this->_translator)); - - $this->_translator->expects($this->any())->method('init')->will($this->returnValue(false)); - - $this->_model = new \Magento\Backend\Model\Locale\Manager( - $this->_session, - $this->_authSession, - $this->_translator, - $this->_backendConfig - ); - } - - /** - * @return array - */ - public function switchBackendInterfaceLocaleDataProvider() - { - return ['case1' => ['locale' => 'de_DE'], 'case2' => ['locale' => 'en_US']]; - } - - /** - * @param string $locale - * @dataProvider switchBackendInterfaceLocaleDataProvider - * @covers \Magento\Backend\Model\Locale\Manager::switchBackendInterfaceLocale - */ - public function testSwitchBackendInterfaceLocale($locale) - { - $this->_model->switchBackendInterfaceLocale($locale); - - $userInterfaceLocale = $this->_authSession->getUser()->getInterfaceLocale(); - $this->assertEquals($userInterfaceLocale, $locale); - - $sessionLocale = $this->_session->getSessionLocale(); - $this->assertEquals($sessionLocale, null); - } - - /** - * @covers \Magento\Backend\Model\Locale\Manager::getUserInterfaceLocale - */ - public function testGetUserInterfaceLocaleDefault() - { - $locale = $this->_model->getUserInterfaceLocale(); - - $this->assertEquals($locale, Resolver::DEFAULT_LOCALE); - } - - /** - * @covers \Magento\Backend\Model\Locale\Manager::getUserInterfaceLocale - */ - public function testGetUserInterfaceLocale() - { - $this->_model->switchBackendInterfaceLocale('de_DE'); - $locale = $this->_model->getUserInterfaceLocale(); - - $this->assertEquals($locale, 'de_DE'); - } - - /** - * @covers \Magento\Backend\Model\Locale\Manager::getUserInterfaceLocale - */ - public function testGetUserInterfaceGeneralLocale() - { - $this->_backendConfig->expects($this->any()) - ->method('getValue') - ->with('general/locale/code') - ->willReturn('test_locale'); - $locale = $this->_model->getUserInterfaceLocale(); - $this->assertEquals($locale, 'test_locale'); - } -} diff --git a/app/code/Magento/Backend/composer.json b/app/code/Magento/Backend/composer.json index f9408768136bb..e54bd136b3494 100644 --- a/app/code/Magento/Backend/composer.json +++ b/app/code/Magento/Backend/composer.json @@ -22,6 +22,7 @@ "magento/module-store": "*", "magento/module-translation": "*", "magento/module-ui": "*", + "magento/module-authorization": "*", "magento/module-user": "*" }, "suggest": { diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index 98b8e702b1c53..65744e56d94ac 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -182,6 +182,10 @@ <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>Minification is not applied in developer mode.</comment> </field> + <field id="move_inline_to_bottom" translate="label" type="select" sortOrder="25" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <label>Move JS code to the bottom of the page</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + </field> </group> <group id="css" translate="label" type="text" sortOrder="110" showInDefault="1" showInWebsite="1" showInStore="1"> <label>CSS Settings</label> @@ -323,11 +327,11 @@ <label>Port (25)</label> <comment>For Windows server only.</comment> </field> - <field id="set_return_path" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="set_return_path" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Set Return-Path</label> <source_model>Magento\Config\Model\Config\Source\Yesnocustom</source_model> </field> - <field id="return_path_email" translate="label" type="text" sortOrder="80" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="return_path_email" translate="label" type="text" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Return-Path Email</label> <validate>validate-email</validate> <backend_model>Magento\Config\Model\Config\Backend\Email\Address</backend_model> diff --git a/app/code/Magento/Backend/etc/di.xml b/app/code/Magento/Backend/etc/di.xml index c526703da9975..41db85b9323a8 100644 --- a/app/code/Magento/Backend/etc/di.xml +++ b/app/code/Magento/Backend/etc/di.xml @@ -198,4 +198,8 @@ <argument name="anchorRenderer" xsi:type="object">Magento\Backend\Block\AnchorRenderer</argument> </arguments> </type> + <preference for="Magento\Backend\Spi\SessionUserHydratorInterface" + type="Magento\Backend\Model\Auth\SessionUserHydrator" /> + <preference for="Magento\Backend\Spi\SessionAclHydratorInterface" + type="Magento\Backend\Model\Auth\SessionAclHydrator" /> </config> diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_block.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_block.xml index 50d210f71025b..6d2ecd8d36a99 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_block.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_cache_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.cache.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">cache_grid</argument> - <argument name="dataSource" xsi:type="object">Magento\Backend\Model\Cache\ResourceModel\Grid\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Backend\Model\Cache\ResourceModel\Grid\Collection</argument> <argument name="pager_visibility" xsi:type="string">0</argument> </arguments> <block class="Magento\Backend\Block\Widget\Grid\Massaction" name="adminhtml.cache.massaction" as="grid.massaction"> diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_grid_block.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_grid_block.xml index b96614f4bd8db..41bfe6e78a2ad 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_grid_block.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_design_grid_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.system.design.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">designGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\Theme\Model\ResourceModel\Design\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Theme\Model\ResourceModel\Design\Collection</argument> <argument name="use_ajax" xsi:type="string">1</argument> <argument name="save_parameters_in_session" xsi:type="string">1</argument> <argument name="grid_url" xsi:type="url" path="*/*/grid"> diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_store_grid_block.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_store_grid_block.xml index 126de5eb4084f..d0c0d8fcbf69b 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_store_grid_block.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_system_store_grid_block.xml @@ -12,7 +12,7 @@ <arguments> <argument name="id" xsi:type="string">storeGrid</argument> <argument name="save_parameters_in_session" xsi:type="string">1</argument> - <argument name="dataSource" xsi:type="object">Magento\Store\Model\ResourceModel\Website\Grid\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Store\Model\ResourceModel\Website\Grid\Collection</argument> </arguments> <block class="Magento\Backend\Block\Widget\Grid\ColumnSet" name="adminhtml.system.store.grid.columnSet" as="grid.columnSet"> <arguments> diff --git a/app/code/Magento/Backup/Controller/Adminhtml/Index/Create.php b/app/code/Magento/Backup/Controller/Adminhtml/Index/Create.php index 99c48b727521a..ee5a56e814837 100644 --- a/app/code/Magento/Backup/Controller/Adminhtml/Index/Create.php +++ b/app/code/Magento/Backup/Controller/Adminhtml/Index/Create.php @@ -58,7 +58,9 @@ public function execute() $this->_coreRegistry->register('backup_manager', $backupManager); if ($this->getRequest()->getParam('maintenance_mode')) { - if (!$this->maintenanceMode->set(true)) { + $this->maintenanceMode->set(true); + + if (!$this->maintenanceMode->isOn()) { $response->setError( __( 'You need more permissions to activate maintenance mode right now.' diff --git a/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php b/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php index 0451f6ed09bd1..7f450e7e313cc 100644 --- a/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php +++ b/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php @@ -6,13 +6,16 @@ */ namespace Magento\Backup\Controller\Adminhtml\Index; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Filesystem; /** + * Backup rollback controller. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Rollback extends \Magento\Backup\Controller\Adminhtml\Index +class Rollback extends \Magento\Backup\Controller\Adminhtml\Index implements HttpPostActionInterface { /** * Rollback Action @@ -82,7 +85,9 @@ public function execute() } if ($this->getRequest()->getParam('maintenance_mode')) { - if (!$this->maintenanceMode->set(true)) { + $this->maintenanceMode->set(true); + + if (!$this->maintenanceMode->isOn()) { $response->setError( __( 'You need more permissions to activate maintenance mode right now.' @@ -122,6 +127,7 @@ public function execute() $adminSession->destroy(); $response->setRedirectUrl($this->getUrl('*')); + // phpcs:disable Magento2.Exceptions.ThrowCatch } catch (\Magento\Framework\Backup\Exception\CantLoadSnapshot $e) { $errorMsg = __('We can\'t find the backup file.'); } catch (\Magento\Framework\Backup\Exception\FtpConnectionFailed $e) { diff --git a/app/code/Magento/Backup/etc/adminhtml/system.xml b/app/code/Magento/Backup/etc/adminhtml/system.xml index 90f6fa861b40f..aa6635b4dde4a 100644 --- a/app/code/Magento/Backup/etc/adminhtml/system.xml +++ b/app/code/Magento/Backup/etc/adminhtml/system.xml @@ -26,6 +26,7 @@ <label>Scheduled Backup Type</label> <depends> <field id="enabled">1</field> + <field id="functionality_enabled">1</field> </depends> <source_model>Magento\Backup\Model\Config\Source\Type</source_model> </field> @@ -33,12 +34,14 @@ <label>Start Time</label> <depends> <field id="enabled">1</field> + <field id="functionality_enabled">1</field> </depends> </field> <field id="frequency" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Frequency</label> <depends> <field id="enabled">1</field> + <field id="functionality_enabled">1</field> </depends> <source_model>Magento\Cron\Model\Config\Source\Frequency</source_model> <backend_model>Magento\Backup\Model\Config\Backend\Cron</backend_model> @@ -48,6 +51,7 @@ <comment>Please put your store into maintenance mode during backup.</comment> <depends> <field id="enabled">1</field> + <field id="functionality_enabled">1</field> </depends> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> diff --git a/app/code/Magento/Backup/view/adminhtml/layout/backup_index_block.xml b/app/code/Magento/Backup/view/adminhtml/layout/backup_index_block.xml index e17618b97e21f..e3e984d933f25 100644 --- a/app/code/Magento/Backup/view/adminhtml/layout/backup_index_block.xml +++ b/app/code/Magento/Backup/view/adminhtml/layout/backup_index_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.system.backup.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">backupsGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\Backup\Model\Fs\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Backup\Model\Fs\Collection</argument> <argument name="default_sort" xsi:type="string">time</argument> <argument name="default_dir" xsi:type="string">desc</argument> </arguments> diff --git a/app/code/Magento/Braintree/Controller/Paypal/PlaceOrder.php b/app/code/Magento/Braintree/Controller/Paypal/PlaceOrder.php index 418cb93900610..ea8a44a1122b4 100644 --- a/app/code/Magento/Braintree/Controller/Paypal/PlaceOrder.php +++ b/app/code/Magento/Braintree/Controller/Paypal/PlaceOrder.php @@ -75,7 +75,7 @@ public function execute() $this->logger->critical($e); $this->messageManager->addExceptionMessage( $e, - 'The order #' . $quote->getReservedOrderId() . ' cannot be processed.' + __('The order #%1 cannot be processed.', $quote->getReservedOrderId()) ); } diff --git a/app/code/Magento/Braintree/Controller/Paypal/Review.php b/app/code/Magento/Braintree/Controller/Paypal/Review.php index eb2de7c7b6e39..2923db6fa88c3 100644 --- a/app/code/Magento/Braintree/Controller/Paypal/Review.php +++ b/app/code/Magento/Braintree/Controller/Paypal/Review.php @@ -14,6 +14,7 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Payment\Model\Method\Logger; /** * Class Review @@ -25,6 +26,11 @@ class Review extends AbstractAction implements HttpPostActionInterface, HttpGetA */ private $quoteUpdater; + /** + * @var Logger + */ + private $logger; + /** * @var string */ @@ -37,15 +43,18 @@ class Review extends AbstractAction implements HttpPostActionInterface, HttpGetA * @param Config $config * @param Session $checkoutSession * @param QuoteUpdater $quoteUpdater + * @param Logger $logger */ public function __construct( Context $context, Config $config, Session $checkoutSession, - QuoteUpdater $quoteUpdater + QuoteUpdater $quoteUpdater, + Logger $logger ) { parent::__construct($context, $config, $checkoutSession); $this->quoteUpdater = $quoteUpdater; + $this->logger = $logger; } /** @@ -57,6 +66,7 @@ public function execute() $this->getRequest()->getPostValue('result', '{}'), true ); + $this->logger->debug($requestData); $quote = $this->checkoutSession->getQuote(); try { diff --git a/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php b/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php index aa23fa767d1ed..ae2b1b1423640 100644 --- a/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php +++ b/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php @@ -123,8 +123,8 @@ private function updateShippingAddress(Quote $quote, array $details) { $shippingAddress = $quote->getShippingAddress(); - $shippingAddress->setLastname($details['lastName']); - $shippingAddress->setFirstname($details['firstName']); + $shippingAddress->setLastname($this->getShippingRecipientLastName($details)); + $shippingAddress->setFirstname($this->getShippingRecipientFirstName($details)); $shippingAddress->setEmail($details['email']); $shippingAddress->setCollectShippingRates(true); @@ -188,4 +188,30 @@ private function updateAddressData(Address $address, array $addressData) $address->setSameAsBilling(false); $address->setCustomerAddressId(null); } + + /** + * Returns shipping recipient first name. + * + * @param array $details + * @return string + */ + private function getShippingRecipientFirstName(array $details) + { + return isset($details['shippingAddress']['recipientName']) + ? explode(' ', $details['shippingAddress']['recipientName'], 2)[0] + : $details['firstName']; + } + + /** + * Returns shipping recipient last name. + * + * @param array $details + * @return string + */ + private function getShippingRecipientLastName(array $details) + { + return isset($details['shippingAddress']['recipientName']) + ? explode(' ', $details['shippingAddress']['recipientName'], 2)[1] + : $details['lastName']; + } } diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml b/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml index f27477ce8a672..a781841e0a77b 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml @@ -81,16 +81,12 @@ <actionGroup ref="LoggedInCheckoutFillNewBillingAddressActionGroup" stepKey="LoggedInCheckoutFillNewBillingAddressActionGroup1"> <argument name="Address" value="US_Address_NY"/> </actionGroup> - <click selector="{{CheckoutPaymentSection.addressAction('Save Address')}}" stepKey="SaveAddress"/> + <click selector="{{CheckoutPaymentSection.addressAction('Ship here')}}" stepKey="SaveAddress"/> <waitForPageLoad stepKey="waitForPageLoad9"/> <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext1"/> <waitForPageLoad stepKey="waitForPageLoad10"/> <click selector="{{BraintreeConfigurationPaymentSection.paymentMethod}}" stepKey="SelectBraintreePaymentMethod1"/> <waitForPageLoad stepKey="waitForPageLoad11"/> - <click selector="{{CheckoutPaymentSection.shippingAndBillingAddressSame}}" stepKey="UncheckCheckBox"/> - - <click selector="{{CheckoutShippingSection.updateAddress}}" stepKey="clickToUpdate"/> - <waitForPageLoad stepKey="waitForPageLoad12"/> <!--Place order--> <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="PlaceOrder1"/> <waitForPageLoad stepKey="waitForPageLoad13"/> diff --git a/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/ReviewTest.php b/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/ReviewTest.php index 609b7f21dbf87..d68838bafbf0e 100644 --- a/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/ReviewTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/ReviewTest.php @@ -6,6 +6,7 @@ namespace Magento\Braintree\Test\Unit\Controller\Paypal; +use Magento\Payment\Model\Method\Logger; use Magento\Quote\Model\Quote; use Magento\Framework\View\Layout; use Magento\Checkout\Model\Session; @@ -65,6 +66,11 @@ class ReviewTest extends \PHPUnit\Framework\TestCase */ private $review; + /** + * @var Logger|\PHPUnit_Framework_MockObject_MockObject + */ + private $loggerMock; + protected function setUp() { /** @var Context|\PHPUnit_Framework_MockObject_MockObject $contextMock */ @@ -88,6 +94,9 @@ protected function setUp() ->getMock(); $this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class) ->getMockForAbstractClass(); + $this->loggerMock = $this->getMockBuilder(Logger::class) + ->disableOriginalConstructor() + ->getMock(); $contextMock->expects(self::once()) ->method('getRequest') @@ -103,7 +112,8 @@ protected function setUp() $contextMock, $this->configMock, $this->checkoutSessionMock, - $this->quoteUpdaterMock + $this->quoteUpdaterMock, + $this->loggerMock ); } diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php index a2b5380d2884b..c2678d1c78437 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php @@ -165,7 +165,7 @@ private function getDetails(): array 'region' => 'IL', 'postalCode' => '60618', 'countryCodeAlpha2' => 'US', - 'recipientName' => 'John Doe', + 'recipientName' => 'Jane Smith', ], 'billingAddress' => [ 'streetAddress' => '123 Billing Street', @@ -186,9 +186,9 @@ private function getDetails(): array private function updateShippingAddressStep(array $details): void { $this->shippingAddress->method('setLastname') - ->with($details['lastName']); + ->with('Smith'); $this->shippingAddress->method('setFirstname') - ->with($details['firstName']); + ->with('Jane'); $this->shippingAddress->method('setEmail') ->with($details['email']); $this->shippingAddress->method('setCollectShippingRates') diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/hosted-fields.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/hosted-fields.js index 05c09abdb7b2e..9e496e43b27c5 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/hosted-fields.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/hosted-fields.js @@ -10,8 +10,9 @@ define([ 'Magento_Braintree/js/view/payment/method-renderer/cc-form', 'Magento_Braintree/js/validator', 'Magento_Vault/js/view/payment/vault-enabler', - 'mage/translate' -], function ($, Component, validator, VaultEnabler, $t) { + 'mage/translate', + 'Magento_Checkout/js/model/payment/additional-validators' +], function ($, Component, validator, VaultEnabler, $t, additionalValidators) { 'use strict'; return Component.extend({ @@ -154,7 +155,7 @@ define([ * Trigger order placing */ placeOrderClick: function () { - if (this.validateCardType()) { + if (this.validateCardType() && additionalValidators.validate()) { this.isPlaceOrderActionAllowed(false); $(this.getSelector('submit')).trigger('click'); } diff --git a/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Attributes/Extend.php b/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Attributes/Extend.php index 0e21e566d5e75..a768e2450bfe8 100644 --- a/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Attributes/Extend.php +++ b/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Attributes/Extend.php @@ -4,13 +4,13 @@ * See COPYING.txt for license details. */ +namespace Magento\Bundle\Block\Adminhtml\Catalog\Product\Edit\Tab\Attributes; + /** - * Bundle Extended Attribures Block + * Bundle Extended Attribures Block. * - * @author Magento Core Team <core@magentocommerce.com> + * @author Magento Core Team <core@magentocommerce.com> */ -namespace Magento\Bundle\Block\Adminhtml\Catalog\Product\Edit\Tab\Attributes; - class Extend extends \Magento\Catalog\Block\Adminhtml\Form\Renderer\Fieldset\Element { /** @@ -75,7 +75,7 @@ public function getElementHtml() } /** - * Execute method getElementHtml from parrent class + * Execute method getElementHtml from parent class * * @return string */ @@ -85,6 +85,8 @@ public function getParentElementHtml() } /** + * Get options. + * * @return array */ public function getOptions() @@ -106,6 +108,8 @@ public function getOptions() } /** + * Is disabled field. + * * @return bool */ public function isDisabledField() @@ -118,6 +122,8 @@ public function isDisabledField() } /** + * Get product. + * * @return mixed */ public function getProduct() @@ -129,6 +135,8 @@ public function getProduct() } /** + * Get extended element. + * * @param string $switchAttributeCode * @return \Magento\Framework\Data\Form\Element\Select * @throws \Magento\Framework\Exception\LocalizedException diff --git a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php index 7c63af0bd0e2e..7c5a64ca0232f 100644 --- a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php +++ b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php @@ -6,6 +6,8 @@ namespace Magento\Bundle\Block\Catalog\Product\View\Type\Bundle; +use Magento\Catalog\Model\Product; + /** * Bundle option renderer * @api @@ -169,7 +171,7 @@ protected function assignSelection(\Magento\Bundle\Model\Option $option, $select { if (is_array($selectionId)) { $this->_selectedOptions = $selectionId; - } else if ($selectionId && $option->getSelectionById($selectionId)) { + } elseif ($selectionId && $option->getSelectionById($selectionId)) { $this->_selectedOptions = $selectionId; } elseif (!$option->getRequired()) { $this->_selectedOptions = 'None'; @@ -179,7 +181,7 @@ protected function assignSelection(\Magento\Bundle\Model\Option $option, $select /** * Define if selection is selected * - * @param \Magento\Catalog\Model\Product $selection + * @param Product $selection * @return bool */ public function isSelected($selection) @@ -219,7 +221,7 @@ protected function _getSelectedQty() /** * Get product model * - * @return \Magento\Catalog\Model\Product + * @return Product */ public function getProduct() { @@ -232,7 +234,7 @@ public function getProduct() /** * Get bundle option price title. * - * @param \Magento\Catalog\Model\Product $selection + * @param Product $selection * @param bool $includeContainer * @return string */ @@ -254,7 +256,7 @@ public function getSelectionQtyTitlePrice($selection, $includeContainer = true) /** * Get price for selection product * - * @param \Magento\Catalog\Model\Product $selection + * @param Product $selection * @return int|float */ public function getSelectionPrice($selection) @@ -277,7 +279,7 @@ public function getSelectionPrice($selection) /** * Get title price for selection product * - * @param \Magento\Catalog\Model\Product $selection + * @param Product $selection * @param bool $includeContainer * @return string */ @@ -299,7 +301,7 @@ public function getSelectionTitlePrice($selection, $includeContainer = true) */ public function setValidationContainer($elementId, $containerId) { - return; + return ''; } /** @@ -318,7 +320,7 @@ public function setOption(\Magento\Bundle\Model\Option $option) /** * Format price string * - * @param \Magento\Catalog\Model\Product $selection + * @param Product $selection * @param bool $includeContainer * @return string */ diff --git a/app/code/Magento/Bundle/Block/Checkout/Cart/Item/Renderer.php b/app/code/Magento/Bundle/Block/Checkout/Cart/Item/Renderer.php index c75ebc700603b..863f273225693 100644 --- a/app/code/Magento/Bundle/Block/Checkout/Cart/Item/Renderer.php +++ b/app/code/Magento/Bundle/Block/Checkout/Cart/Item/Renderer.php @@ -69,6 +69,7 @@ public function __construct( /** * Overloaded method for getting list of bundle options + * * Caches result in quote item, because it can be used in cart 'recent view' and on same page in cart checkout * * @return array @@ -88,7 +89,7 @@ public function getMessages() $messages = []; $quoteItem = $this->getItem(); - // Add basic messages occuring during this page load + // Add basic messages occurring during this page load $baseMessages = $quoteItem->getMessage(false); if ($baseMessages) { foreach ($baseMessages as $message) { diff --git a/app/code/Magento/Bundle/Block/DataProviders/OptionPriceRenderer.php b/app/code/Magento/Bundle/Block/DataProviders/OptionPriceRenderer.php new file mode 100644 index 0000000000000..058b3a981b52f --- /dev/null +++ b/app/code/Magento/Bundle/Block/DataProviders/OptionPriceRenderer.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Bundle\Block\DataProviders; + +use Magento\Catalog\Model\Product; +use Magento\Catalog\Pricing\Price\TierPrice; +use Magento\Framework\Pricing\Render; +use Magento\Framework\View\Element\Block\ArgumentInterface; +use Magento\Framework\View\LayoutInterface; + +/** + * Provides additional data for bundle options + */ +class OptionPriceRenderer implements ArgumentInterface +{ + /** + * Parent layout of the block + * + * @var LayoutInterface + */ + private $layout; + + /** + * @param LayoutInterface $layout + */ + public function __construct(LayoutInterface $layout) + { + $this->layout = $layout; + } + + /** + * Format tier price string + * + * @param Product $selection + * @param array $arguments + * @return string + */ + public function renderTierPrice(Product $selection, array $arguments = []): string + { + if (!array_key_exists('zone', $arguments)) { + $arguments['zone'] = Render::ZONE_ITEM_OPTION; + } + + $priceHtml = ''; + + /** @var Render $priceRender */ + $priceRender = $this->layout->getBlock('product.price.render.default'); + if ($priceRender !== false) { + $priceHtml = $priceRender->render( + TierPrice::PRICE_CODE, + $selection, + $arguments + ); + } + + return $priceHtml; + } +} diff --git a/app/code/Magento/Bundle/Model/Product/Price.php b/app/code/Magento/Bundle/Model/Product/Price.php index 00b6b2d7a3f5a..0e75ad2dc828c 100644 --- a/app/code/Magento/Bundle/Model/Product/Price.php +++ b/app/code/Magento/Bundle/Model/Product/Price.php @@ -11,8 +11,11 @@ use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory; /** + * Bundle product type price model + * * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @since 100.0.2 */ class Price extends \Magento\Catalog\Model\Product\Type\Price @@ -180,9 +183,9 @@ protected function getBundleSelectionIds(\Magento\Catalog\Model\Product $product /** * Get product final price * - * @param float $qty - * @param \Magento\Catalog\Model\Product $product - * @return float + * @param float $qty + * @param \Magento\Catalog\Model\Product $product + * @return float */ public function getFinalPrice($qty, $product) { @@ -207,9 +210,9 @@ public function getFinalPrice($qty, $product) * Returns final price of a child product * * @param \Magento\Catalog\Model\Product $product - * @param float $productQty + * @param float $productQty * @param \Magento\Catalog\Model\Product $childProduct - * @param float $childProductQty + * @param float $childProductQty * @return float */ public function getChildFinalPrice($product, $productQty, $childProduct, $childProductQty) @@ -220,10 +223,10 @@ public function getChildFinalPrice($product, $productQty, $childProduct, $childP /** * Retrieve Price considering tier price * - * @param \Magento\Catalog\Model\Product $product - * @param string|null $which - * @param bool|null $includeTax - * @param bool $takeTierPrice + * @param \Magento\Catalog\Model\Product $product + * @param string|null $which + * @param bool|null $includeTax + * @param bool $takeTierPrice * @return float|array * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -255,67 +258,68 @@ public function getTotalPrices($product, $which = null, $includeTax = null, $tak foreach ($options as $option) { /* @var $option \Magento\Bundle\Model\Option */ $selections = $option->getSelections(); - if ($selections) { - $selectionMinimalPrices = []; - $selectionMaximalPrices = []; - - foreach ($option->getSelections() as $selection) { - /* @var $selection \Magento\Bundle\Model\Selection */ - if (!$selection->isSalable()) { - /** - * @todo CatalogInventory Show out of stock Products - */ - continue; - } - - $qty = $selection->getSelectionQty(); - - $item = $product->getPriceType() == self::PRICE_TYPE_FIXED ? $product : $selection; - - $selectionMinimalPrices[] = $this->_catalogData->getTaxPrice( - $item, - $this->getSelectionFinalTotalPrice( - $product, - $selection, - 1, - $qty, - true, - $takeTierPrice - ), - $includeTax - ); - $selectionMaximalPrices[] = $this->_catalogData->getTaxPrice( - $item, - $this->getSelectionFinalTotalPrice( - $product, - $selection, - 1, - null, - true, - $takeTierPrice - ), - $includeTax + if (empty($selections)) { + continue; + } + $selectionMinimalPrices = []; + $selectionMaximalPrices = []; + + foreach ($option->getSelections() as $selection) { + /* @var $selection \Magento\Bundle\Model\Selection */ + if (!$selection->isSalable()) { + /** + * @todo CatalogInventory Show out of stock Products + */ + continue; + } + + $qty = $selection->getSelectionQty(); + + $item = $product->getPriceType() == self::PRICE_TYPE_FIXED ? $product : $selection; + + $selectionMinimalPrices[] = $this->_catalogData->getTaxPrice( + $item, + $this->getSelectionFinalTotalPrice( + $product, + $selection, + 1, + $qty, + true, + $takeTierPrice + ), + $includeTax + ); + $selectionMaximalPrices[] = $this->_catalogData->getTaxPrice( + $item, + $this->getSelectionFinalTotalPrice( + $product, + $selection, + 1, + null, + true, + $takeTierPrice + ), + $includeTax + ); + } + + if (count($selectionMinimalPrices)) { + $selMinPrice = min($selectionMinimalPrices); + if ($option->getRequired()) { + $minimalPrice += $selMinPrice; + $minPriceFounded = true; + } elseif (true !== $minPriceFounded) { + $selMinPrice += $minimalPrice; + $minPriceFounded = false === $minPriceFounded ? $selMinPrice : min( + $minPriceFounded, + $selMinPrice ); } - if (count($selectionMinimalPrices)) { - $selMinPrice = min($selectionMinimalPrices); - if ($option->getRequired()) { - $minimalPrice += $selMinPrice; - $minPriceFounded = true; - } elseif (true !== $minPriceFounded) { - $selMinPrice += $minimalPrice; - $minPriceFounded = false === $minPriceFounded ? $selMinPrice : min( - $minPriceFounded, - $selMinPrice - ); - } - - if ($option->isMultiSelection()) { - $maximalPrice += array_sum($selectionMaximalPrices); - } else { - $maximalPrice += max($selectionMaximalPrices); - } + if ($option->isMultiSelection()) { + $maximalPrice += array_sum($selectionMaximalPrices); + } else { + $maximalPrice += max($selectionMaximalPrices); } } } @@ -338,23 +342,25 @@ public function getTotalPrices($product, $which = null, $includeTax = null, $tak $prices[] = $valuePrice; } - if (count($prices)) { - if ($customOption->getIsRequire()) { - $minimalPrice += $this->_catalogData->getTaxPrice($product, min($prices), $includeTax); - } - - $multiTypes = [ - \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX, - \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_MULTIPLE, - ]; - - if (in_array($customOption->getType(), $multiTypes)) { - $maximalValue = array_sum($prices); - } else { - $maximalValue = max($prices); - } - $maximalPrice += $this->_catalogData->getTaxPrice($product, $maximalValue, $includeTax); + if (empty($prices)) { + continue; + } + + if ($customOption->getIsRequire()) { + $minimalPrice += $this->_catalogData->getTaxPrice($product, min($prices), $includeTax); + } + + $multiTypes = [ + \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX, + \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_MULTIPLE, + ]; + + if (in_array($customOption->getType(), $multiTypes)) { + $maximalValue = array_sum($prices); + } else { + $maximalValue = max($prices); } + $maximalPrice += $this->_catalogData->getTaxPrice($product, $maximalValue, $includeTax); } else { $valuePrice = $customOption->getPrice(true); @@ -402,8 +408,8 @@ public function getOptions($product) * * @param \Magento\Catalog\Model\Product $bundleProduct * @param \Magento\Catalog\Model\Product $selectionProduct - * @param float|null $selectionQty - * @param null|bool $multiplyQty Whether to multiply selection's price by its quantity + * @param float|null $selectionQty + * @param null|bool $multiplyQty Whether to multiply selection's price by its quantity * @return float * * @see \Magento\Bundle\Model\Product\Price::getSelectionFinalTotalPrice() @@ -418,7 +424,7 @@ public function getSelectionPrice($bundleProduct, $selectionProduct, $selectionQ * * @param \Magento\Catalog\Model\Product $bundleProduct * @param \Magento\Catalog\Model\Product $selectionProduct - * @param float $qty + * @param float $qty * @return float */ public function getSelectionPreFinalPrice($bundleProduct, $selectionProduct, $qty = null) @@ -427,15 +433,14 @@ public function getSelectionPreFinalPrice($bundleProduct, $selectionProduct, $qt } /** - * Calculate final price of selection - * with take into account tier price + * Calculate final price of selection with take into account tier price * - * @param \Magento\Catalog\Model\Product $bundleProduct - * @param \Magento\Catalog\Model\Product $selectionProduct - * @param float $bundleQty - * @param float $selectionQty - * @param bool $multiplyQty - * @param bool $takeTierPrice + * @param \Magento\Catalog\Model\Product $bundleProduct + * @param \Magento\Catalog\Model\Product $selectionProduct + * @param float $bundleQty + * @param float $selectionQty + * @param bool $multiplyQty + * @param bool $takeTierPrice * @return float */ public function getSelectionFinalTotalPrice( @@ -454,7 +459,11 @@ public function getSelectionFinalTotalPrice( } if ($bundleProduct->getPriceType() == self::PRICE_TYPE_DYNAMIC) { - $price = $selectionProduct->getFinalPrice($takeTierPrice ? $selectionQty : 1); + $totalQty = $bundleQty * $selectionQty; + if (!$takeTierPrice || $totalQty === 0) { + $totalQty = 1; + } + $price = $selectionProduct->getFinalPrice($totalQty); } else { if ($selectionProduct->getSelectionPriceType()) { // percent @@ -485,10 +494,10 @@ public function getSelectionFinalTotalPrice( /** * Apply tier price for bundle * - * @param \Magento\Catalog\Model\Product $product - * @param float $qty - * @param float $finalPrice - * @return float + * @param \Magento\Catalog\Model\Product $product + * @param float $qty + * @param float $finalPrice + * @return float */ protected function _applyTierPrice($product, $qty, $finalPrice) { @@ -509,9 +518,9 @@ protected function _applyTierPrice($product, $qty, $finalPrice) /** * Get product tier price by qty * - * @param float $qty - * @param \Magento\Catalog\Model\Product $product - * @return float|array + * @param float $qty + * @param \Magento\Catalog\Model\Product $product + * @return float|array * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -605,11 +614,11 @@ public function getTierPrice($qty, $product) /** * Calculate and apply special price * - * @param float $finalPrice - * @param float $specialPrice + * @param float $finalPrice + * @param float $specialPrice * @param string $specialPriceFrom * @param string $specialPriceTo - * @param mixed $store + * @param mixed $store * @return float */ public function calculateSpecialPrice( @@ -634,7 +643,7 @@ public function calculateSpecialPrice( * * @param /Magento/Catalog/Model/Product $bundleProduct * @param float|string $price - * @param int $bundleQty + * @param int $bundleQty * @return float */ public function getLowestPrice($bundleProduct, $price, $bundleQty = 1) diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminCreateApiBundleProductActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminCreateApiBundleProductActionGroup.xml index ad9a8253e910c..4cd16320d3d78 100644 --- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminCreateApiBundleProductActionGroup.xml +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminCreateApiBundleProductActionGroup.xml @@ -106,4 +106,62 @@ <requiredEntity createDataKey="simpleProduct4"/> </createData> </actionGroup> + <actionGroup name="AdminCreateApiDynamicBundleProductAllOptionTypesActionGroup"> + <arguments> + <argument name="productName" defaultValue="Api Dynamic Bundle Product" type="string"/> + </arguments> + <!-- Create simple products --> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">20</field> + </createData> + <!-- Create Bundle product --> + <createData entity="ApiBundleProduct" stepKey="createBundleProduct"> + <field key="name">{{productName}}</field> + </createData> + <createData entity="DropDownBundleOption" stepKey="createDropDownBundleOption"> + <requiredEntity createDataKey="createBundleProduct"/> + <field key="title">Drop-down Option</field> + </createData> + <createData entity="RadioButtonsOption" stepKey="createBundleRadioButtonsOption"> + <requiredEntity createDataKey="createBundleProduct"/> + <field key="title">Radio Buttons Option</field> + </createData> + <createData entity="CheckboxOption" stepKey="createBundleCheckboxOption"> + <requiredEntity createDataKey="createBundleProduct"/> + <field key="title">Checkbox Option</field> + </createData> + <createData entity="ApiBundleLink" stepKey="linkCheckboxOptionToProduct1"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleCheckboxOption"/> + <requiredEntity createDataKey="simpleProduct1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkCheckboxOptionToProduct2"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleCheckboxOption"/> + <requiredEntity createDataKey="simpleProduct2"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkDropDownOptionToProduct1"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createDropDownBundleOption"/> + <requiredEntity createDataKey="simpleProduct1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkDropDownOptionToProduct2"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createDropDownBundleOption"/> + <requiredEntity createDataKey="simpleProduct2"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkRadioButtonsOptionToProduct1"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleRadioButtonsOption"/> + <requiredEntity createDataKey="simpleProduct1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkRadioButtonsOptionToProduct2"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleRadioButtonsOption"/> + <requiredEntity createDataKey="simpleProduct2"/> + </createData> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml index e36730a87b41a..1767db0a00974 100644 --- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml @@ -38,4 +38,24 @@ <waitForElementVisible selector="{{StorefrontMinicartSection.productCount}}" stepKey="waitProductCount"/> <see userInput="You added {{productName}} to your shopping cart." selector="{{StorefrontMessagesSection.success}}" stepKey="seeSuccessMessage"/> </actionGroup> + + <!-- Add Bundle Product to Cart from product Page--> + <actionGroup name="StorefrontAddBundleProductFromProductToCartActionGroup"> + <arguments> + <argument name="productName" type="string"/> + </arguments> + <click selector="{{StorefrontBundledSection.addToCart}}" stepKey="clickCustomizeAndAddToCart"/> + <click selector="{{StorefrontBundledSection.addToCartConfigured}}" stepKey="clickAddBundleProductToCart"/> + <waitForElementVisible selector="{{StorefrontMinicartSection.productCount}}" stepKey="waitProductCount"/> + <see userInput="You added {{productName}} to your shopping cart." selector="{{StorefrontMessagesSection.success}}" stepKey="seeSuccessMessage"/> + </actionGroup> + + <!-- Add Bundled Product to Cart with selected multiselect option--> + <actionGroup name="StorefrontAddBundleProductFromProductToCartWithMultiOption" extends="StorefrontAddBundleProductFromProductToCartActionGroup"> + <arguments> + <argument name="optionName" type="string"/> + <argument name="value" type="string"/> + </arguments> + <selectOption selector="{{StorefrontBundledSection.multiselectOptionFourProducts(optionName)}}" userInput="{{value}}" stepKey="selectValue" before="clickAddBundleProductToCart"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml index dbe48c46c820b..30a7e8b777f3b 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml @@ -26,11 +26,14 @@ <element name="bundleProductName" type="text" selector="//*[@id='maincontent']//span[@itemprop='name']"/> <element name="pageNotFound" type="text" selector="//h1[@class='page-title']//span[contains(., 'Whoops, our bad...')]"/> <element name="dropDownOptionOneProducts" type="select" selector="//label//span[contains(text(), '{{productName}}')]/../..//div[@class='control']//select" parameterized="true"/> + <element name="dropDownOptionTierPrices" type="text" selector="//label//span[contains(text(), '{{optionName}}')]/../..//div[@class='control']//div[@class='option-tier-prices']" parameterized="true"/> <element name="productInBundle" type="select" selector="//label//span[contains(text(), '{{productName}}')]" parameterized="true"/> <element name="dropDownOptionOneQuantity" type="input" selector="//span[contains(text(), '{{productName}}')]/../..//input" parameterized="true"/> <element name="radioButtonOptionTwoProducts" type="checkbox" selector="//label//span[contains(text(), '{{productName}}')]/../..//div[@class='control']//div[@class='field choice'][{{productNumber}}]/input" parameterized="true"/> <element name="radioButtonOptionTwoQuantity" type="input" selector="//label//span[contains(text(), '{{productName}}')]/../..//div[@class='control']//div[@class='field qty qty-holder']//input" parameterized="true"/> + <element name="radioButtonOptionLabel" type="text" selector="//label//span[contains(text(), '{{optionName}}')]/../..//div[@class='control']//div[@class='field choice']//label[contains(.,'{{productName}}')]" parameterized="true"/> <element name="checkboxOptionThreeProducts" type="checkbox" selector="//label//span[contains(text(), '{{productName}}')]/../..//div[@class='control']//div[@class='field choice'][{{productNumber}}]/input" parameterized="true"/> + <element name="checkboxOptionLabel" type="text" selector="//label//span[contains(text(), '{{optionName}}')]/../..//div[@class='control']//div[@class='field choice']//label[contains(.,'{{productName}}')]" parameterized="true"/> <element name="multiselectOptionFourProducts" type="multiselect" selector="//label//span[contains(text(), '{{productName}}')]/../..//select[@multiple='multiple']" parameterized="true"/> <element name="currencyTrigger" type="select" selector="#switcher-currency-trigger" timeout="30"/> <element name="currency" type="select" selector="//a[text()='{{arg}}']" parameterized="true"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml new file mode 100644 index 0000000000000..4c39cbc4ab0a4 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml @@ -0,0 +1,104 @@ +<?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="StorefrontCheckBundleProductOptionTierPrices"> + <annotations> + <features value="Bundle"/> + <stories value="View bundle products"/> + <title value="Check tier prices for bundle options"/> + <testCaseId value="MAGETWO-98968"/> + <useCaseId value="MAGETWO-98603"/> + <group value="catalog"/> + <group value="bundle"/> + </annotations> + <before> + <!-- Create Dynamic Bundle product --> + <actionGroup ref="AdminCreateApiDynamicBundleProductAllOptionTypesActionGroup" stepKey="createBundleProduct"/> + + <!-- Add tier prices to simple products --> + <!-- Simple product 1 --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <amOnPage url="{{AdminProductEditPage.url($$simpleProduct1CreateBundleProduct.id$$)}}" stepKey="openAdminEditPageProduct1"/> + <actionGroup ref="ProductSetAdvancedPricing" stepKey="addTierPriceProduct1"> + <argument name="group" value="ALL GROUPS"/> + <argument name="quantity" value="5"/> + <argument name="price" value="Discount"/> + <argument name="amount" value="50"/> + </actionGroup> + <!-- Simple product 2 --> + <amOnPage url="{{AdminProductEditPage.url($$simpleProduct2CreateBundleProduct.id$$)}}" stepKey="openAdminEditPageProduct2"/> + <actionGroup ref="ProductSetAdvancedPricing" stepKey="addTierPriceProduct2"> + <argument name="group" value="ALL GROUPS"/> + <argument name="quantity" value="7"/> + <argument name="price" value="Discount"/> + <argument name="amount" value="25"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logoutAsAdmin"/> + + <!-- Run reindex --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + </before> + <after> + <deleteData createDataKey="createBundleProductCreateBundleProduct" stepKey="deleteDynamicBundleProduct"/> + <deleteData createDataKey="simpleProduct1CreateBundleProduct" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2CreateBundleProduct" stepKey="deleteSimpleProduct2"/> + </after> + + <!-- Go to storefront product page --> + <amOnPage url="{{StorefrontProductPage.url($$createBundleProductCreateBundleProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToBundleProductPage"/> + <click selector="{{StorefrontBundledSection.addToCart}}" stepKey="clickCustomize"/> + + <!--"Drop-down" type option--> + <!-- Check Tier Prices for product 1 --> + <selectOption selector="{{StorefrontBundledSection.dropDownOptionOneProducts('Drop-down Option')}}" userInput="$$simpleProduct1CreateBundleProduct.sku$$ +$$$simpleProduct1CreateBundleProduct.price$$.00" stepKey="selectDropDownOptionProduct1"/> + <seeOptionIsSelected selector="{{StorefrontBundledSection.dropDownOptionOneProducts('Drop-down Option')}}" userInput="$$simpleProduct1CreateBundleProduct.sku$$ +$$$simpleProduct1CreateBundleProduct.price$$.00" stepKey="checkDropDownOptionProduct1"/> + <grabTextFrom selector="{{StorefrontBundledSection.dropDownOptionTierPrices('Drop-down Option')}}" stepKey="DropDownTierPriceTextProduct1"/> + <assertContains stepKey="assertDropDownTierPriceTextProduct1"> + <expectedResult type="string">Buy 5 for $5.00 each and save 50%</expectedResult> + <actualResult type="variable">DropDownTierPriceTextProduct1</actualResult> + </assertContains> + <!-- Check Tier Prices for product 2 --> + <selectOption selector="{{StorefrontBundledSection.dropDownOptionOneProducts('Drop-down Option')}}" userInput="$$simpleProduct2CreateBundleProduct.sku$$ +$$$simpleProduct2CreateBundleProduct.price$$.00" stepKey="selectDropDownOptionProduct2"/> + <seeOptionIsSelected selector="{{StorefrontBundledSection.dropDownOptionOneProducts('Drop-down Option')}}" userInput="$$simpleProduct2CreateBundleProduct.sku$$ +$$$simpleProduct2CreateBundleProduct.price$$.00" stepKey="checkDropDownOptionProduct2"/> + <grabTextFrom selector="{{StorefrontBundledSection.dropDownOptionTierPrices('Drop-down Option')}}" stepKey="dropDownTierPriceTextProduct2"/> + <assertContains stepKey="assertDropDownTierPriceTextProduct2"> + <expectedResult type="string">Buy 7 for $15.00 each and save 25%</expectedResult> + <actualResult type="variable">dropDownTierPriceTextProduct2</actualResult> + </assertContains> + + <!--"Radio Buttons" type option--> + <!-- Check Tier Prices for product 1 --> + <grabTextFrom selector="{{StorefrontBundledSection.radioButtonOptionLabel('Radio Buttons Option', '$$simpleProduct1CreateBundleProduct.sku$$')}}" stepKey="radioButtonsOptionTierPriceTextProduct1"/> + <assertContains stepKey="assertRadioButtonsOptionTierPriceTextProduct1"> + <expectedResult type="string">Buy 5 for $5.00 each and save 50%</expectedResult> + <actualResult type="variable">radioButtonsOptionTierPriceTextProduct1</actualResult> + </assertContains> + <!-- Check Tier Prices for product 2 --> + <grabTextFrom selector="{{StorefrontBundledSection.radioButtonOptionLabel('Radio Buttons Option', '$$simpleProduct2CreateBundleProduct.sku$$')}}" stepKey="radioButtonsOptionTierPriceTextProduct2"/> + <assertContains stepKey="assertRadioButtonsOptionTierPriceTextProduct2"> + <expectedResult type="string">Buy 7 for $15.00 each and save 25%</expectedResult> + <actualResult type="variable">radioButtonsOptionTierPriceTextProduct2</actualResult> + </assertContains> + + <!--"Checkbox" type option--> + <!-- Check Tier Prices for product 1 --> + <grabTextFrom selector="{{StorefrontBundledSection.checkboxOptionLabel('Checkbox Option', '$$simpleProduct1CreateBundleProduct.sku$$')}}" stepKey="checkBoxOptionTierPriceTextProduct1"/> + <assertContains stepKey="assertCheckBoxOptionTierPriceTextProduct1"> + <expectedResult type="string">Buy 5 for $5.00 each and save 50%</expectedResult> + <actualResult type="variable">checkBoxOptionTierPriceTextProduct1</actualResult> + </assertContains> + <!-- Check Tier Prices for product 2 --> + <grabTextFrom selector="{{StorefrontBundledSection.checkboxOptionLabel('Checkbox Option', '$$simpleProduct2CreateBundleProduct.sku$$')}}" stepKey="checkBoxOptionTierPriceTextProduct2"/> + <assertContains stepKey="assertCheckBoxOptionTierPriceTextProduct2"> + <expectedResult type="string">Buy 7 for $15.00 each and save 25%</expectedResult> + <actualResult type="variable">checkBoxOptionTierPriceTextProduct2</actualResult> + </assertContains> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Unit/Block/DataProviders/OptionPriceRendererTest.php b/app/code/Magento/Bundle/Test/Unit/Block/DataProviders/OptionPriceRendererTest.php new file mode 100644 index 0000000000000..1af73bafc6256 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Unit/Block/DataProviders/OptionPriceRendererTest.php @@ -0,0 +1,99 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Bundle\Test\Unit\Block\DataProviders; + +use Magento\Bundle\Block\DataProviders\OptionPriceRenderer; +use Magento\Catalog\Model\Product; +use Magento\Framework\Pricing\Render; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\View\Element\BlockInterface; +use Magento\Framework\View\LayoutInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Class to test additional data for bundle options + */ +class OptionPriceRendererTest extends TestCase +{ + /** + * @var LayoutInterface|MockObject + */ + private $layoutMock; + + /** + * @var OptionPriceRenderer + */ + private $renderer; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = new ObjectManager($this); + + $this->layoutMock = $this->createMock( + LayoutInterface::class + ); + + $this->renderer = $objectManager->getObject( + OptionPriceRenderer::class, + ['layout' => $this->layoutMock] + ); + } + + /** + * Test to render Tier price html + * + * @return void + */ + public function testRenderTierPrice(): void + { + $expectedHtml = 'tier price html'; + $expectedArguments = ['zone' => Render::ZONE_ITEM_OPTION]; + + $productMock = $this->createMock(Product::class); + + $priceRenderer = $this->createPartialMock(BlockInterface::class, ['toHtml', 'render']); + $priceRenderer->expects($this->once()) + ->method('render') + ->with('tier_price', $productMock, $expectedArguments) + ->willReturn($expectedHtml); + + $this->layoutMock->method('getBlock') + ->with('product.price.render.default') + ->willReturn($priceRenderer); + + $this->assertEquals( + $expectedHtml, + $this->renderer->renderTierPrice($productMock), + 'Render Tier price is wrong' + ); + } + + /** + * Test to render Tier price html when render block is not exists + * + * @return void + */ + public function testRenderTierPriceNotExist(): void + { + $productMock = $this->createMock(Product::class); + + $this->layoutMock->method('getBlock') + ->with('product.price.render.default') + ->willReturn(false); + + $this->assertEquals( + '', + $this->renderer->renderTierPrice($productMock), + 'Render Tier price is wrong' + ); + } +} diff --git a/app/code/Magento/Bundle/view/base/web/js/price-bundle.js b/app/code/Magento/Bundle/view/base/web/js/price-bundle.js index f8d2f8bc11116..49ee253ad1e88 100644 --- a/app/code/Magento/Bundle/view/base/web/js/price-bundle.js +++ b/app/code/Magento/Bundle/view/base/web/js/price-bundle.js @@ -27,7 +27,8 @@ define([ '<% } %>', controlContainer: 'dd', // should be eliminated priceFormat: {}, - isFixedPrice: false + isFixedPrice: false, + optionTierPricesBlocksSelector: '#option-tier-prices-{1} [data-role="selection-tier-prices"]' }; $.widget('mage.priceBundle', { @@ -91,6 +92,8 @@ define([ if (changes) { priceBox.trigger('updatePrice', changes); } + + this._displayTierPriceBlock(bundleOption); this.updateProductSummary(); }, @@ -207,6 +210,35 @@ define([ return this; }, + /** + * Show or hide option tier prices block + * + * @param {Object} optionElement + * @private + */ + _displayTierPriceBlock: function (optionElement) { + var optionType = optionElement.prop('type'), + optionId, + optionValue, + optionTierPricesElements; + + if (optionType === 'select-one') { + optionId = utils.findOptionId(optionElement[0]); + optionValue = optionElement.val() || null; + optionTierPricesElements = $(this.options.optionTierPricesBlocksSelector.replace('{1}', optionId)); + + _.each(optionTierPricesElements, function (tierPriceElement) { + var selectionId = $(tierPriceElement).data('selection-id') + ''; + + if (selectionId === optionValue) { + $(tierPriceElement).show(); + } else { + $(tierPriceElement).hide(); + } + }); + } + }, + /** * Handler to update productSummary box */ diff --git a/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_bundle.xml b/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_bundle.xml index 5b8c050e5af54..d12f2e8f6a952 100644 --- a/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_bundle.xml +++ b/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_bundle.xml @@ -29,10 +29,22 @@ <container name="product.info.bundle.options.top" as="product_info_bundle_options_top"> <block class="Magento\Catalog\Block\Product\View" name="bundle.back.button" as="backButton" before="-" template="Magento_Bundle::catalog/product/view/backbutton.phtml"/> </container> - <block class="Magento\Bundle\Block\Catalog\Product\View\Type\Bundle\Option\Select" name="product.info.bundle.options.select" as="select"/> + <block class="Magento\Bundle\Block\Catalog\Product\View\Type\Bundle\Option\Select" name="product.info.bundle.options.select" as="select"> + <arguments> + <argument name="tier_price_renderer" xsi:type="object">\Magento\Bundle\Block\DataProviders\OptionPriceRenderer</argument> + </arguments> + </block> <block class="Magento\Bundle\Block\Catalog\Product\View\Type\Bundle\Option\Multi" name="product.info.bundle.options.multi" as="multi"/> - <block class="Magento\Bundle\Block\Catalog\Product\View\Type\Bundle\Option\Radio" name="product.info.bundle.options.radio" as="radio"/> - <block class="Magento\Bundle\Block\Catalog\Product\View\Type\Bundle\Option\Checkbox" name="product.info.bundle.options.checkbox" as="checkbox"/> + <block class="Magento\Bundle\Block\Catalog\Product\View\Type\Bundle\Option\Radio" name="product.info.bundle.options.radio" as="radio"> + <arguments> + <argument name="tier_price_renderer" xsi:type="object">\Magento\Bundle\Block\DataProviders\OptionPriceRenderer</argument> + </arguments> + </block> + <block class="Magento\Bundle\Block\Catalog\Product\View\Type\Bundle\Option\Checkbox" name="product.info.bundle.options.checkbox" as="checkbox"> + <arguments> + <argument name="tier_price_renderer" xsi:type="object">\Magento\Bundle\Block\DataProviders\OptionPriceRenderer</argument> + </arguments> + </block> </block> </referenceBlock> <referenceBlock name="product.info.form.options"> diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/checkbox.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/checkbox.phtml index bda649eb603e6..830d03c826f32 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/checkbox.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/checkbox.phtml @@ -19,6 +19,7 @@ <div class="nested options-list"> <?php if ($block->showSingle()): ?> <?= /* @escapeNotVerified */ $block->getSelectionQtyTitlePrice($_selections[0]) ?> + <?= /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selections[0]) ?> <input type="hidden" class="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?> product bundle option" name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>]" @@ -38,6 +39,8 @@ <label class="label" for="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>"> <span><?= /* @escapeNotVerified */ $block->getSelectionQtyTitlePrice($_selection) ?></span> + <br/> + <?= /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selection) ?> </label> </div> <?php endforeach; ?> diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml index 7ea89e8609818..1f33d97227ea3 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml @@ -21,6 +21,7 @@ <div class="nested options-list"> <?php if ($block->showSingle()): ?> <?= /* @escapeNotVerified */ $block->getSelectionTitlePrice($_selections[0]) ?> + <?= /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selections[0]) ?> <input type="hidden" class="bundle-option-<?= (int)$_option->getId() ?> product bundle option" name="bundle_option[<?= (int)$_option->getId() ?>]" @@ -57,6 +58,8 @@ <label class="label" for="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>"> <span><?= /* @escapeNotVerified */ $block->getSelectionTitlePrice($_selection) ?></span> + <br/> + <?= /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selection) ?> </label> </div> <?php endforeach; ?> diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/select.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/select.phtml index 977daa2b2a446..4ea00f62b2043 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/select.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/select.phtml @@ -20,6 +20,7 @@ <div class="control"> <?php if ($block->showSingle()): ?> <?= /* @escapeNotVerified */ $block->getSelectionTitlePrice($_selections[0]) ?> + <?= /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selections[0]) ?> <input type="hidden" class="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?> product bundle option" name="bundle_option[<?= /* @escapeNotVerified */ $_option->getId() ?>]" @@ -39,6 +40,15 @@ </option> <?php endforeach; ?> </select> + <div id="option-tier-prices-<?= /* @escapeNotVerified */ $_option->getId() ?>" class="option-tier-prices"> + <?php foreach ($_selections as $_selection): ?> + <div data-role="selection-tier-prices" + data-selection-id="<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>" + class="selection-tier-prices"> + <?= /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selection) ?> + </div> + <?php endforeach; ?> + </div> <?php endif; ?> <div class="nested"> <div class="field qty qty-holder"> diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/AdminResetUserPasswordFailedTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/AdminResetUserPasswordFailedTest.xml new file mode 100644 index 0000000000000..8f9c5828e2f5e --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Test/AdminResetUserPasswordFailedTest.xml @@ -0,0 +1,19 @@ +<?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="AdminResetUserPasswordFailedTest"> + <before> + <magentoCLI command="config:set {{AdminCaptchaDisableConfigData.path}} {{AdminCaptchaDisableConfigData.value}} " stepKey="disableAdminCaptcha"/> + </before> + <after> + <magentoCLI command="config:set {{AdminCaptchaEnableConfigData.path}} {{AdminCaptchaEnableConfigData.value}} " stepKey="enableAdminCaptcha"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Api/Data/CategoryInterface.php b/app/code/Magento/Catalog/Api/Data/CategoryInterface.php index b9a23e9d08ec3..1940a0ac80c0b 100644 --- a/app/code/Magento/Catalog/Api/Data/CategoryInterface.php +++ b/app/code/Magento/Catalog/Api/Data/CategoryInterface.php @@ -1,7 +1,5 @@ <?php /** - * Category data interface - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -9,6 +7,8 @@ namespace Magento\Catalog\Api\Data; /** + * Category data interface. + * * @api * @since 100.0.2 */ @@ -46,11 +46,15 @@ interface CategoryInterface extends \Magento\Framework\Api\CustomAttributesDataI /**#@-*/ /** + * Retrieve category id. + * * @return int|null */ public function getId(); /** + * Set category id. + * * @param int $id * @return $this */ @@ -74,7 +78,7 @@ public function setParentId($parentId); /** * Get category name * - * @return string + * @return string|null */ public function getName(); @@ -132,60 +136,82 @@ public function getLevel(); public function setLevel($level); /** + * Retrieve children ids comma separated. + * * @return string|null */ public function getChildren(); /** + * Retrieve category creation date and time. + * * @return string|null */ public function getCreatedAt(); /** + * Set category creation date and time. + * * @param string $createdAt * @return $this */ public function setCreatedAt($createdAt); /** + * Retrieve category last update date and time. + * * @return string|null */ public function getUpdatedAt(); /** + * Set category last update date and time. + * * @param string $updatedAt * @return $this */ public function setUpdatedAt($updatedAt); /** + * Retrieve category full path. + * * @return string|null */ public function getPath(); /** + * Set category full path. + * * @param string $path * @return $this */ public function setPath($path); /** + * Retrieve available sort by for category. + * * @return string[]|null */ public function getAvailableSortBy(); /** + * Set available sort by for category. + * * @param string[]|string $availableSortBy * @return $this */ public function setAvailableSortBy($availableSortBy); /** + * Get category is included in menu. + * * @return bool|null */ public function getIncludeInMenu(); /** + * Set category is included in menu. + * * @param bool $includeInMenu * @return $this */ diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Search.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Search.php index c7c71b2f56026..316983298a1b9 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Search.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Search.php @@ -9,11 +9,12 @@ namespace Magento\Catalog\Controller\Adminhtml\Product; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; /** * Controller to search product for ui-select component */ -class Search extends \Magento\Backend\App\Action +class Search extends \Magento\Backend\App\Action implements HttpGetActionInterface { /** * Authorization level of a basic admin session @@ -48,6 +49,8 @@ public function __construct( } /** + * Execute product search. + * * @return \Magento\Framework\Controller\ResultInterface */ public function execute() : \Magento\Framework\Controller\ResultInterface diff --git a/app/code/Magento/Catalog/Controller/Category/View.php b/app/code/Magento/Catalog/Controller/Category/View.php index 2088bb5ea77cd..da3d99a8d2745 100644 --- a/app/code/Magento/Catalog/Controller/Category/View.php +++ b/app/code/Magento/Catalog/Controller/Category/View.php @@ -6,14 +6,28 @@ */ namespace Magento\Catalog\Controller\Category; -use Magento\Framework\App\Action\HttpPostActionInterface; -use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\Design; use Magento\Catalog\Model\Layer\Resolver; use Magento\Catalog\Model\Product\ProductList\ToolbarMemorizer; +use Magento\Catalog\Model\Session; +use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator; +use Magento\Framework\App\Action\Action; +use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\ActionInterface; +use Magento\Framework\Controller\Result\ForwardFactory; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\DataObject; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\Framework\View\Result\Page; use Magento\Framework\View\Result\PageFactory; -use Magento\Framework\App\Action\Action; +use Magento\Store\Model\StoreManagerInterface; +use Psr\Log\LoggerInterface; /** * View a category on storefront. Needs to be accessible by POST because of the store switching. @@ -25,41 +39,41 @@ class View extends Action implements HttpGetActionInterface, HttpPostActionInter /** * Core registry * - * @var \Magento\Framework\Registry + * @var Registry */ protected $_coreRegistry = null; /** * Catalog session * - * @var \Magento\Catalog\Model\Session + * @var Session */ protected $_catalogSession; /** * Catalog design * - * @var \Magento\Catalog\Model\Design + * @var Design */ protected $_catalogDesign; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ protected $_storeManager; /** - * @var \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator + * @var CategoryUrlPathGenerator */ protected $categoryUrlPathGenerator; /** - * @var \Magento\Framework\View\Result\PageFactory + * @var PageFactory */ protected $resultPageFactory; /** - * @var \Magento\Framework\Controller\Result\ForwardFactory + * @var ForwardFactory */ protected $resultForwardFactory; @@ -83,28 +97,28 @@ class View extends Action implements HttpGetActionInterface, HttpPostActionInter /** * Constructor * - * @param \Magento\Framework\App\Action\Context $context - * @param \Magento\Catalog\Model\Design $catalogDesign - * @param \Magento\Catalog\Model\Session $catalogSession - * @param \Magento\Framework\Registry $coreRegistry - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator - * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory - * @param \Magento\Framework\Controller\Result\ForwardFactory $resultForwardFactory + * @param Context $context + * @param Design $catalogDesign + * @param Session $catalogSession + * @param Registry $coreRegistry + * @param StoreManagerInterface $storeManager + * @param CategoryUrlPathGenerator $categoryUrlPathGenerator + * @param PageFactory $resultPageFactory + * @param ForwardFactory $resultForwardFactory * @param Resolver $layerResolver * @param CategoryRepositoryInterface $categoryRepository * @param ToolbarMemorizer|null $toolbarMemorizer * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( - \Magento\Framework\App\Action\Context $context, - \Magento\Catalog\Model\Design $catalogDesign, - \Magento\Catalog\Model\Session $catalogSession, - \Magento\Framework\Registry $coreRegistry, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator, + Context $context, + Design $catalogDesign, + Session $catalogSession, + Registry $coreRegistry, + StoreManagerInterface $storeManager, + CategoryUrlPathGenerator $categoryUrlPathGenerator, PageFactory $resultPageFactory, - \Magento\Framework\Controller\Result\ForwardFactory $resultForwardFactory, + ForwardFactory $resultForwardFactory, Resolver $layerResolver, CategoryRepositoryInterface $categoryRepository, ToolbarMemorizer $toolbarMemorizer = null @@ -125,7 +139,7 @@ public function __construct( /** * Initialize requested category object * - * @return \Magento\Catalog\Model\Category|bool + * @return Category|bool */ protected function _initCategory() { @@ -150,8 +164,8 @@ protected function _initCategory() 'catalog_controller_category_init_after', ['category' => $category, 'controller_action' => $this] ); - } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); + } catch (LocalizedException $e) { + $this->_objectManager->get(LoggerInterface::class)->critical($e); return false; } @@ -161,13 +175,12 @@ protected function _initCategory() /** * Category view action * - * @return \Magento\Framework\Controller\ResultInterface - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) + * @return ResultInterface + * @throws NoSuchEntityException */ public function execute() { - if ($this->_request->getParam(\Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED)) { + if ($this->_request->getParam(ActionInterface::PARAM_NAME_URL_ENCODED)) { return $this->resultRedirectFactory->create()->setUrl($this->_redirect->getRedirectUrl()); } $category = $this->_initCategory(); @@ -188,29 +201,18 @@ public function execute() $page->getConfig()->setPageLayout($settings->getPageLayout()); } - $hasChildren = $category->hasChildren(); - if ($category->getIsAnchor()) { - $type = $hasChildren ? 'layered' : 'layered_without_children'; - } else { - $type = $hasChildren ? 'default' : 'default_without_children'; - } + $pageType = $this->getPageType($category); - if (!$hasChildren) { + if (!$category->hasChildren()) { // Two levels removed from parent. Need to add default page type. - $parentType = strtok($type, '_'); - $page->addPageLayoutHandles(['type' => $parentType], null, false); + $parentPageType = strtok($pageType, '_'); + $page->addPageLayoutHandles(['type' => $parentPageType], null, false); } - $page->addPageLayoutHandles(['type' => $type], null, false); + $page->addPageLayoutHandles(['type' => $pageType], null, false); $page->addPageLayoutHandles(['id' => $category->getId()]); // apply custom layout update once layout is loaded - $layoutUpdates = $settings->getLayoutUpdates(); - if ($layoutUpdates && is_array($layoutUpdates)) { - foreach ($layoutUpdates as $layoutUpdate) { - $page->addUpdate($layoutUpdate); - $page->addPageLayoutHandles(['layout_update' => sha1($layoutUpdate)], null, false); - } - } + $this->applyLayoutUpdates($page, $settings); $page->getConfig()->addBodyClass('page-products') ->addBodyClass('categorypath-' . $this->categoryUrlPathGenerator->getUrlPath($category)) @@ -221,4 +223,40 @@ public function execute() return $this->resultForwardFactory->create()->forward('noroute'); } } + + /** + * Get page type based on category + * + * @param Category $category + * @return string + */ + private function getPageType(Category $category) : string + { + $hasChildren = $category->hasChildren(); + if ($category->getIsAnchor()) { + return $hasChildren ? 'layered' : 'layered_without_children'; + } + + return $hasChildren ? 'default' : 'default_without_children'; + } + + /** + * Apply custom layout updates + * + * @param Page $page + * @param DataObject $settings + * @return void + */ + private function applyLayoutUpdates( + Page $page, + DataObject $settings + ) { + $layoutUpdates = $settings->getLayoutUpdates(); + if ($layoutUpdates && is_array($layoutUpdates)) { + foreach ($layoutUpdates as $layoutUpdate) { + $page->addUpdate($layoutUpdate); + $page->addPageLayoutHandles(['layout_update' => sha1($layoutUpdate)], null, false); + } + } + } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php index a3d958ea537e1..e6c098ab0254e 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php @@ -115,7 +115,7 @@ public function build($storeId, $changedIds, $valueFieldSuffix) /** * Create empty temporary table with given columns list * - * @param string $tableName Table name + * @param string $tableName Table name * @param array $columns array('columnName' => \Magento\Catalog\Model\ResourceModel\Eav\Attribute, ...) * @param string $valueFieldSuffix * @@ -304,12 +304,16 @@ protected function _fillTemporaryTable( /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ foreach ($columnsList as $columnName => $attribute) { - $countTableName = 't' . $iterationNum++; + $countTableName = 't' . ($iterationNum++); $joinCondition = sprintf( - 'e.%3$s = %1$s.%3$s AND %1$s.attribute_id = %2$d AND %1$s.store_id = 0', + 'e.%3$s = %1$s.%3$s' . + ' AND %1$s.attribute_id = %2$d' . + ' AND (%1$s.store_id = %4$d' . + ' OR %1$s.store_id = 0)', $countTableName, $attribute->getId(), - $metadata->getLinkField() + $metadata->getLinkField(), + $storeId ); $select->joinLeft( @@ -323,9 +327,10 @@ protected function _fillTemporaryTable( $columnValueName = $attributeCode . $valueFieldSuffix; if (isset($flatColumns[$columnValueName])) { $valueJoinCondition = sprintf( - 'e.%1$s = %2$s.option_id AND %2$s.store_id = 0', + 'e.%1$s = %2$s.option_id AND (%2$s.store_id = %3$d OR %2$s.store_id = 0)', $attributeCode, - $countTableName + $countTableName, + $storeId ); $selectValue->joinLeft( [ diff --git a/app/code/Magento/Catalog/Model/Product/Copier.php b/app/code/Magento/Catalog/Model/Product/Copier.php index 53fa11df04b35..44ebdf0f1f283 100644 --- a/app/code/Magento/Catalog/Model/Product/Copier.php +++ b/app/code/Magento/Catalog/Model/Product/Copier.php @@ -1,7 +1,5 @@ <?php /** - * Catalog product copier. Creates product duplicate - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -11,7 +9,11 @@ use Magento\Catalog\Model\Product; /** - * The copier creates product duplicates. + * Catalog product copier. + * + * Creates product duplicate. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Copier { @@ -74,22 +76,9 @@ public function copy(Product $product) $duplicate->setUpdatedAt(null); $duplicate->setId(null); $duplicate->setStoreId(\Magento\Store\Model\Store::DEFAULT_STORE_ID); - $this->copyConstructor->build($product, $duplicate); - $isDuplicateSaved = false; - do { - $urlKey = $duplicate->getUrlKey(); - $urlKey = preg_match('/(.*)-(\d+)$/', $urlKey, $matches) - ? $matches[1] . '-' . ($matches[2] + 1) - : $urlKey . '-1'; - $duplicate->setUrlKey($urlKey); - $duplicate->setData('url_path', null); - try { - $duplicate->save(); - $isDuplicateSaved = true; - } catch (\Magento\Framework\Exception\AlreadyExistsException $e) { - } - } while (!$isDuplicateSaved); + $this->setDefaultUrl($product, $duplicate); + $this->setStoresUrl($product, $duplicate); $this->getOptionRepository()->duplicate($product, $duplicate); $product->getResource()->duplicate( $product->getData($metadata->getLinkField()), @@ -98,6 +87,81 @@ public function copy(Product $product) return $duplicate; } + /** + * Set default URL. + * + * @param Product $product + * @param Product $duplicate + * @return void + */ + private function setDefaultUrl(Product $product, Product $duplicate) : void + { + $duplicate->setStoreId(\Magento\Store\Model\Store::DEFAULT_STORE_ID); + $resource = $product->getResource(); + $attribute = $resource->getAttribute('url_key'); + $productId = $product->getId(); + $urlKey = $resource->getAttributeRawValue($productId, 'url_key', \Magento\Store\Model\Store::DEFAULT_STORE_ID); + do { + $urlKey = $this->modifyUrl($urlKey); + $duplicate->setUrlKey($urlKey); + } while (!$attribute->getEntity()->checkAttributeUniqueValue($attribute, $duplicate)); + $duplicate->setData('url_path', null); + $duplicate->save(); + } + + /** + * Set URL for each store. + * + * @param Product $product + * @param Product $duplicate + * @return void + */ + private function setStoresUrl(Product $product, Product $duplicate) : void + { + $storeIds = $duplicate->getStoreIds(); + $productId = $product->getId(); + $productResource = $product->getResource(); + $defaultUrlKey = $productResource->getAttributeRawValue( + $productId, + 'url_key', + \Magento\Store\Model\Store::DEFAULT_STORE_ID + ); + $duplicate->setData('save_rewrites_history', false); + foreach ($storeIds as $storeId) { + $isDuplicateSaved = false; + $duplicate->setStoreId($storeId); + $urlKey = $productResource->getAttributeRawValue($productId, 'url_key', $storeId); + if ($urlKey === $defaultUrlKey) { + continue; + } + do { + $urlKey = $this->modifyUrl($urlKey); + $duplicate->setUrlKey($urlKey); + $duplicate->setData('url_path', null); + try { + $duplicate->save(); + $isDuplicateSaved = true; + // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock + } catch (\Magento\Framework\Exception\AlreadyExistsException $e) { + } + } while (!$isDuplicateSaved); + } + $duplicate->setStoreId(\Magento\Store\Model\Store::DEFAULT_STORE_ID); + } + + /** + * Modify URL key. + * + * @param string $urlKey + * @return string + */ + private function modifyUrl(string $urlKey) : string + { + return preg_match('/(.*)-(\d+)$/', $urlKey, $matches) + ? $matches[1] . '-' . ($matches[2] + 1) + : $urlKey . '-1'; + } + /** * Returns product option repository. * diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php b/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php index 42b9639d2717b..e06e85e90a2d8 100644 --- a/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php @@ -206,7 +206,7 @@ public function execute($product, $arguments = []) } /** - * Returns media gallery atribute instance + * Returns media gallery attribute instance * * @return \Magento\Catalog\Api\Data\ProductAttributeInterface * @since 101.0.0 @@ -230,6 +230,7 @@ public function getAttribute() * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @since 101.0.0 + * phpcs:disable Magento2.CodeAnalysis.EmptyBlock */ protected function processDeletedImages($product, array &$images) { @@ -400,6 +401,7 @@ protected function getUniqueFileName($file, $forTmp = false) $destinationFile = $forTmp ? $this->mediaDirectory->getAbsolutePath($this->mediaConfig->getTmpMediaPath($file)) : $this->mediaDirectory->getAbsolutePath($this->mediaConfig->getMediaPath($file)); + // phpcs:disable Magento2.Functions.DiscouragedFunction $destFile = dirname($file) . '/' . FileUploader::getNewFileName($destinationFile); } @@ -420,6 +422,7 @@ protected function copyImage($file) $destinationFile = $this->getUniqueFileName($file); if (!$this->mediaDirectory->isFile($this->mediaConfig->getMediaPath($file))) { + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception(); } @@ -437,6 +440,7 @@ protected function copyImage($file) } return str_replace('\\', '/', $destinationFile); + // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Exception $e) { $file = $this->mediaConfig->getMediaPath($file); throw new \Magento\Framework\Exception\LocalizedException( diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php index 0e08b0af92862..c993e51c8bc09 100644 --- a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php +++ b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php @@ -71,8 +71,10 @@ public function create($sku, ProductAttributeMediaGalleryEntryInterface $entry) $product->setMediaGalleryEntries($existingMediaGalleryEntries); try { $product = $this->productRepository->save($product); + // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (InputException $inputException) { throw $inputException; + // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Exception $e) { throw new StateException(__("The product can't be saved.")); } @@ -105,7 +107,10 @@ public function update($sku, ProductAttributeMediaGalleryEntryInterface $entry) if ($existingEntry->getId() == $entry->getId()) { $found = true; - if ($entry->getFile()) { + + $file = $entry->getContent(); + + if ($file && $file->getBase64EncodedData() || $entry->getFile()) { $entry->setId(null); } $existingMediaGalleryEntries[$key] = $entry; diff --git a/app/code/Magento/Catalog/Model/Product/Type/FrontSpecialPrice.php b/app/code/Magento/Catalog/Model/Product/Type/FrontSpecialPrice.php index f6893a41113e6..dabfdb74f0118 100644 --- a/app/code/Magento/Catalog/Model/Product/Type/FrontSpecialPrice.php +++ b/app/code/Magento/Catalog/Model/Product/Type/FrontSpecialPrice.php @@ -17,6 +17,9 @@ * * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * + * @deprecated + * @see \Magento\Catalog\Model\Product\Type\Price */ class FrontSpecialPrice extends Price { @@ -66,6 +69,8 @@ public function __construct( /** * @inheritdoc + * + * @deprecated */ protected function _applySpecialPrice($product, $finalPrice) { diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index 48f45d0ce9373..c87b6e9763205 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -1,18 +1,18 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Catalog\Model; +use Magento\Catalog\Api\CategoryLinkManagementInterface; use Magento\Catalog\Api\Data\ProductExtension; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product\Gallery\MimeTypeExtensionMap; +use Magento\Catalog\Model\ProductRepository\MediaGalleryProcessor; use Magento\Catalog\Model\ResourceModel\Product\Collection; use Magento\Eav\Model\Entity\Attribute\Exception as AttributeException; -use Magento\Framework\Api\Data\ImageContentInterface; use Magento\Framework\Api\Data\ImageContentInterfaceFactory; use Magento\Framework\Api\ImageContentValidatorInterface; use Magento\Framework\Api\ImageProcessorInterface; @@ -25,8 +25,8 @@ use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Framework\Exception\TemporaryState\CouldNotSaveException as TemporaryCouldNotSaveException; use Magento\Framework\Exception\StateException; +use Magento\Framework\Exception\TemporaryState\CouldNotSaveException as TemporaryCouldNotSaveException; use Magento\Framework\Exception\ValidatorException; /** @@ -122,11 +122,15 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa protected $fileSystem; /** + * @deprecated + * * @var ImageContentInterfaceFactory */ protected $contentFactory; /** + * @deprecated + * * @var ImageProcessorInterface */ protected $imageProcessor; @@ -137,10 +141,17 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa protected $extensionAttributesJoinProcessor; /** + * @deprecated + * * @var \Magento\Catalog\Model\Product\Gallery\Processor */ protected $mediaGalleryProcessor; + /** + * @var MediaGalleryProcessor + */ + private $mediaProcessor; + /** * @var CollectionProcessorInterface */ @@ -161,6 +172,11 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa */ private $readExtensions; + /** + * @var CategoryLinkManagementInterface + */ + private $linkManagement; + /** * ProductRepository constructor. * @param ProductFactory $productFactory @@ -186,7 +202,8 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa * @param CollectionProcessorInterface $collectionProcessor [optional] * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer * @param int $cacheLimit [optional] - * @param ReadExtensions|null $readExtensions + * @param ReadExtensions $readExtensions + * @param CategoryLinkManagementInterface $linkManagement * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ @@ -214,7 +231,8 @@ public function __construct( CollectionProcessorInterface $collectionProcessor = null, \Magento\Framework\Serialize\Serializer\Json $serializer = null, $cacheLimit = 1000, - ReadExtensions $readExtensions = null + ReadExtensions $readExtensions = null, + CategoryLinkManagementInterface $linkManagement = null ) { $this->productFactory = $productFactory; $this->collectionFactory = $collectionFactory; @@ -239,6 +257,8 @@ public function __construct( $this->cacheLimit = (int)$cacheLimit; $this->readExtensions = $readExtensions ?: \Magento\Framework\App\ObjectManager::getInstance() ->get(ReadExtensions::class); + $this->linkManagement = $linkManagement ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(CategoryLinkManagementInterface::class); } /** @@ -381,6 +401,9 @@ private function assignProductToWebsites(\Magento\Catalog\Model\Product $product /** * Process new gallery media entry. * + * @deprecated + * @see MediaGalleryProcessor::processNewMediaGalleryEntry() + * * @param ProductInterface $product * @param array $newEntry * @return $this @@ -392,40 +415,8 @@ protected function processNewMediaGalleryEntry( ProductInterface $product, array $newEntry ) { - /** @var ImageContentInterface $contentDataObject */ - $contentDataObject = $newEntry['content']; + $this->getMediaGalleryProcessor()->processNewMediaGalleryEntry($product, $newEntry); - /** @var \Magento\Catalog\Model\Product\Media\Config $mediaConfig */ - $mediaConfig = $product->getMediaConfig(); - $mediaTmpPath = $mediaConfig->getBaseTmpMediaPath(); - - $relativeFilePath = $this->imageProcessor->processImageContent($mediaTmpPath, $contentDataObject); - $tmpFilePath = $mediaConfig->getTmpMediaShortUrl($relativeFilePath); - - if (!$product->hasGalleryAttribute()) { - throw new StateException( - __("The product that was requested doesn't exist. Verify the product and try again.") - ); - } - - $imageFileUri = $this->getMediaGalleryProcessor()->addImage( - $product, - $tmpFilePath, - isset($newEntry['types']) ? $newEntry['types'] : [], - true, - isset($newEntry['disabled']) ? $newEntry['disabled'] : true - ); - // Update additional fields that are still empty after addImage call - $this->getMediaGalleryProcessor()->updateImage( - $product, - $imageFileUri, - [ - 'label' => $newEntry['label'], - 'position' => $newEntry['position'], - 'disabled' => $newEntry['disabled'], - 'media_type' => $newEntry['media_type'], - ] - ); return $this; } @@ -500,68 +491,13 @@ private function processLinks(ProductInterface $product, $newLinks) * @return $this * @throws InputException * @throws StateException + * @throws LocalizedException * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function processMediaGallery(ProductInterface $product, $mediaGalleryEntries) { - $existingMediaGallery = $product->getMediaGallery('images'); - $newEntries = []; - $entriesById = []; - if (!empty($existingMediaGallery)) { - foreach ($mediaGalleryEntries as $entry) { - if (isset($entry['value_id'])) { - $entriesById[$entry['value_id']] = $entry; - } else { - $newEntries[] = $entry; - } - } - foreach ($existingMediaGallery as $key => &$existingEntry) { - if (isset($entriesById[$existingEntry['value_id']])) { - $updatedEntry = $entriesById[$existingEntry['value_id']]; - if ($updatedEntry['file'] === null) { - unset($updatedEntry['file']); - } - $existingMediaGallery[$key] = array_merge($existingEntry, $updatedEntry); - } else { - //set the removed flag - $existingEntry['removed'] = true; - } - } - $product->setData('media_gallery', ["images" => $existingMediaGallery]); - } else { - $newEntries = $mediaGalleryEntries; - } - - $images = (array)$product->getMediaGallery('images'); - $images = $this->determineImageRoles($product, $images); - - $this->getMediaGalleryProcessor()->clearMediaAttribute($product, array_keys($product->getMediaAttributes())); - - foreach ($images as $image) { - if (!isset($image['removed']) && !empty($image['types'])) { - $this->getMediaGalleryProcessor()->setMediaAttribute($product, $image['types'], $image['file']); - } - } + $this->getMediaGalleryProcessor()->processMediaGallery($product, $mediaGalleryEntries); - foreach ($newEntries as $newEntry) { - if (!isset($newEntry['content'])) { - throw new InputException(__('The image content is invalid. Verify the content and try again.')); - } - /** @var ImageContentInterface $contentDataObject */ - $contentDataObject = $this->contentFactory->create() - ->setName($newEntry['content']['data'][ImageContentInterface::NAME]) - ->setBase64EncodedData($newEntry['content']['data'][ImageContentInterface::BASE64_ENCODED_DATA]) - ->setType($newEntry['content']['data'][ImageContentInterface::TYPE]); - $newEntry['content'] = $contentDataObject; - $this->processNewMediaGalleryEntry($product, $newEntry); - - $finalGallery = $product->getData('media_gallery'); - $newEntryId = key(array_diff_key($product->getData('media_gallery')['images'], $entriesById)); - $newEntry = array_replace_recursive($newEntry, $finalGallery['images'][$newEntryId]); - $entriesById[$newEntryId] = $newEntry; - $finalGallery['images'][$newEntryId] = $newEntry; - $product->setData('media_gallery', $finalGallery); - } return $this; } @@ -572,6 +508,7 @@ protected function processMediaGallery(ProductInterface $product, $mediaGalleryE */ public function save(ProductInterface $product, $saveOptions = false) { + $assignToCategories = false; $tierPrices = $product->getData('tier_price'); try { @@ -589,6 +526,7 @@ public function save(ProductInterface $product, $saveOptions = false) $extensionAttributes = $product->getExtensionAttributes(); if (empty($extensionAttributes->__toArray())) { $product->setExtensionAttributes($existingProduct->getExtensionAttributes()); + $assignToCategories = true; } } catch (NoSuchEntityException $e) { $existingProduct = null; @@ -626,6 +564,12 @@ public function save(ProductInterface $product, $saveOptions = false) } $this->saveProduct($product); + if ($assignToCategories === true && $product->getCategoryIds()) { + $this->linkManagement->assignProductToCategories( + $product->getSku(), + $product->getCategoryIds() + ); + } $this->removeProductFromLocalCache($product->getSku()); unset($this->instancesById[$product->getId()]); @@ -763,44 +707,19 @@ public function cleanCache() $this->instancesById = null; } - /** - * Ascertain image roles, if they are not set against the gallery entries - * - * @param ProductInterface $product - * @param array $images - * @return array - */ - private function determineImageRoles(ProductInterface $product, array $images) : array - { - $imagesWithRoles = []; - foreach ($images as $image) { - if (!isset($image['types'])) { - $image['types'] = []; - if (isset($image['file'])) { - foreach (array_keys($product->getMediaAttributes()) as $attribute) { - if ($image['file'] == $product->getData($attribute)) { - $image['types'][] = $attribute; - } - } - } - } - $imagesWithRoles[] = $image; - } - return $imagesWithRoles; - } - /** * Retrieve media gallery processor. * - * @return Product\Gallery\Processor + * @return MediaGalleryProcessor */ private function getMediaGalleryProcessor() { - if (null === $this->mediaGalleryProcessor) { - $this->mediaGalleryProcessor = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Catalog\Model\Product\Gallery\Processor::class); + if (null === $this->mediaProcessor) { + $this->mediaProcessor = \Magento\Framework\App\ObjectManager::getInstance() + ->get(MediaGalleryProcessor::class); } - return $this->mediaGalleryProcessor; + + return $this->mediaProcessor; } /** @@ -912,6 +831,7 @@ private function saveProduct($product): void throw new CouldNotSaveException(__($e->getMessage())); } catch (LocalizedException $e) { throw $e; + // phpcs:disable Magento2.Exceptions.ThrowCatch } catch (\Exception $e) { throw new CouldNotSaveException( __('The product was unable to be saved. Please try again.'), diff --git a/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php b/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php new file mode 100644 index 0000000000000..70311954f63e9 --- /dev/null +++ b/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php @@ -0,0 +1,239 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\ProductRepository; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\Product\Gallery\Processor; +use Magento\Catalog\Model\Product\Media\Config; +use Magento\Framework\Api\Data\ImageContentInterface; +use Magento\Framework\Api\Data\ImageContentInterfaceFactory; +use Magento\Framework\Api\ImageProcessorInterface; +use Magento\Framework\Exception\InputException; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\StateException; + +/** + * Process Media gallery data for ProductRepository before save product. + */ +class MediaGalleryProcessor +{ + /** + * Catalog gallery processor. + * + * @var Processor + */ + private $processor; + + /** + * Image content data object factory. + * + * @var ImageContentInterfaceFactory + */ + private $contentFactory; + + /** + * Image processor. + * + * @var ImageProcessorInterface + */ + private $imageProcessor; + + /** + * @param Processor $processor + * @param ImageContentInterfaceFactory $contentFactory + * @param ImageProcessorInterface $imageProcessor + */ + public function __construct( + Processor $processor, + ImageContentInterfaceFactory $contentFactory, + ImageProcessorInterface $imageProcessor + ) { + $this->processor = $processor; + $this->contentFactory = $contentFactory; + $this->imageProcessor = $imageProcessor; + } + + /** + * Process Media gallery data before save product. + * + * Compare Media Gallery Entries Data with existing Media Gallery + * * If Media entry has not value_id set it as new + * * If Existing entry 'value_id' absent in Media Gallery set 'removed' flag + * * Merge Existing and new media gallery + * + * @param ProductInterface $product contains only existing media gallery items + * @param array $mediaGalleryEntries array which contains all media gallery items + * @return void + * @throws InputException + * @throws StateException + * @throws LocalizedException + */ + public function processMediaGallery(ProductInterface $product, array $mediaGalleryEntries) :void + { + $existingMediaGallery = $product->getMediaGallery('images'); + $newEntries = []; + $entriesById = []; + if (!empty($existingMediaGallery)) { + foreach ($mediaGalleryEntries as $entry) { + if (isset($entry['value_id'])) { + $entriesById[$entry['value_id']] = $entry; + } else { + $newEntries[] = $entry; + } + } + foreach ($existingMediaGallery as $key => &$existingEntry) { + if (isset($entriesById[$existingEntry['value_id']])) { + $updatedEntry = $entriesById[$existingEntry['value_id']]; + if ($updatedEntry['file'] === null) { + unset($updatedEntry['file']); + } + $existingMediaGallery[$key] = array_merge($existingEntry, $updatedEntry); + } else { + //set the removed flag + $existingEntry['removed'] = true; + } + } + $product->setData('media_gallery', ["images" => $existingMediaGallery]); + } else { + $newEntries = $mediaGalleryEntries; + } + + $images = (array)$product->getMediaGallery('images'); + $images = $this->determineImageRoles($product, $images); + + $this->processor->clearMediaAttribute($product, array_keys($product->getMediaAttributes())); + + $this->processMediaAttributes($product, $images); + $this->processEntries($product, $newEntries, $entriesById); + } + + /** + * Process new gallery media entry. + * + * @param ProductInterface $product + * @param array $newEntry + * @return void + * @throws InputException + * @throws StateException + * @throws LocalizedException + */ + public function processNewMediaGalleryEntry( + ProductInterface $product, + array $newEntry + ) :void { + /** @var ImageContentInterface $contentDataObject */ + $contentDataObject = $newEntry['content']; + + /** @var Config $mediaConfig */ + $mediaConfig = $product->getMediaConfig(); + $mediaTmpPath = $mediaConfig->getBaseTmpMediaPath(); + + $relativeFilePath = $this->imageProcessor->processImageContent($mediaTmpPath, $contentDataObject); + $tmpFilePath = $mediaConfig->getTmpMediaShortUrl($relativeFilePath); + + if (!$product->hasGalleryAttribute()) { + throw new StateException( + __("The product that was requested doesn't exist. Verify the product and try again.") + ); + } + + $imageFileUri = $this->processor->addImage( + $product, + $tmpFilePath, + isset($newEntry['types']) ? $newEntry['types'] : [], + true, + isset($newEntry['disabled']) ? $newEntry['disabled'] : true + ); + // Update additional fields that are still empty after addImage call + $this->processor->updateImage( + $product, + $imageFileUri, + [ + 'label' => $newEntry['label'], + 'position' => $newEntry['position'], + 'disabled' => $newEntry['disabled'], + 'media_type' => $newEntry['media_type'], + ] + ); + } + + /** + * Ascertain image roles, if they are not set against the gallery entries. + * + * @param ProductInterface $product + * @param array $images + * @return array + */ + private function determineImageRoles(ProductInterface $product, array $images) : array + { + $imagesWithRoles = []; + foreach ($images as $image) { + if (!isset($image['types'])) { + $image['types'] = []; + if (isset($image['file'])) { + foreach (array_keys($product->getMediaAttributes()) as $attribute) { + if ($image['file'] == $product->getData($attribute)) { + $image['types'][] = $attribute; + } + } + } + } + $imagesWithRoles[] = $image; + } + + return $imagesWithRoles; + } + + /** + * Convert entries into product media gallery data and set to product. + * + * @param ProductInterface $product + * @param array $newEntries + * @param array $entriesById + * @throws InputException + * @throws LocalizedException + * @throws StateException + */ + private function processEntries(ProductInterface $product, array $newEntries, array $entriesById): void + { + foreach ($newEntries as $newEntry) { + if (!isset($newEntry['content'])) { + throw new InputException(__('The image content is invalid. Verify the content and try again.')); + } + /** @var ImageContentInterface $contentDataObject */ + $contentDataObject = $this->contentFactory->create() + ->setName($newEntry['content']['data'][ImageContentInterface::NAME]) + ->setBase64EncodedData($newEntry['content']['data'][ImageContentInterface::BASE64_ENCODED_DATA]) + ->setType($newEntry['content']['data'][ImageContentInterface::TYPE]); + $newEntry['content'] = $contentDataObject; + $this->processNewMediaGalleryEntry($product, $newEntry); + + $finalGallery = $product->getData('media_gallery'); + $newEntryId = key(array_diff_key($product->getData('media_gallery')['images'], $entriesById)); + $newEntry = array_replace_recursive($newEntry, $finalGallery['images'][$newEntryId]); + $entriesById[$newEntryId] = $newEntry; + $finalGallery['images'][$newEntryId] = $newEntry; + $product->setData('media_gallery', $finalGallery); + } + } + + /** + * Set media attribute values. + * + * @param ProductInterface $product + * @param array $images + */ + private function processMediaAttributes(ProductInterface $product, array $images): void + { + foreach ($images as $image) { + if (!isset($image['removed']) && !empty($image['types'])) { + $this->processor->setMediaAttribute($product, $image['types'], $image['file']); + } + } + } +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php index b5668a12f94a5..657daca13055e 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php @@ -7,7 +7,6 @@ use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator; use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; use Magento\Store\Model\ScopeInterface; /** @@ -83,8 +82,6 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig - * - * @param ResourceModelPoolInterface|null $resourceModelPool * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -99,8 +96,7 @@ public function __construct( \Magento\Framework\Validator\UniversalFactory $universalFactory, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, - \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig = null, - ResourceModelPoolInterface $resourceModelPool = null + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig = null ) { parent::__construct( $entityFactory, @@ -113,8 +109,7 @@ public function __construct( $resourceHelper, $universalFactory, $storeManager, - $connection, - $resourceModelPool + $connection ); $this->scopeConfig = $scopeConfig ?: \Magento\Framework\App\ObjectManager::getInstance()->get(ScopeConfigInterface::class); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Collection/AbstractCollection.php b/app/code/Magento/Catalog/Model/ResourceModel/Collection/AbstractCollection.php index 2e40d13f1ccac..3a0d47fe573fb 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Collection/AbstractCollection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Collection/AbstractCollection.php @@ -5,8 +5,6 @@ */ namespace Magento\Catalog\Model\ResourceModel\Collection; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; - /** * Catalog EAV collection resource abstract model * @@ -45,8 +43,6 @@ class AbstractCollection extends \Magento\Eav\Model\Entity\Collection\AbstractCo * @param \Magento\Framework\Validator\UniversalFactory $universalFactory * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection - * - * @param ResourceModelPoolInterface|null $resourceModelPool * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -60,8 +56,7 @@ public function __construct( \Magento\Eav\Model\ResourceModel\Helper $resourceHelper, \Magento\Framework\Validator\UniversalFactory $universalFactory, \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, - ResourceModelPoolInterface $resourceModelPool = null + \Magento\Framework\DB\Adapter\AdapterInterface $connection = null ) { $this->_storeManager = $storeManager; parent::__construct( @@ -74,8 +69,7 @@ public function __construct( $eavEntityFactory, $resourceHelper, $universalFactory, - $connection, - $resourceModelPool + $connection ); } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php index 23f612582f42e..d56cc40ad0fc2 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php @@ -845,9 +845,14 @@ public function afterDelete() /** * @inheritdoc * @since 100.0.9 + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $this->unsetData('entity_type'); return array_diff( parent::__sleep(), @@ -858,9 +863,14 @@ public function __sleep() /** * @inheritdoc * @since 100.0.9 + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + parent::__wakeup(); $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); $this->_indexerEavProcessor = $objectManager->get(\Magento\Catalog\Model\Indexer\Product\Flat\Processor::class); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 136c7e800bf08..0cdf8b39f7d52 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -7,6 +7,8 @@ namespace Magento\Catalog\Model\ResourceModel\Product; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; +use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus; use Magento\Catalog\Model\Product\Gallery\ReadHandler as GalleryReadHandler; use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; @@ -16,12 +18,10 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\MetadataPool; -use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; -use Magento\Store\Model\Indexer\WebsiteDimensionProvider; -use Magento\Store\Model\Store; -use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; use Magento\Framework\Indexer\DimensionFactory; use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; +use Magento\Store\Model\Store; /** * Product collection @@ -32,6 +32,7 @@ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.NumberOfChildren) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @since 100.0.2 */ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\AbstractCollection @@ -324,7 +325,6 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac * @param TableMaintainer|null $tableMaintainer * @param PriceTableResolver|null $priceTableResolver * @param DimensionFactory|null $dimensionFactory - * @param ResourceModelPoolInterface|null $resourceModelPool * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -353,8 +353,7 @@ public function __construct( MetadataPool $metadataPool = null, TableMaintainer $tableMaintainer = null, PriceTableResolver $priceTableResolver = null, - DimensionFactory $dimensionFactory = null, - ResourceModelPoolInterface $resourceModelPool = null + DimensionFactory $dimensionFactory = null ) { $this->moduleManager = $moduleManager; $this->_catalogProductFlatState = $catalogProductFlatState; @@ -382,8 +381,7 @@ public function __construct( $resourceHelper, $universalFactory, $storeManager, - $connection, - $resourceModelPool + $connection ); $this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class); $this->priceTableResolver = $priceTableResolver ?: ObjectManager::getInstance()->get(PriceTableResolver::class); @@ -445,7 +443,7 @@ protected function _preparePriceExpressionParameters($select) */ public function getPriceExpression($select) { - //@todo: Add caching of price expresion + //@todo: Add caching of price expression $this->_preparePriceExpressionParameters($select); return $this->_priceExpression; } @@ -1979,6 +1977,7 @@ protected function _productLimitationPrice($joinLeft = false) } // Set additional field filters foreach ($this->_priceDataFieldFilters as $filterData) { + // phpcs:ignore Magento2.Functions.DiscouragedFunction $select->where(call_user_func_array('sprintf', $filterData)); } } else { @@ -2284,6 +2283,7 @@ private function getBackend() public function addPriceDataFieldFilter($comparisonFormat, $fields) { if (!preg_match('/^%s( (<|>|=|<=|>=|<>) %s)*$/', $comparisonFormat)) { + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception('Invalid comparison format.'); } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php index a45e2060d7c20..aa6fb8c1f8827 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php @@ -5,13 +5,6 @@ */ namespace Magento\Catalog\Model\ResourceModel\Product\Compare\Item; -use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; -use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; -use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; -use Magento\Framework\EntityManager\MetadataPool; -use Magento\Framework\Indexer\DimensionFactory; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; - /** * Catalog Product Compare Items Resource Collection * @@ -19,6 +12,7 @@ * @author Magento Core Team <core@magentocommerce.com> * @SuppressWarnings(PHPMD.LongVariable) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @since 100.0.2 */ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection @@ -82,12 +76,6 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection * @param \Magento\Catalog\Model\ResourceModel\Product\Compare\Item $catalogProductCompareItem * @param \Magento\Catalog\Helper\Product\Compare $catalogProductCompare * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection - * @param ProductLimitationFactory|null $productLimitationFactory - * @param MetadataPool|null $metadataPool - * @param TableMaintainer|null $tableMaintainer - * @param PriceTableResolver|null $priceTableResolver - * @param DimensionFactory|null $dimensionFactory - * @param ResourceModelPoolInterface|null $resourceModelPool * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -112,13 +100,7 @@ public function __construct( \Magento\Customer\Api\GroupManagementInterface $groupManagement, \Magento\Catalog\Model\ResourceModel\Product\Compare\Item $catalogProductCompareItem, \Magento\Catalog\Helper\Product\Compare $catalogProductCompare, - \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, - ProductLimitationFactory $productLimitationFactory = null, - MetadataPool $metadataPool = null, - TableMaintainer $tableMaintainer = null, - PriceTableResolver $priceTableResolver = null, - DimensionFactory $dimensionFactory = null, - ResourceModelPoolInterface $resourceModelPool = null + \Magento\Framework\DB\Adapter\AdapterInterface $connection = null ) { $this->_catalogProductCompareItem = $catalogProductCompareItem; $this->_catalogProductCompare = $catalogProductCompare; @@ -142,13 +124,7 @@ public function __construct( $customerSession, $dateTime, $groupManagement, - $connection, - $productLimitationFactory, - $metadataPool, - $tableMaintainer, - $priceTableResolver, - $dimensionFactory, - $resourceModelPool + $connection ); } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php index 318c9bd132ccd..494dbac02d792 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php @@ -17,11 +17,12 @@ use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\Store; use Magento\Store\Model\StoreManagerInterface; +use Magento\Catalog\Helper\Data; /** * Catalog product custom option resource model * - * @author Magento Core Team <core@magentocommerce.com> + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Value extends AbstractDb { @@ -51,6 +52,11 @@ class Value extends AbstractDb */ private $localeFormat; + /** + * @var Data + */ + private $dataHelper; + /** * Class constructor * @@ -59,17 +65,21 @@ class Value extends AbstractDb * @param StoreManagerInterface $storeManager * @param ScopeConfigInterface $config * @param string $connectionName + * @param Data $dataHelper */ public function __construct( Context $context, CurrencyFactory $currencyFactory, StoreManagerInterface $storeManager, ScopeConfigInterface $config, - $connectionName = null + $connectionName = null, + Data $dataHelper = null ) { $this->_currencyFactory = $currencyFactory; $this->_storeManager = $storeManager; $this->_config = $config; + $this->dataHelper = $dataHelper ?: ObjectManager::getInstance() + ->get(Data::class); parent::__construct($context, $connectionName); } @@ -131,7 +141,7 @@ protected function _saveValuePrices(AbstractModel $object) $optionTypeId = $this->getConnection()->fetchOne($select); if ($optionTypeId) { - if ($object->getStoreId() == '0') { + if ($object->getStoreId() == '0' || $this->dataHelper->isPriceGlobal()) { $bind = ['price' => $price, 'price_type' => $priceType]; $where = [ 'option_type_id = ?' => $optionTypeId, diff --git a/app/code/Magento/Catalog/Setup/CategorySetup.php b/app/code/Magento/Catalog/Setup/CategorySetup.php index 271387932829b..f8542454bef92 100644 --- a/app/code/Magento/Catalog/Setup/CategorySetup.php +++ b/app/code/Magento/Catalog/Setup/CategorySetup.php @@ -10,7 +10,6 @@ use Magento\Catalog\Block\Adminhtml\Category\Helper\Pricestep; use Magento\Catalog\Block\Adminhtml\Category\Helper\Sortby\Available; use Magento\Catalog\Block\Adminhtml\Category\Helper\Sortby\DefaultSortby; -use Magento\Catalog\Block\Adminhtml\Product\Helper\Form\BaseImage; use Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Category as CategoryFormHelper; use Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Weight as WeightFormHelper; use Magento\Catalog\Model\Attribute\Backend\Customlayoutupdate; @@ -54,6 +53,8 @@ use Magento\Theme\Model\Theme\Source\Theme; /** + * Setup category with default entities. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CategorySetup extends EavSetup @@ -593,7 +594,6 @@ public function getDefaultEntities() 'label' => 'Base Image', 'input' => 'media_image', 'frontend' => ImageFrontendModel::class, - 'input_renderer' => BaseImage::class, 'required' => false, 'sort_order' => 0, 'global' => ScopedAttributeInterface::SCOPE_STORE, @@ -626,7 +626,6 @@ public function getDefaultEntities() 'type' => 'varchar', 'label' => 'Media Gallery', 'input' => 'gallery', - 'backend' => Media::class, 'required' => false, 'sort_order' => 4, 'group' => 'Images', diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml new file mode 100644 index 0000000000000..dd66919640a73 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml @@ -0,0 +1,17 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminCreateRecentlyProductsWidgetActionGroup" extends="AdminCreateWidgetActionGroup"> + <selectOption selector="{{AdminCatalogProductWidgetSection.productAttributesToShow}}" parameterArray="['Name', 'Image', 'Price']" stepKey="selectAllProductAttributes"/> + <selectOption selector="{{AdminCatalogProductWidgetSection.productButtonsToShow}}" parameterArray="['Add to Cart', 'Add to Compare', 'Add to Wishlist']" stepKey="selectAllProductButtons"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSaveWidget"/> + <waitForElementVisible selector="{{AdminMessagesSection.successMessage}}" stepKey="waitForSuccessMessageAppears"/> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="The widget instance has been saved" stepKey="seeSuccess"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenNewProductFormPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenNewProductFormPageActionGroup.xml new file mode 100644 index 0000000000000..fe859fab52667 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenNewProductFormPageActionGroup.xml @@ -0,0 +1,19 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenNewProductFormPageActionGroup"> + <arguments> + <argument name="productType" type="string" defaultValue="simple" /> + <argument name="attributeSetId" type="string" defaultValue="{{defaultAttributeSet.attribute_set_id}}" /> + </arguments> + + <amOnPage url="{{AdminProductCreatePage.url(attributeSetId, productType)}}" stepKey="openProductNewPage" /> + <waitForPageLoad stepKey="waitForPageLoad" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml index da570f9ed99b0..3c44a8f1898ad 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml @@ -19,6 +19,14 @@ <seeInCurrentUrl url="{{AdminProductCreatePage.url(AddToDefaultSet.attributeSetId, product.type_id)}}" stepKey="seeNewProductUrl"/> <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Product" stepKey="seeNewProductTitle"/> </actionGroup> + + <!--Navigate to create product page directly via ID--> + <actionGroup name="goToProductPageViaID"> + <arguments> + <argument name="productId" type="string"/> + </arguments> + <amOnPage url="{{AdminProductEditPage.url(productId)}}" stepKey="goToProduct"/> + </actionGroup> <!-- Fill main fields in create product form using a product entity --> <actionGroup name="fillMainProductForm"> @@ -96,6 +104,9 @@ <see selector="{{AdminProductMessagesSection.successMessage}}" userInput="You saved the product." stepKey="seeSaveConfirmation"/> </actionGroup> + <actionGroup name="toggleProductEnabled"> + <click selector="{{AdminProductFormSection.enableProductLabel}}" stepKey="toggleEnabled"/> + </actionGroup> <!-- Save product but do not expect a success message --> <actionGroup name="SaveProductFormNoSuccessCheck" extends="saveProductForm"> <remove keyForRemoval="seeSaveConfirmation"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml index 46329dde278bc..86158aba68f82 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml @@ -65,6 +65,60 @@ <click selector="{{AdminProductFormNewAttributeSection.saveAttribute}}" stepKey="saveAttribute1"/> </actionGroup> + <!-- Creates attribute and attribute set from the product page--> + <actionGroup name="AdminProductPageCreateAttributeSetWithAttribute"> + <arguments> + <argument name="attributeName" type="string"/> + <argument name="attributeSetName" type="string"/> + <argument name="attributeType" type="string" defaultValue="TextField"/> + </arguments> + <click selector="{{AdminProductFormSection.addAttributeBtn}}" stepKey="clickAddAttributeBtn"/> + <waitForPageLoad stepKey="waitForSidePanel"/> + <see userInput="Select Attribute" stepKey="checkNewAttributePopUpAppeared"/> + <click selector="{{AdminProductFormAttributeSection.createNewAttribute}}" stepKey="clickCreateNewAttribute"/> + <fillField selector="{{AdminProductFormNewAttributeSection.attributeLabel}}" userInput="{{attributeName}}" stepKey="fillAttributeLabel"/> + <selectOption selector="{{AdminProductFormNewAttributeSection.attributeType}}" userInput="{{attributeType}}" stepKey="selectAttributeType"/> + <click selector="{{AdminProductFormNewAttributeSection.saveInNewSet}}" stepKey="saveAttribute"/> + <fillField selector="{{AdminProductFormNewAttributeNewSetSection.setName}}" userInput="{{attributeSetName}}" stepKey="fillSetName"/> + <click selector="{{AdminProductFormNewAttributeNewSetSection.accept}}" stepKey="acceptPopup"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingToFinish"/> + <!-- Product page will hang if there is no reload--> + <reloadPage stepKey="reloadPage"/> + <waitForPageLoad stepKey="waitForReload"/> + </actionGroup> + + <!-- Create attribute and set with given search weight and defaultValue from the Edit Product Page --> + <actionGroup name="AdminCreateAttributeWithSearchWeight" extends="AdminProductPageCreateAttributeSetWithAttribute" insertAfter="selectAttributeType"> + <arguments> + <argument name="weight" type="string" defaultValue="1"/> + <argument name="defaultValue" type="string" defaultValue="default"/> + </arguments> + <click selector="{{AdminProductFormNewAttributeAdvancedSection.sectionHeader}}" stepKey="openAdvancedSection"/> + <fillField selector="{{AdminProductFormNewAttributeAdvancedSection.defaultValue}}" userInput="{{defaultValue}}" stepKey="inputDefault"/> + <click selector="{{AdminProductFormNewAttributeStorefrontSection.sectionHeader}}" stepKey="openStorefrontSection"/> + <checkOption selector="{{AdminProductFormNewAttributeStorefrontSection.useInSearch}}" stepKey="checkUseInSearch"/> + <waitForElementVisible selector="{{AdminProductFormNewAttributeStorefrontSection.searchWeight}}" stepKey="waitForSearchWeight"/> + <selectOption selector="{{AdminProductFormNewAttributeStorefrontSection.searchWeight}}" userInput="{{weight}}" stepKey="selectWeight"/> + </actionGroup> + + <actionGroup name="AdminProductPageSelectAttributeSet"> + <arguments> + <argument name="attributeSetName" type="string"/> + </arguments> + <click stepKey="openDropdown" selector="{{AdminProductFormSection.attributeSet}}"/> + <fillField stepKey="filter" selector="{{AdminProductFormSection.attributeSetFilter}}" userInput="{{attributeSetName}}"/> + <click stepKey="clickResult" selector="{{AdminProductFormSection.attributeSetFilterResult}}"/> + </actionGroup> + + <actionGroup name="AdminProductPageFillTextAttributeValueByName"> + <arguments> + <argument name="attributeName" type="string"/> + <argument name="value" type="string"/> + </arguments> + <click stepKey="openSection" selector="{{AdminProductAttributeSection.attributeSectionHeader}}"/> + <fillField stepKey="fillValue" selector="{{AdminProductAttributeSection.textAttributeByName(attributeName)}}" userInput="{{value}}"/> + </actionGroup> + <actionGroup name="changeUseForPromoRuleConditionsProductAttribute"> <arguments> <argument name="option" type="string"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml index 2914ecc470220..019d103a31cf2 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml @@ -45,6 +45,9 @@ <fillField selector="{{AdminProductAttributeSetSection.name}}" userInput="{{label}}" stepKey="fillName"/> <click selector="{{AdminProductAttributeSetSection.saveBtn}}" stepKey="clickSave1"/> </actionGroup> + <actionGroup name="goToAttributeGridPage"> + <amOnPage url="{{AdminProductAttributeSetGridPage.url}}" stepKey="goToAttributeSets"/> + </actionGroup> <actionGroup name="goToAttributeSetByName"> <arguments> <argument name="name" type="string"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml index cbdabe6a2d218..ad32b8edbd243 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml @@ -289,4 +289,13 @@ <waitForPageLoad stepKey="waitForProductToLoad"/> <waitForElementVisible selector="{{AdminHeaderSection.pageTitle}}" stepKey="waitForProductTitle"/> </actionGroup> + + <actionGroup name="deleteProductsIfTheyExist"> + <conditionalClick selector="{{AdminProductGridSection.multicheckDropdown}}" dependentSelector="{{AdminProductGridSection.firstProductRow}}" visible="true" stepKey="openMulticheckDropdown"/> + <conditionalClick selector="{{AdminProductGridSection.multicheckOption('Select All')}}" dependentSelector="{{AdminProductGridSection.firstProductRow}}" visible="true" stepKey="selectAllProductInFilteredGrid"/> + <click selector="{{AdminProductGridSection.bulkActionDropdown}}" stepKey="clickActionDropdown"/> + <click selector="{{AdminProductGridSection.bulkActionOption('Delete')}}" stepKey="clickDeleteAction"/> + <waitForElementVisible selector="{{AdminProductGridConfirmActionSection.ok}}" stepKey="waitForModalPopUp"/> + <click selector="{{AdminProductGridConfirmActionSection.ok}}" stepKey="confirmProductDelete"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductInWidgetActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductInWidgetActionGroup.xml new file mode 100644 index 0000000000000..c25b73bab21ae --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductInWidgetActionGroup.xml @@ -0,0 +1,37 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Check the product in recently viewed widget --> + <actionGroup name="StorefrontAssertProductInRecentlyViewedWidgetActionGroup"> + <arguments> + <argument name="product"/> + </arguments> + <waitForElementVisible selector="{{StorefrontWidgetsSection.widgetRecentlyViewedProductsGrid}}" stepKey="waitWidgetRecentlyViewedProductsGrid"/> + <see selector="{{StorefrontWidgetsSection.widgetRecentlyViewedProductsGrid}}" userInput="{{product.name}}" stepKey="seeProductInRecentlyViewedWidget"/> + </actionGroup> + + <!-- Check the product in recently compared widget --> + <actionGroup name="StorefrontAssertProductInRecentlyComparedWidgetActionGroup"> + <arguments> + <argument name="product"/> + </arguments> + <waitForElementVisible selector="{{StorefrontWidgetsSection.widgetRecentlyComparedProductsGrid}}" stepKey="waitWidgetRecentlyComparedProductsGrid"/> + <see selector="{{StorefrontWidgetsSection.widgetRecentlyComparedProductsGrid}}" userInput="{{product.name}}" stepKey="seeProductInRecentlyComparedWidget"/> + </actionGroup> + + <!-- Check the product in recently ordered widget --> + <actionGroup name="StorefrontAssertProductInRecentlyOrderedWidgetActionGroup"> + <arguments> + <argument name="product"/> + </arguments> + <waitForElementVisible selector="{{StorefrontWidgetsSection.widgetRecentlyOrderedProductsGrid}}" stepKey="waitWidgetRecentlyOrderedProductsGrid"/> + <see selector="{{StorefrontWidgetsSection.widgetRecentlyOrderedProductsGrid}}" userInput="{{product.name}}" stepKey="seeProductInRecentlyOrderedWidget"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontClickAddToCartOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontClickAddToCartOnProductPageActionGroup.xml new file mode 100644 index 0000000000000..fb2065d228d5a --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontClickAddToCartOnProductPageActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontClickAddToCartOnProductPageActionGroup"> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addToCart" /> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..24e1fe9cf5ecd --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,36 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuCatalog"> + <data key="pageTitle">Catalog</data> + <data key="title">Catalog</data> + <data key="dataUiId">magento-catalog-catalog</data> + </entity> + <entity name="AdminMenuCatalogCategories"> + <data key="pageTitle">Default Category (ID: 2)</data> + <data key="title">Categories</data> + <data key="dataUiId">magento-catalog-catalog-categories</data> + </entity> + <entity name="AdminMenuCatalogProducts"> + <data key="pageTitle">Products</data> + <data key="title">Products</data> + <data key="dataUiId">magento-catalog-catalog-products</data> + </entity> + <entity name="AdminMenuStoresAttributesAttributeSet"> + <data key="pageTitle">Attribute Sets</data> + <data key="title">Attribute Set</data> + <data key="dataUiId">magento-catalog-catalog-attributes-sets</data> + </entity> + <entity name="AdminMenuStoresAttributesProduct"> + <data key="pageTitle">Product Attributes</data> + <data key="title">Product</data> + <data key="dataUiId">magento-catalog-catalog-attributes-attributes</data> + </entity> +</entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/AttributeSetData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/AttributeSetData.xml new file mode 100644 index 0000000000000..6e1b25fb9cdc4 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/AttributeSetData.xml @@ -0,0 +1,16 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="defaultAttributeSet"> + <data key="attribute_set_id">4</data> + <data key="attribute_set_name">Default</data> + <data key="sort_order">0</data> + </entity> +</entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogRecentlyProductsConfigData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogRecentlyProductsConfigData.xml new file mode 100644 index 0000000000000..d1e469deaebba --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogRecentlyProductsConfigData.xml @@ -0,0 +1,25 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="EnableSynchronizeWidgetProductsWithBackendStorage" type="catalog_recently_products"> + <requiredEntity type="synchronize_with_backend">EnableCatalogRecentlyProductsSynchronize</requiredEntity> + </entity> + + <entity name="EnableCatalogRecentlyProductsSynchronize" type="synchronize_with_backend"> + <data key="value">1</data> + </entity> + + <entity name="DisableSynchronizeWidgetProductsWithBackendStorage" type="catalog_recently_products"> + <requiredEntity type="synchronize_with_backend">DefaultCatalogRecentlyProductsSynchronize</requiredEntity> + </entity> + + <entity name="DefaultCatalogRecentlyProductsSynchronize" type="synchronize_with_backend"> + <data key="value">0</data> + </entity> +</entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml index 134abcaa50354..817dd637f81dd 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml @@ -73,6 +73,20 @@ <data key="used_for_sort_by">false</data> <requiredEntity type="FrontendLabel">ProductAttributeFrontendLabel</requiredEntity> </entity> + <entity name="hiddenDropdownAttributeWithOptions" extends="productAttributeWithTwoOptions"> + <data key="is_searchable">false</data> + <data key="is_visible_in_advanced_search">false</data> + <data key="is_visible_on_front">false</data> + <data key="is_filterable">false</data> + <data key="is_filterable_in_search">false</data> + <data key="used_in_product_listing">false</data> + <data key="is_used_for_promo_rules">false</data> + <data key="is_comparable">false</data> + <data key="is_used_in_grid">false</data> + <data key="is_visible_in_grid">false</data> + <data key="is_filterable_in_grid">false</data> + <data key="used_for_sort_by">false</data> + </entity> <entity name="productDropDownAttribute" type="ProductAttribute"> <data key="attribute_code" unique="suffix">attribute</data> <data key="frontend_input">select</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml index 713c453bb7ad4..6d4314a6d865f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml @@ -20,4 +20,10 @@ <data key="attributeGroupId">7</data> <data key="sortOrder">1</data> </entity> + <entity name="AddToSetBlank" type="ProductAttributeSet"> + <var key="attributeCode" entityKey="attribute_code" entityType="ProductAttribute"/> + <data key="attributeSetId">0</data> + <data key="attributeGroupId">0</data> + <data key="sortOrder">0</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml index ba4a623e35def..b8d6ec8e63e71 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -305,6 +305,15 @@ <data key="filename">magento-again</data> <data key="file_extension">jpg</data> </entity> + <entity name="TestImageAdobe" type="image"> + <data key="title" unique="suffix">magento-adobe</data> + <data key="price">1.00</data> + <data key="file_type">Upload File</data> + <data key="shareable">Yes</data> + <data key="file">adobe-base.jpg</data> + <data key="filename">adobe-base</data> + <data key="file_extension">jpg</data> + </entity> <entity name="ProductWithUnicode" type="product"> <data key="sku" unique="suffix">霁产品</data> <data key="type_id">simple</data> @@ -940,6 +949,19 @@ <data key="attributeGroupId">13</data> <data key="sortOrder">0</data> </entity> + <entity name="productAlphabeticalA" type="product" extends="_defaultProduct"> + <data key="name" unique="suffix">AAA Product</data> + </entity> + <entity name="productAlphabeticalB" type="product" extends="_defaultProduct"> + <data key="name" unique="suffix">BBB Product</data> + </entity> + <entity name="productWithSpecialCharacters" type="product" extends="_defaultProduct"> + <data key="name" unique="suffix">Product "!@#$%^&*()+:;\|}{][?=~` </data> + <data key="nameWithSafeChars" unique="suffix">|}{][?=~` </data> + </entity> + <entity name="productWith130CharName" type="product" extends="_defaultProduct"> + <data key="name" unique="suffix">ProductWith128Chars 1234567891123456789112345678911234567891123456789112345678911234567891123456789112345678 endnums</data> + </entity> <entity name="simpleProductDefault" type="product"> <data key="sku" unique="suffix">sku_simple_product_</data> <data key="type_id">simple</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml index ea0bcafe56c48..71c8af318e9b4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml @@ -12,4 +12,7 @@ <data key="from">10</data> <data key="to">100</data> </entity> + <entity name="ProductPerPage"> + <data key="productCount">1</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/WidgetsData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/WidgetsData.xml index 83f0a56c21545..18564ff101fd9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/WidgetsData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/WidgetsData.xml @@ -12,4 +12,24 @@ <data key="type">Catalog Product Link</data> <data key="template">Product Link Block Template</data> </entity> + <entity name="RecentlyComparedProductsWidget" type="widget"> + <data key="type">Recently Compared Products</data> + <data key="design_theme">Magento Luma</data> + <data key="name" unique="suffix">Recently Compared Products</data> + <array key="store_ids"> + <item>All Store Views</item> + </array> + <data key="display_on">All Pages</data> + <data key="container">Sidebar Additional</data> + </entity> + <entity name="RecentlyViewedProductsWidget" type="widget"> + <data key="type">Recently Viewed Products</data> + <data key="design_theme">Magento Luma</data> + <data key="name" unique="suffix">Recently Viewed Products</data> + <array key="store_ids"> + <item>All Store Views</item> + </array> + <data key="display_on">All Pages</data> + <data key="container">Sidebar Additional</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_recently_products-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_recently_products-meta.xml new file mode 100644 index 0000000000000..0fe4f154d5ef5 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_recently_products-meta.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="CatalogRecentlyProductsConfiguration" dataType="catalog_recently_products" type="create" + auth="adminFormKey" url="/admin/system_config/save/section/catalog/" method="POST" successRegex="/messages-message-success/"> + <object key="groups" dataType="catalog_recently_products"> + <object key="recently_products" dataType="catalog_recently_products"> + <object key="fields" dataType="catalog_recently_products"> + <object key="synchronize_with_backend" dataType="synchronize_with_backend"> + <field key="value">integer</field> + </object> + </object> + </object> + </object> + </operation> +</operations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminNewWidgetPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminNewWidgetPage.xml index e23a503266e33..dd5d5aef08a7c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminNewWidgetPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminNewWidgetPage.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminNewWidgetPage" url="admin/widget_instance/new/" area="admin" module="Magento_Widget"> <section name="AdminNewWidgetSelectProductPopupSection"/> + <section name="AdminCatalogProductWidgetSection"/> </page> </pages> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCatalogProductWidgetSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCatalogProductWidgetSection.xml new file mode 100644 index 0000000000000..3261db1f63f24 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCatalogProductWidgetSection.xml @@ -0,0 +1,15 @@ +<?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="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCatalogProductWidgetSection"> + <element name="productAttributesToShow" type="multiselect" selector="select[name='parameters[show_attributes][]']"/> + <element name="productButtonsToShow" type="multiselect" selector="select[name='parameters[show_buttons][]']"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml index 697648cedb7ba..3ef78a3fe8f41 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml @@ -14,6 +14,7 @@ <element name="advancedPricingCloseButton" type="button" selector=".product_form_product_form_advanced_pricing_modal button.action-close" timeout="30"/> <element name="productTierPriceWebsiteSelect" type="select" selector="[name='product[tier_price][{{var1}}][website_id]']" parameterized="true"/> <element name="productTierPriceCustGroupSelect" type="select" selector="[name='product[tier_price][{{var1}}][cust_group]']" parameterized="true"/> + <element name="productTierPriceCustGroupSelectOptions" type="select" selector="[name='product[tier_price][{{var1}}][cust_group]'] option" parameterized="true"/> <element name="productTierPriceQtyInput" type="input" selector="[name='product[tier_price][{{var1}}][price_qty]']" parameterized="true"/> <element name="productTierPriceValueTypeSelect" type="select" selector="[name='product[tier_price][{{var1}}][value_type]']" parameterized="true"/> <element name="productTierPriceFixedPriceInput" type="input" selector="[name='product[tier_price][{{var1}}][price]']" parameterized="true"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAttributeSection.xml index e159a4ce5c0b6..a2a349ed67611 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAttributeSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAttributeSection.xml @@ -17,8 +17,23 @@ <element name="addValue" type="button" selector="//button[@data-action='add_new_row']" timeout="30"/> <element name="optionViewName" type="text" selector="//table[@data-index='attribute_options_select']//span[contains(text(), '{{arg}}')]" parameterized="true" timeout="30"/> <element name="optionValue" type="input" selector="(//input[contains(@name, 'option[value]')])[{{arg}}]" timeout="30" parameterized="true"/> - <element name="manageTitlesHeader" type="button" selector="//div[@class='fieldset-wrapper-title']//span[contains(text(), 'Manage Titles')]" timeout="30/"/> + <element name="manageTitlesHeader" type="button" selector="//div[@class='fieldset-wrapper-title']//span[contains(text(), 'Manage Titles')]" timeout="30"/> <element name="manageTitlesViewName" type="text" selector="//div[@data-index='manage-titles']//span[contains(text(), '{{arg}}')]" timeout="30" parameterized="true"/> <element name="saveAttribute" type="button" selector="button#save" timeout="30"/> + <element name="saveInNewSet" type="button" selector="button#saveInNewSet" timeout="10"/> + </section> + <section name="AdminProductFormNewAttributeAdvancedSection"> + <element name="sectionHeader" type="button" selector="div[data-index='advanced_fieldset']"/> + <element name="defaultValue" type="textarea" selector="input[name='default_value_text']"/> + </section> + <section name="AdminProductFormNewAttributeStorefrontSection"> + <element name="sectionHeader" type="button" selector="div[data-index='front_fieldset']"/> + <element name="useInSearch" type="checkbox" selector="div[data-index='is_searchable'] .admin__field-control label"/> + <element name="searchWeight" type="select" selector="select[name='search_weight']"/> + </section> + <section name="AdminProductFormNewAttributeNewSetSection"> + <element name="setName" type="button" selector="//div[contains(@class, 'modal-inner-wrap') and .//*[contains(., 'Enter Name for New Attribute Set')]]//input[contains(@class, 'admin__control-text')]"/> + <element name="accept" type="button" selector="//div[contains(@class, 'modal-inner-wrap') and .//*[contains(., 'Enter Name for New Attribute Set')]]//button[contains(@class, 'action-accept')]"/> + <element name="cancel" type="button" selector="//div[contains(@class, 'modal-inner-wrap') and .//*[contains(., 'Enter Name for New Attribute Set')]]//button[contains(@class, 'action-dismiss')]"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml index da282d06145aa..f515171e835db 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml @@ -203,7 +203,9 @@ </section> <section name="AdminProductAttributeSection"> <element name="attributeSectionHeader" type="button" selector="//div[@data-index='attributes']" timeout="30"/> + <element name="textAttributeByCode" type="text" selector="//input[@name='product[{{arg}}]']" parameterized="true"/> + <element name="textAttributeByName" type="text" selector="//div[@data-index='attributes']//fieldset[contains(@class, 'admin__field') and .//*[contains(.,'{{var}}')]]//input" parameterized="true"/> <element name="dropDownAttribute" type="select" selector="//select[@name='product[{{arg}}]']" parameterized="true" timeout="30"/> - <element name="attributeSection" type="div" selector="//div[@data-index='attributes']/div[contains(@class, 'admin__collapsible-content _show')]" timeout="30"/> + <element name="attributeSection" type="block" selector="//div[@data-index='attributes']/div[contains(@class, 'admin__collapsible-content _show')]" timeout="30"/> </section> </sections> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontWidgetsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontWidgetsSection.xml new file mode 100644 index 0000000000000..87aab45bd8cb7 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontWidgetsSection.xml @@ -0,0 +1,16 @@ +<?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="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontWidgetsSection"> + <element name="widgetRecentlyViewedProductsGrid" type="block" selector=".block.widget.block-viewed-products-grid"/> + <element name="widgetRecentlyComparedProductsGrid" type="block" selector=".block.widget.block-compared-products-grid"/> + <element name="widgetRecentlyOrderedProductsGrid" type="block" selector=".block.block-reorder"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AddOutOfStockProductToCompareListTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AddOutOfStockProductToCompareListTest.xml new file mode 100644 index 0000000000000..4f66395bd0fbf --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AddOutOfStockProductToCompareListTest.xml @@ -0,0 +1,94 @@ +<?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="AddOutOfStockProductToCompareListTest"> + <annotations> + <features value="Catalog"/> + <title value="Add out of stock product to compare list"/> + <description value="Add out of stock product to compare list"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-98644"/> + <useCaseId value="MAGETWO-98522"/> + <group value="Catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <magentoCLI command="config:set cataloginventory/options/show_out_of_stock 0" stepKey="displayOutOfStockNo"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <createData entity="SimpleSubCategory" stepKey="category"/> + <createData entity="SimpleProduct4" stepKey="product"> + <requiredEntity createDataKey="category"/> + </createData> + </before> + <after> + <magentoCLI command="config:set cataloginventory/options/show_out_of_stock 0" stepKey="displayOutOfStockNo2"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <deleteData createDataKey="product" stepKey="deleteProduct"/> + <deleteData createDataKey="category" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Open product page--> + <comment userInput="Open product page" stepKey="openProdPage"/> + <amOnPage url="{{StorefrontProductPage.url($$product.name$$)}}" stepKey="goToSimpleProductPage"/> + <waitForPageLoad stepKey="waitForSimpleProductPage"/> + <!--'Add to compare' link is not available--> + <comment userInput="'Add to compare' link is not available" stepKey="addToCompareLinkAvailability"/> + <dontSeeElement selector="{{StorefrontProductInfoMainSection.productAddToCompare}}" stepKey="dontSeeAddToCompareLink"/> + <!--Turn on 'out on stock' config--> + <comment userInput="Turn on 'out of stock' config" stepKey="onOutOfStockConfig"/> + <magentoCLI command="config:set cataloginventory/options/show_out_of_stock 1" stepKey="displayOutOfStockYes"/> + <!--Clear cache and reindex--> + <comment userInput="Clear cache and reindex" stepKey="cleanCache"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <!--Open product page--> + <comment userInput="Open product page" stepKey="openProductPage"/> + <amOnPage url="{{StorefrontProductPage.url($$product.name$$)}}" stepKey="goToSimpleProductPage2"/> + <waitForPageLoad stepKey="waitForSimpleProductPage2"/> + <!--Click on 'Add to Compare' link--> + <comment userInput="Click on 'Add to Compare' link" stepKey="clickOnAddToCompareLink"/> + <click selector="{{StorefrontProductInfoMainSection.productAddToCompare}}" stepKey="clickOnAddToCompare"/> + <waitForPageLoad stepKey="waitForProdAddToCmpList"/> + <!--Assert success message--> + <comment userInput="Assert success message" stepKey="assertSuccessMsg"/> + <grabTextFrom selector="{{AdminProductMessagesSection.successMessage}}" stepKey="grabTextFromSuccessMessage"/> + <assertEquals expected='You added product $$product.name$$ to the comparison list.' expectedType="string" actual="($grabTextFromSuccessMessage)" stepKey="assertSuccessMessage"/> + <!--See product in the comparison list--> + <comment userInput="See product in the comparison list" stepKey="seeProductInComparisonList"/> + <amOnPage url="{{StorefrontProductComparePage.url}}" stepKey="navigateToComparePage"/> + <waitForPageLoad stepKey="waitForStorefrontProductComparePageLoad"/> + <seeElement selector="{{StorefrontProductCompareMainSection.ProductLinkByName($product.name$)}}" stepKey="seeProductInCompareList"/> + <!--Go to Category page and delete product from comparison list--> + <comment userInput="Go to Category page and delete prduct from comparison list" stepKey="deletProdFromCmpList"/> + <amOnPage url="{{StorefrontCategoryPage.url($$category.name$$)}}" stepKey="onCategoryPage"/> + <waitForPageLoad time="30" stepKey="waitForPageLoad1"/> + <click selector="{{StorefrontComparisonSidebarSection.ClearAll}}" stepKey="clickClearAll"/> + <waitForPageLoad time="30" stepKey="waitForConfirmPageLoad"/> + <click selector="{{AdminDeleteRoleSection.confirm}}" stepKey="confirmProdDelate"/> + <waitForPageLoad time="30" stepKey="waitForConfirmLoad"/> + <!--Add product to compare list from Category page--> + <comment userInput="Add product to compare list fom Category page" stepKey="addToCmpFromCategPage"/> + <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverOverProduct"/> + <click selector="{{StorefrontProductInfoMainSection.productAddToCompare}}" stepKey="clickAddToCompare"/> + <waitForPageLoad stepKey="waitProdAddingToCmpList"/> + <!--Assert success message--> + <comment userInput="Assert success message" stepKey="assertSuccessMsg2"/> + <grabTextFrom selector="{{AdminProductMessagesSection.successMessage}}" stepKey="grabTextFromSuccessMessage2"/> + <assertEquals expected='You added product $$product.name$$ to the comparison list.' expectedType="string" actual="($grabTextFromSuccessMessage)" stepKey="assertSuccessMessage2"/> + <!--Check that product displays on add to compare widget--> + <comment userInput="Check that product displays on add to compare widget" stepKey="checkProdNameOnWidget"/> + <seeElement selector="{{StorefrontComparisonSidebarSection.ProductTitleByName($$product.name$$)}}" stepKey="seeProdNameOnCmpWidget"/> + <!--See product in the compare page--> + <comment userInput="See product in the compare page" stepKey="seeProductInComparePage"/> + <amOnPage url="{{StorefrontProductComparePage.url}}" stepKey="navigateToComparePage2"/> + <waitForPageLoad stepKey="waitForStorefrontProductComparePageLoad2"/> + <seeElement selector="{{StorefrontProductCompareMainSection.ProductLinkByName($product.name$)}}" stepKey="seeProductInCompareList2"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml index 88a39a9087bb3..117f094ee0607 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml @@ -43,7 +43,9 @@ <actionGroup ref="saveProductForm" stepKey="saveSimpleProduct"/> <!-- Assert product image in admin product form --> - <actionGroup ref="assertProductImageAdminProductPage" stepKey="assertProductImageAdminProductPage"/> + <actionGroup ref="assertProductImageAdminProductPage" stepKey="assertProductImageAdminProductPage"> + <argument name="image" value="MagentoLogo"/> + </actionGroup> <!-- Assert product in storefront product page --> <actionGroup ref="AssertProductInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCatalogCategoriesNavigateMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCatalogCategoriesNavigateMenuTest.xml new file mode 100644 index 0000000000000..a51df86d0327a --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCatalogCategoriesNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminCatalogCategoriesNavigateMenuTest"> + <annotations> + <features value="Catalog"/> + <stories value="Menu Navigation"/> + <title value="Admin catalog categories navigate menu test"/> + <description value="Admin should be able to navigate to Catalog > Categories"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14131"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToCategoriesPage"> + <argument name="menuUiId" value="{{AdminMenuCatalog.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuCatalogCategories.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuCatalogCategories.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCatalogProductsNavigateMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCatalogProductsNavigateMenuTest.xml new file mode 100644 index 0000000000000..1d9400bf81e4d --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCatalogProductsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminCatalogProductsNavigateMenuTest"> + <annotations> + <features value="Catalog"/> + <stories value="Menu Navigation"/> + <title value="Admin catalog products navigate menu test"/> + <description value="Admin should be able to navigate to Catalog > Products"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14130"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToCatalogProductsPage"> + <argument name="menuUiId" value="{{AdminMenuCatalog.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuCatalogProducts.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuCatalogProducts.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml new file mode 100644 index 0000000000000..b24ed7f9c9a81 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml @@ -0,0 +1,69 @@ +<?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="AdminGridPageNumberAfterSaveAndCloseActionTest"> + <annotations> + <features value="Catalog"/> + <title value="Checking Catalog grid page number after Save and Close action"/> + <description value="Checking Catalog grid page number after Save and Close action"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-96164"/> + <useCaseId value="MAGETWO-96127"/> + <group value="Catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Clear product grid--> + <comment userInput="Clear product grid" stepKey="commentClearProductGrid"/> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="goToProductCatalog"/> + <waitForPageLoad stepKey="waitForProductIndexPage"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" + dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> + <waitForLoadingMaskToDisappear stepKey="waitForGridLoad"/> + <actionGroup ref="deleteProductsIfTheyExist" stepKey="deleteProductIfTheyExist"/> + <createData stepKey="category1" entity="SimpleSubCategory"/> + <createData stepKey="product1" entity="SimpleProduct"> + <requiredEntity createDataKey="category1"/> + </createData> + <createData stepKey="category2" entity="SimpleSubCategory"/> + <createData stepKey="product2" entity="SimpleProduct"> + <requiredEntity createDataKey="category2"/> + </createData> + </before> + <after> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="goToProductCatalog"/> + <waitForPageLoad stepKey="waitForProductIndexPage"/> + <click selector="{{AdminDataGridPaginationSection.previousPage}}" stepKey="clickPrevPageOrderGrid"/> + <actionGroup ref="adminDataGridDeleteCustomPerPage" stepKey="deleteCustomAddedPerPage"> + <argument name="perPage" value="ProductPerPage.productCount"/> + </actionGroup> + <deleteData stepKey="deleteCategory1" createDataKey="category1"/> + <deleteData stepKey="deleteProduct1" createDataKey="product1"/> + <deleteData stepKey="deleteCategory2" createDataKey="category2"/> + <deleteData stepKey="deleteProduct2" createDataKey="product2"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="goToProductCatalog"/> + <waitForPageLoad stepKey="waitForProductIndexPage"/> + <actionGroup ref="adminDataGridSelectCustomPerPage" stepKey="select1OrderPerPage"> + <argument name="perPage" value="ProductPerPage.productCount"/> + </actionGroup> + <!--Go to the next page and edit the product--> + <comment userInput="Go to the next page and edit the product" stepKey="commentEdiProduct"/> + <click selector="{{AdminDataGridPaginationSection.nextPage}}" stepKey="clickNextPageOrderGrid"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct2"> + <argument name="product" value="$$product2$$"/> + </actionGroup> + <actionGroup ref="AdminFormSaveAndClose" stepKey="saveAndCloseProduct"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <seeInField selector="{{AdminDataGridPaginationSection.currentPage}}" userInput="2" stepKey="seeOnSecondPageOrderGrid"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminStoresAttributeSetNavigateMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminStoresAttributeSetNavigateMenuTest.xml new file mode 100644 index 0000000000000..ed29c281b804c --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminStoresAttributeSetNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminStoresAttributeSetNavigateMenuTest"> + <annotations> + <features value="Catalog"/> + <stories value="Menu Navigation"/> + <title value="Admin stores attribute set navigate menu test"/> + <description value="Admin should be able to navigate to Stores > Attribute Set"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14133"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToAttributeSetPage"> + <argument name="menuUiId" value="{{AdminMenuStores.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuStoresAttributesAttributeSet.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuStoresAttributesAttributeSet.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminStoresProductNavigateMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminStoresProductNavigateMenuTest.xml new file mode 100644 index 0000000000000..28a33c4f20c01 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminStoresProductNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminStoresProductNavigateMenuTest"> + <annotations> + <features value="Catalog"/> + <stories value="Menu Navigation"/> + <title value="Admin stores product navigate menu test"/> + <description value="Admin should be able to navigate to Stores > Product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14132"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToProductAttributePage"> + <argument name="menuUiId" value="{{AdminMenuStores.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuStoresAttributesProduct.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuStoresAttributesProduct.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViewsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViewsTest.xml index df4803bcd7906..fb95fc3f57bca 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViewsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViewsTest.xml @@ -52,10 +52,6 @@ <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> - <!-- Reset Product filter --> - - <actionGroup ref="ClearProductsFilterActionGroup" stepKey="clearProductsFilter"/> - <!-- Delete Store View EN --> <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView1"> @@ -67,6 +63,14 @@ <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView2"> <argument name="customStore" value="customStoreFR"/> </actionGroup> + <actionGroup ref="clearFiltersAdminDataGrid" stepKey="clearWebsitesGridFilters"/> + + <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="clearOrdersGridFilter"/> + + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <actionGroup ref="clearFiltersAdminDataGrid" stepKey="clearProductsGridFilters"/> + <actionGroup ref="logout" stepKey="logout"/> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogoutStorefront"/> </after> <!-- Open Product Grid, Filter product and open --> @@ -78,8 +82,9 @@ <argument name="product" value="_defaultProduct"/> </actionGroup> - <click selector="{{AdminProductGridSection.productGridXRowYColumnButton('1', '2')}}" stepKey="openProductForEdit"/> - <waitForPageLoad time="30" stepKey="waitForPageLoad2"/> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProductPage"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> <!-- Update Product with Option Value DropDown 1--> <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.checkIfCustomizableOptionsTabOpen}}" visible="true" stepKey="clickIfContentTabCloses2"/> @@ -101,15 +106,12 @@ <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveButton1"/> <!-- Switcher to Store FR--> - <scrollToTopOfPage stepKey="scrollToTopOfPage1"/> - - <click selector="{{AdminProductFormActionSection.changeStoreButton}}" stepKey="clickStoreSwitcher"/> - <click selector="{{AdminProductFormActionSection.selectStoreView(customStoreFR.name)}}" stepKey="clickStoreView"/> - <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="acceptMessage"/> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="switchToStoreFR"> + <argument name="storeView" value="customStoreFR.name"/> + </actionGroup> <!-- Open tab Customizable Options --> - <waitForPageLoad time="10" stepKey="waitForPageLoad4"/> <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.checkIfCustomizableOptionsTabOpen}}" visible="true" stepKey="clickIfContentTabCloses3"/> <!-- Update Option Customizable Options and Option Value 1--> @@ -129,11 +131,9 @@ <!-- Login Customer Storefront --> - <amOnPage url="{{StorefrontCustomerSignInPage.url}}" stepKey="amOnSignInPage"/> - <waitForPageLoad time="30" stepKey="waitForPageLoad6"/> - <fillField userInput="$$createCustomer.email$$" selector="{{StorefrontCustomerSignInFormSection.emailField}}" stepKey="fillEmail"/> - <fillField userInput="$$createCustomer.password$$" selector="{{StorefrontCustomerSignInFormSection.passwordField}}" stepKey="fillPassword"/> - <click selector="{{StorefrontCustomerSignInFormSection.signInAccountButton}}" stepKey="clickSignInAccountButton"/> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="customerLogin"> + <argument name="Customer" value="$$createCustomer$$" /> + </actionGroup> <!-- Go to Product Page --> @@ -176,24 +176,29 @@ <conditionalClick selector="{{CheckoutPaymentSection.productOptionsByProductItemPrice('150')}}" dependentSelector="{{CheckoutPaymentSection.productOptionsActiveByProductItemPrice('150')}}" visible="false" stepKey="exposeProductOptions1"/> <see selector="{{CheckoutPaymentSection.productOptionsActiveByProductItemPrice('150')}}" userInput="option2" stepKey="seeProductOptionValueDropdown1Input2"/> - <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/> - <waitForPageLoad time="30" stepKey="waitForPageLoad8"/> <!-- Place Order --> - <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyOrder1"/> - <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> + <!--Select shipping method--> + <actionGroup ref="CheckoutSelectFlatRateShippingMethodActionGroup" stepKey="selectFlatRateShippingMethod"/> + <waitForElementVisible selector="{{CheckoutShippingSection.next}}" time="30" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskAfterClickNext"/> + <!--Select payment method--> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectPaymentMethod"/> + <!-- Place Order --> + <actionGroup ref="CheckoutPlaceOrderActionGroup" stepKey="customerPlaceOrder"> + <argument name="orderNumberMessage" value="CONST.successCheckoutOrderNumberMessage"/> + <argument name="emailYouMessage" value="CONST.successCheckoutEmailYouMessage"/> + </actionGroup> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> <!-- Open Order --> - <amOnPage url="{{AdminOrdersPage.url}}" stepKey="onOrdersPage"/> - <waitForPageLoad stepKey="waitForPageLoadOrdersPage"/> - <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearFilters" /> - <fillField selector="{{AdminOrdersGridSection.search}}" userInput="{$grabOrderNumber}" stepKey="fillOrderNum"/> - <click selector="{{AdminOrdersGridSection.submitSearch}}" stepKey="submitSearchOrderNum"/> - <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappearOnSearch"/> + <actionGroup ref="filterOrderGridById" stepKey="openOrdersGrid"> + <argument name="orderId" value="{$grabOrderNumber}"/> + </actionGroup> <click selector="{{AdminOrdersGridSection.firstRow}}" stepKey="clickOrderRow"/> <waitForPageLoad time="30" stepKey="waitForPageLoad10"/> @@ -205,14 +210,12 @@ <!-- Switch to FR Store View Storefront --> <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnProduct4Page"/> - <waitForPageLoad time="30" stepKey="waitForPageLoad11"/> - <click selector="{{StorefrontHeaderSection.storeViewSwitcher}}" stepKey="clickStoreViewSwitcher1"/> - <waitForElementVisible selector="{{StorefrontHeaderSection.storeViewDropdown}}" stepKey="waitForStoreViewDropdown1"/> - <click selector="{{StorefrontHeaderSection.storeViewOption(customStoreFR.code)}}" stepKey="selectStoreView1"/> - <waitForPageLoad stepKey="waitForPageLoad12"/> - <amOnPage url="{{StorefrontHomePage.url}}$$createProduct.custom_attributes[url_key]$$.html" stepKey="amOnProduct2Page"/> - <waitForPageLoad time="30" stepKey="waitForPageLoad13"/> + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchStore"> + <argument name="storeView" value="customStoreFR"/> + </actionGroup> + + <amOnPage url="{{StorefrontProductPage.url($$createProduct.custom_attributes[url_key]$$)}}" stepKey="amOnProduct2Page"/> <seeElement selector="{{StorefrontProductInfoMainSection.productOptionDropDownTitle('FR Custom Options 1')}}" stepKey="seeProductFrOptionDropDownTitle"/> <seeElement selector="{{StorefrontProductInfoMainSection.productOptionDropDownOptionTitle('FR Custom Options 1', 'FR option1')}}" stepKey="productFrOptionDropDownOptionTitle1"/> @@ -250,13 +253,22 @@ <conditionalClick selector="{{CheckoutPaymentSection.productOptionsByProductItemPrice('150')}}" dependentSelector="{{CheckoutPaymentSection.productOptionsActiveByProductItemPrice('150')}}" visible="false" stepKey="exposeProductOptions3"/> <see selector="{{CheckoutPaymentSection.productOptionsActiveByProductItemPrice('150')}}" userInput="FR option2" stepKey="seeProductFrOptionValueDropdown1Input3"/> - <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext1"/> - <waitForPageLoad time="30" stepKey="waitForPageLoad14"/> <!-- Place Order --> - <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyOrder2"/> - <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder1"/> + <!--Select shipping method--> + <actionGroup ref="CheckoutSelectFlatRateShippingMethodActionGroup" stepKey="selectFlatRateShippingMethod2"/> + <waitForElementVisible selector="{{CheckoutShippingSection.next}}" time="30" stepKey="waitForNextButton2"/> + <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext2"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskAfterClickNext2"/> + + <!--Select payment method--> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectPaymentMethod2"/> + <!-- Place Order --> + <actionGroup ref="CheckoutPlaceOrderActionGroup" stepKey="customerPlaceOrder2"> + <argument name="orderNumberMessage" value="CONST.successCheckoutOrderNumberMessage"/> + <argument name="emailYouMessage" value="CONST.successCheckoutEmailYouMessage"/> + </actionGroup> <!-- Open Product Grid, Filter product and open --> @@ -296,8 +308,7 @@ <!--Go to Product Page--> - <amOnPage url="{{StorefrontHomePage.url}}$$createProduct.custom_attributes[url_key]$$.html" stepKey="amOnProduct2Page2"/> - <waitForPageLoad time="30" stepKey="waitForPageLoad20"/> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.custom_attributes[url_key]$$)}}" stepKey="amOnProduct2Page2"/> <seeElement selector="{{StorefrontProductInfoMainSection.productOptionDropDownTitle('Custom Options 1')}}" stepKey="seeProductOptionDropDownTitle1"/> <seeElement selector="{{StorefrontProductInfoMainSection.productOptionDropDownOptionTitle('Custom Options 1', 'option1')}}" stepKey="seeProductOptionDropDownOptionTitle3"/> diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Category/ViewTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Category/ViewTest.php index d93520297e485..60c6f2f1bd821 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Category/ViewTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Category/ViewTest.php @@ -124,7 +124,7 @@ protected function setUp() ->disableOriginalConstructor()->getMock(); $this->pageConfig->expects($this->any())->method('addBodyClass')->will($this->returnSelf()); - $this->page = $this->getMockBuilder(\Magento\Framework\View\Page::class) + $this->page = $this->getMockBuilder(\Magento\Framework\View\Result\Page::class) ->setMethods(['getConfig', 'initLayout', 'addPageLayoutHandles', 'getLayout', 'addUpdate']) ->disableOriginalConstructor()->getMock(); $this->page->expects($this->any())->method('getConfig')->will($this->returnValue($this->pageConfig)); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php index e9eee5c766883..80b6db2a516bd 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php @@ -6,10 +6,12 @@ namespace Magento\Catalog\Test\Unit\Model\Product; use Magento\Catalog\Api\Data\ProductInterface; -use \Magento\Catalog\Model\Product\Copier; use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Copier; /** + * Test for Magento\Catalog\Model\Product\Copier class. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CopierTest extends \PHPUnit\Framework\TestCase @@ -76,6 +78,9 @@ protected function setUp() ]); } + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ public function testCopy() { $stockItem = $this->getMockBuilder(\Magento\CatalogInventory\Api\Data\StockItemInterface::class) @@ -103,8 +108,44 @@ public function testCopy() ['linkField', null, '1'], ]); - $resourceMock = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product::class); - $this->productMock->expects($this->once())->method('getResource')->will($this->returnValue($resourceMock)); + $entityMock = $this->getMockForAbstractClass( + \Magento\Eav\Model\Entity\AbstractEntity::class, + [], + '', + false, + true, + true, + ['checkAttributeUniqueValue'] + ); + $entityMock->expects($this->any()) + ->method('checkAttributeUniqueValue') + ->willReturn(true); + + $attributeMock = $this->getMockForAbstractClass( + \Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class, + [], + '', + false, + true, + true, + ['getEntity'] + ); + $attributeMock->expects($this->any()) + ->method('getEntity') + ->willReturn($entityMock); + + $resourceMock = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributeRawValue', 'duplicate', 'getAttribute']) + ->getMock(); + $resourceMock->expects($this->any()) + ->method('getAttributeRawValue') + ->willReturn('urk-key-1'); + $resourceMock->expects($this->any()) + ->method('getAttribute') + ->willReturn($attributeMock); + + $this->productMock->expects($this->any())->method('getResource')->will($this->returnValue($resourceMock)); $duplicateMock = $this->createPartialMock( Product::class, @@ -119,11 +160,11 @@ public function testCopy() 'setCreatedAt', 'setUpdatedAt', 'setId', - 'setStoreId', 'getEntityId', 'save', 'setUrlKey', - 'getUrlKey', + 'setStoreId', + 'getStoreIds', ] ); $this->productFactoryMock->expects($this->once())->method('create')->will($this->returnValue($duplicateMock)); @@ -138,19 +179,13 @@ public function testCopy() )->with( \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED ); + $duplicateMock->expects($this->atLeastOnce())->method('setStoreId'); $duplicateMock->expects($this->once())->method('setCreatedAt')->with(null); $duplicateMock->expects($this->once())->method('setUpdatedAt')->with(null); $duplicateMock->expects($this->once())->method('setId')->with(null); - $duplicateMock->expects( - $this->once() - )->method( - 'setStoreId' - )->with( - \Magento\Store\Model\Store::DEFAULT_STORE_ID - ); + $duplicateMock->expects($this->atLeastOnce())->method('getStoreIds')->willReturn([]); $duplicateMock->expects($this->atLeastOnce())->method('setData')->willReturn($duplicateMock); $this->copyConstructorMock->expects($this->once())->method('build')->with($this->productMock, $duplicateMock); - $duplicateMock->expects($this->once())->method('getUrlKey')->willReturn('urk-key-1'); $duplicateMock->expects($this->once())->method('setUrlKey')->with('urk-key-2')->willReturn($duplicateMock); $duplicateMock->expects($this->once())->method('save'); @@ -158,7 +193,8 @@ public function testCopy() $duplicateMock->expects($this->any())->method('getData')->willReturnMap([ ['linkField', null, '2'], - ]); $this->optionRepositoryMock->expects($this->once()) + ]); + $this->optionRepositoryMock->expects($this->once()) ->method('duplicate') ->with($this->productMock, $duplicateMock); $resourceMock->expects($this->once())->method('duplicate')->with(1, 2); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php index c729a0c58e1ec..cb92cc6c2d523 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -9,30 +8,41 @@ namespace Magento\Catalog\Test\Unit\Model; use Magento\Catalog\Api\Data\ProductExtensionInterface; +use Magento\Catalog\Api\Data\ProductSearchResultsInterface; use Magento\Catalog\Api\Data\ProductSearchResultsInterfaceFactory; use Magento\Catalog\Api\ProductAttributeRepositoryInterface; use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Gallery\MimeTypeExtensionMap; +use Magento\Catalog\Model\Product\Gallery\Processor; use Magento\Catalog\Model\Product\LinkTypeProvider; +use Magento\Catalog\Model\Product\Media\Config; +use Magento\Catalog\Model\Product\Option; +use Magento\Catalog\Model\Product\Option\Value; use Magento\Catalog\Model\ProductFactory; +use Magento\Catalog\Model\ProductLink\Link; use Magento\Catalog\Model\ProductRepository; +use Magento\Catalog\Model\ResourceModel\Product\Collection; use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; +use Magento\Framework\Api\Data\ImageContentInterface; use Magento\Framework\Api\Data\ImageContentInterfaceFactory; use Magento\Framework\Api\ExtensibleDataObjectConverter; use Magento\Framework\Api\FilterBuilder; +use Magento\Framework\Api\ImageContent; use Magento\Framework\Api\ImageContentValidator; use Magento\Framework\Api\ImageContentValidatorInterface; use Magento\Framework\Api\ImageProcessorInterface; -use Magento\Framework\Api\Data\ImageContentInterface; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Api\SearchCriteriaInterface; use Magento\Framework\DB\Adapter\ConnectionException; use Magento\Framework\Filesystem; use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\Store; use Magento\Store\Model\StoreManagerInterface; +use PHPUnit\Framework\TestCase; use PHPUnit_Framework_MockObject_MockObject as MockObject; /** @@ -42,12 +52,12 @@ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ -class ProductRepositoryTest extends \PHPUnit\Framework\TestCase +class ProductRepositoryTest extends TestCase { /** * @var Product|MockObject */ - protected $product; + private $product; /** * @var Product|MockObject @@ -153,12 +163,12 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase private $storeManager; /** - * @var \Magento\Catalog\Model\Product\Gallery\Processor|\PHPUnit_Framework_MockObject_MockObject + * @var Processor|MockObject */ - private $mediaGalleryProcessor; + private $processor; /** - * @var CollectionProcessorInterface|\PHPUnit_Framework_MockObject_MockObject + * @var CollectionProcessorInterface|MockObject */ private $collectionProcessor; @@ -168,7 +178,7 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase private $productExtension; /** - * @var Json|\PHPUnit_Framework_MockObject_MockObject + * @var Json|MockObject */ private $serializerMock; @@ -185,12 +195,12 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase protected function setUp() { $this->productFactory = $this->createPartialMock( - \Magento\Catalog\Model\ProductFactory::class, + ProductFactory::class, ['create', 'setData'] ); $this->product = $this->createPartialMock( - \Magento\Catalog\Model\Product::class, + Product::class, [ 'getId', 'getSku', @@ -200,12 +210,13 @@ protected function setUp() 'setData', 'getStoreId', 'getMediaGalleryEntries', - 'getExtensionAttributes' + 'getExtensionAttributes', + 'getCategoryIds' ] ); $this->initializedProduct = $this->createPartialMock( - \Magento\Catalog\Model\Product::class, + Product::class, [ 'getWebsiteIds', 'setProductOptions', @@ -220,7 +231,8 @@ protected function setUp() 'validate', 'save', 'getMediaGalleryEntries', - 'getExtensionAttributes' + 'getExtensionAttributes', + 'getCategoryIds' ] ); $this->initializedProduct->expects($this->any()) @@ -232,7 +244,7 @@ protected function setUp() $this->searchCriteriaBuilder = $this->createMock(SearchCriteriaBuilder::class); $this->metadataService = $this->createMock(ProductAttributeRepositoryInterface::class); $this->searchResultsFactory = $this->createPartialMock( - \Magento\Catalog\Api\Data\ProductSearchResultsInterfaceFactory::class, + ProductSearchResultsInterfaceFactory::class, ['create'] ); $this->resourceModel = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product::class); @@ -269,15 +281,21 @@ protected function setUp() $this->initializedProduct ->method('getExtensionAttributes') ->willReturn($this->productExtension); + $this->product + ->method('getCategoryIds') + ->willReturn([1, 2, 3, 4]); + $this->initializedProduct + ->method('getCategoryIds') + ->willReturn([1, 2, 3, 4]); $storeMock = $this->getMockBuilder(StoreInterface::class) ->disableOriginalConstructor() ->setMethods([]) ->getMockForAbstractClass(); $storeMock->expects($this->any())->method('getWebsiteId')->willReturn('1'); - $storeMock->expects($this->any())->method('getCode')->willReturn(\Magento\Store\Model\Store::ADMIN_CODE); + $storeMock->expects($this->any())->method('getCode')->willReturn(Store::ADMIN_CODE); $this->storeManager->expects($this->any())->method('getStore')->willReturn($storeMock); - $this->mediaGalleryProcessor = $this->createMock(\Magento\Catalog\Model\Product\Gallery\Processor::class); + $this->processor = $this->createMock(Processor::class); $this->collectionProcessor = $this->getMockBuilder(CollectionProcessorInterface::class) ->getMock(); @@ -293,6 +311,14 @@ function ($value) { ) ); + $mediaProcessor = $this->objectManager->getObject( + ProductRepository\MediaGalleryProcessor::class, + [ + 'processor' => $this->processor, + 'contentFactory' => $this->contentFactory, + 'imageProcessor' => $this->imageProcessor, + ] + ); $this->model = $this->objectManager->getObject( ProductRepository::class, [ @@ -307,17 +333,16 @@ function ($value) { 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverter, 'contentValidator' => $this->contentValidator, 'fileSystem' => $this->fileSystem, - 'contentFactory' => $this->contentFactory, 'mimeTypeExtensionMap' => $this->mimeTypeExtensionMap, 'linkTypeProvider' => $this->linkTypeProvider, - 'imageProcessor' => $this->imageProcessor, 'storeManager' => $this->storeManager, - 'mediaGalleryProcessor' => $this->mediaGalleryProcessor, + 'mediaGalleryProcessor' => $this->processor, 'collectionProcessor' => $this->collectionProcessor, 'serializer' => $this->serializerMock, 'cacheLimit' => $this->cacheLimit ] ); + $this->objectManager->setBackwardCompatibleProperty($this->model, 'mediaProcessor', $mediaProcessor); } /** @@ -500,7 +525,7 @@ private function getProductMocksForReducedCache($productsCount) $productMocks = []; for ($i = 1; $i <= $productsCount; $i++) { - $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + $productMock = $this->getMockBuilder(Product::class) ->disableOriginalConstructor() ->setMethods([ 'getId', @@ -753,8 +778,8 @@ public function testDeleteById() public function testGetList() { - $searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteriaInterface::class); - $collectionMock = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product\Collection::class); + $searchCriteriaMock = $this->createMock(SearchCriteriaInterface::class); + $collectionMock = $this->createMock(Collection::class); $this->collectionFactory->expects($this->once())->method('create')->willReturn($collectionMock); $this->product->method('getSku')->willReturn('simple'); $collectionMock->expects($this->once())->method('addAttributeToSelect')->with('*'); @@ -769,7 +794,7 @@ public function testGetList() $collectionMock->expects($this->once())->method('addCategoryIds'); $collectionMock->expects($this->atLeastOnce())->method('getItems')->willReturn([$this->product]); $collectionMock->expects($this->once())->method('getSize')->willReturn(128); - $searchResultsMock = $this->createMock(\Magento\Catalog\Api\Data\ProductSearchResultsInterface::class); + $searchResultsMock = $this->createMock(ProductSearchResultsInterface::class); $searchResultsMock->expects($this->once())->method('setSearchCriteria')->with($searchCriteriaMock); $searchResultsMock->expects($this->once())->method('setItems')->with([$this->product]); $this->searchResultsFactory->expects($this->once())->method('create')->willReturn($searchResultsMock); @@ -903,8 +928,8 @@ public function saveExistingWithOptionsDataProvider() ], ]; - /** @var \Magento\Catalog\Model\Product\Option|\PHPUnit_Framework_MockObject_MockObject $existingOption1 */ - $existingOption1 = $this->getMockBuilder(\Magento\Catalog\Model\Product\Option::class) + /** @var Option|MockObject $existingOption1 */ + $existingOption1 = $this->getMockBuilder(Option::class) ->disableOriginalConstructor() ->setMethods(null) ->getMock(); @@ -914,8 +939,8 @@ public function saveExistingWithOptionsDataProvider() "type" => "drop_down", ] ); - /** @var \Magento\Catalog\Model\Product\Option\Value $existingOptionValue1 */ - $existingOptionValue1 = $this->getMockBuilder(\Magento\Catalog\Model\Product\Option\Value::class) + /** @var Value $existingOptionValue1 */ + $existingOptionValue1 = $this->getMockBuilder(Value::class) ->disableOriginalConstructor() ->setMethods(null) ->getMock(); @@ -926,7 +951,7 @@ public function saveExistingWithOptionsDataProvider() "price" => 5, ] ); - $existingOptionValue2 = $this->getMockBuilder(\Magento\Catalog\Model\Product\Option\Value::class) + $existingOptionValue2 = $this->getMockBuilder(Value::class) ->disableOriginalConstructor() ->setMethods(null) ->getMock(); @@ -943,7 +968,7 @@ public function saveExistingWithOptionsDataProvider() "9" => $existingOptionValue2, ] ); - $existingOption2 = $this->getMockBuilder(\Magento\Catalog\Model\Product\Option::class) + $existingOption2 = $this->getMockBuilder(Option::class) ->disableOriginalConstructor() ->setMethods(null) ->getMock(); @@ -1008,8 +1033,8 @@ public function saveExistingWithOptionsDataProvider() * @param array $existingLinks * @param array $expectedData * @dataProvider saveWithLinksDataProvider - * @throws \Magento\Framework\Exception\CouldNotSaveException - * @throws \Magento\Framework\Exception\InputException + * @throws CouldNotSaveException + * @throws InputException */ public function testSaveWithLinks(array $newLinks, array $existingLinks, array $expectedData) { @@ -1037,7 +1062,7 @@ public function testSaveWithLinks(array $newLinks, array $existingLinks, array $ ->expects($this->any())->method('getProductsIdsBySkus') ->willReturn([$newLinks['linked_product_sku'] => $newLinks['linked_product_sku']]); - $inputLink = $this->objectManager->getObject(\Magento\Catalog\Model\ProductLink\Link::class); + $inputLink = $this->objectManager->getObject(Link::class); $inputLink->setProductSku($newLinks['product_sku']); $inputLink->setLinkType($newLinks['link_type']); $inputLink->setLinkedProductSku($newLinks['linked_product_sku']); @@ -1081,7 +1106,7 @@ public function testSaveWithLinks(array $newLinks, array $existingLinks, array $ $outputLinks = []; if (!empty($expectedData)) { foreach ($expectedData as $link) { - $outputLink = $this->objectManager->getObject(\Magento\Catalog\Model\ProductLink\Link::class); + $outputLink = $this->objectManager->getObject(Link::class); $outputLink->setProductSku($link['product_sku']); $outputLink->setLinkType($link['link_type']); $outputLink->setLinkedProductSku($link['linked_product_sku']); @@ -1235,10 +1260,10 @@ public function testSaveExistingWithNewMediaGalleryEntries() $mediaTmpPath = '/tmp'; $absolutePath = '/a/b/filename.jpg'; - $this->mediaGalleryProcessor->expects($this->once())->method('clearMediaAttribute') + $this->processor->expects($this->once())->method('clearMediaAttribute') ->with($this->initializedProduct, ['image', 'small_image']); - $mediaConfigMock = $this->getMockBuilder(\Magento\Catalog\Model\Product\Media\Config::class) + $mediaConfigMock = $this->getMockBuilder(Config::class) ->disableOriginalConstructor() ->getMock(); $mediaConfigMock->expects($this->once()) @@ -1250,7 +1275,7 @@ public function testSaveExistingWithNewMediaGalleryEntries() ->willReturn($mediaConfigMock); //verify new entries - $contentDataObject = $this->getMockBuilder(\Magento\Framework\Api\ImageContent::class) + $contentDataObject = $this->getMockBuilder(ImageContent::class) ->disableOriginalConstructor() ->setMethods(null) ->getMock(); @@ -1263,10 +1288,10 @@ public function testSaveExistingWithNewMediaGalleryEntries() ->willReturn($absolutePath); $imageFileUri = "imageFileUri"; - $this->mediaGalleryProcessor->expects($this->once())->method('addImage') + $this->processor->expects($this->once())->method('addImage') ->with($this->initializedProduct, $mediaTmpPath . $absolutePath, ['image', 'small_image'], true, false) ->willReturn($imageFileUri); - $this->mediaGalleryProcessor->expects($this->once())->method('updateImage') + $this->processor->expects($this->once())->method('updateImage') ->with( $this->initializedProduct, $imageFileUri, @@ -1386,9 +1411,9 @@ public function testSaveExistingWithMediaGalleryEntries() ->method('getMediaAttributes') ->willReturn(["image" => "filename1", "small_image" => "filename2"]); - $this->mediaGalleryProcessor->expects($this->once())->method('clearMediaAttribute') + $this->processor->expects($this->once())->method('clearMediaAttribute') ->with($this->initializedProduct, ['image', 'small_image']); - $this->mediaGalleryProcessor->expects($this->once()) + $this->processor->expects($this->once()) ->method('setMediaAttribute') ->with($this->initializedProduct, ['image', 'small_image'], 'filename1'); $this->initializedProduct->expects($this->atLeastOnce()) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php index 5da5625189ee3..0316b2e374d2f 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php @@ -5,33 +5,13 @@ */ namespace Magento\Catalog\Test\Unit\Model\ResourceModel\Product; -use Magento\Catalog\Model\Indexer; -use Magento\Catalog\Model\Product as ProductModel; -use Magento\Catalog\Model\ResourceModel\Product as ProductResource; use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; use Magento\Framework\DB\Select; -use Magento\Eav\Model\Entity\AbstractEntity; -use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; -use Magento\Eav\Model\EntityFactory; -use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Framework\App\ResourceConnection; -use Magento\Framework\Data\Collection; -use Magento\Framework\Data\Collection\Db\FetchStrategyInterface; -use Magento\Framework\DB; -use Magento\Framework\EntityManager\EntityMetadataInterface; -use Magento\Framework\EntityManager\MetadataPool; -use Magento\Framework\Event; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; -use Magento\Framework\Stdlib\DateTime\TimezoneInterface; -use Magento\Store\Api\Data\StoreInterface; -use Magento\Store\Model\StoreManagerInterface; -use PHPUnit\Framework\TestCase; -use Psr\Log\LoggerInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class CollectionTest extends TestCase +class CollectionTest extends \PHPUnit\Framework\TestCase { /** * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager @@ -44,12 +24,12 @@ class CollectionTest extends TestCase protected $selectMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject|DB\Adapter\AdapterInterface + * @var \PHPUnit_Framework_MockObject_MockObject */ protected $connectionMock; /** - * @var ProductResource\Collection + * @var \Magento\Catalog\Model\ResourceModel\Product\Collection */ protected $collection; @@ -90,50 +70,121 @@ protected function setUp() { $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->entityFactory = $this->createMock(\Magento\Framework\Data\Collection\EntityFactory::class); - $this->selectMock = $this->createMock(DB\Select::class); - $this->connectionMock = $this->createMock(DB\Adapter\AdapterInterface::class); - $this->connectionMock->expects($this->atLeastOnce())->method('select')->willReturn($this->selectMock); - $this->entityMock = $this->createMock(AbstractEntity::class); + $logger = $this->getMockBuilder(\Psr\Log\LoggerInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $fetchStrategy = $this->getMockBuilder(\Magento\Framework\Data\Collection\Db\FetchStrategyInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $eventManager = $this->getMockBuilder(\Magento\Framework\Event\ManagerInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $eavConfig = $this->getMockBuilder(\Magento\Eav\Model\Config::class) + ->disableOriginalConstructor() + ->getMock(); + $resource = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + $eavEntityFactory = $this->getMockBuilder(\Magento\Eav\Model\EntityFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $resourceHelper = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Helper::class) + ->disableOriginalConstructor() + ->getMock(); + $universalFactory = $this->getMockBuilder(\Magento\Framework\Validator\UniversalFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getStore', 'getId', 'getWebsiteId']) + ->getMockForAbstractClass(); + $moduleManager = $this->getMockBuilder(\Magento\Framework\Module\Manager::class) + ->disableOriginalConstructor() + ->getMock(); + $catalogProductFlatState = $this->getMockBuilder(\Magento\Catalog\Model\Indexer\Product\Flat\State::class) + ->disableOriginalConstructor() + ->getMock(); + $scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $productOptionFactory = $this->getMockBuilder(\Magento\Catalog\Model\Product\OptionFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $catalogUrl = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Url::class) + ->disableOriginalConstructor() + ->getMock(); + $localeDate = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $customerSession = $this->getMockBuilder(\Magento\Customer\Model\Session::class) + ->disableOriginalConstructor() + ->getMock(); + $dateTime = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime::class) + ->disableOriginalConstructor() + ->getMock(); + $groupManagement = $this->getMockBuilder(\Magento\Customer\Api\GroupManagementInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->setMethods(['getId']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class) + ->disableOriginalConstructor() + ->getMock(); + $this->entityMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\AbstractEntity::class) + ->disableOriginalConstructor() + ->getMock(); + $this->galleryResourceMock = $this->getMockBuilder( + \Magento\Catalog\Model\ResourceModel\Product\Gallery::class + )->disableOriginalConstructor()->getMock(); + $this->metadataPoolMock = $this->getMockBuilder( + \Magento\Framework\EntityManager\MetadataPool::class + )->disableOriginalConstructor()->getMock(); + $this->galleryReadHandlerMock = $this->getMockBuilder( + \Magento\Catalog\Model\Product\Gallery\ReadHandler::class + )->disableOriginalConstructor()->getMock(); + $this->storeManager->expects($this->any())->method('getId')->willReturn(1); + $this->storeManager->expects($this->any())->method('getStore')->willReturnSelf(); + $universalFactory->expects($this->exactly(1))->method('create')->willReturnOnConsecutiveCalls( + $this->entityMock + ); $this->entityMock->expects($this->once())->method('getConnection')->willReturn($this->connectionMock); $this->entityMock->expects($this->once())->method('getDefaultAttributes')->willReturn([]); - $this->entityMock->method('getTable')->willReturnArgument(0); - $this->galleryResourceMock = $this->createMock(ProductResource\Gallery::class); - $this->metadataPoolMock = $this->createMock(MetadataPool::class); - $this->galleryReadHandlerMock = $this->createMock(ProductModel\Gallery\ReadHandler::class); + $this->entityMock->expects($this->any())->method('getTable')->willReturnArgument(0); + $this->connectionMock->expects($this->atLeastOnce())->method('select')->willReturn($this->selectMock); - $storeStub = $this->createMock(StoreInterface::class); - $storeStub->method('getId')->willReturn(1); - $storeStub->method('getWebsiteId')->willReturn(1); - $this->storeManager = $this->createMock(StoreManagerInterface::class); - $this->storeManager->method('getStore')->willReturn($storeStub); - $resourceModelPool = $this->createMock(ResourceModelPoolInterface::class); - $resourceModelPool->expects($this->exactly(1))->method('get')->willReturn($this->entityMock); + $productLimitationMock = $this->createMock( + \Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitation::class + ); + $productLimitationFactoryMock = $this->getMockBuilder( + ProductLimitationFactory::class + )->disableOriginalConstructor()->setMethods(['create'])->getMock(); - $productLimitationFactoryMock = $this->createPartialMock(ProductLimitationFactory::class, ['create']); $productLimitationFactoryMock->method('create') - ->willReturn($this->createMock(ProductResource\Collection\ProductLimitation::class)); + ->willReturn($productLimitationMock); $this->collection = $this->objectManager->getObject( - ProductResource\Collection::class, + \Magento\Catalog\Model\ResourceModel\Product\Collection::class, [ 'entityFactory' => $this->entityFactory, - 'logger' => $this->createMock(LoggerInterface::class), - 'fetchStrategy' => $this->createMock(FetchStrategyInterface::class), - 'eventManager' => $this->createMock(Event\ManagerInterface::class), - 'eavConfig' => $this->createMock(\Magento\Eav\Model\Config::class), - 'resource' => $this->createMock(ResourceConnection::class), - 'eavEntityFactory' => $this->createMock(EntityFactory::class), - 'resourceHelper' => $this->createMock(\Magento\Catalog\Model\ResourceModel\Helper::class), - 'resourceModelPool' => $resourceModelPool, + 'logger' => $logger, + 'fetchStrategy' => $fetchStrategy, + 'eventManager' => $eventManager, + 'eavConfig' => $eavConfig, + 'resource' => $resource, + 'eavEntityFactory' => $eavEntityFactory, + 'resourceHelper' => $resourceHelper, + 'universalFactory' => $universalFactory, 'storeManager' => $this->storeManager, - 'moduleManager' => $this->createMock(\Magento\Framework\Module\Manager::class), - 'catalogProductFlatState' => $this->createMock(Indexer\Product\Flat\State::class), - 'scopeConfig' => $this->createMock(ScopeConfigInterface::class), - 'productOptionFactory' => $this->createMock(ProductModel\OptionFactory::class), - 'catalogUrl' => $this->createMock(\Magento\Catalog\Model\ResourceModel\Url::class), - 'localeDate' => $this->createMock(TimezoneInterface::class), - 'customerSession' => $this->createMock(\Magento\Customer\Model\Session::class), - 'dateTime' => $this->createMock(\Magento\Framework\Stdlib\DateTime::class), - 'groupManagement' => $this->createMock(\Magento\Customer\Api\GroupManagementInterface::class), + 'moduleManager' => $moduleManager, + 'catalogProductFlatState' => $catalogProductFlatState, + 'scopeConfig' => $scopeConfig, + 'productOptionFactory' => $productOptionFactory, + 'catalogUrl' => $catalogUrl, + 'localeDate' => $localeDate, + 'customerSession' => $customerSession, + 'dateTime' => $dateTime, + 'groupManagement' => $groupManagement, 'connection' => $this->connectionMock, 'productLimitationFactory' => $productLimitationFactoryMock, 'metadataPool' => $this->metadataPoolMock, @@ -158,8 +209,9 @@ public function testAddProductCategoriesFilter() $condition = ['in' => [1, 2]]; $values = [1, 2]; $conditionType = 'nin'; - $preparedSql = 'category_id IN(1,2)'; - $tableName = 'catalog_category_product'; + $preparedSql = "category_id IN(1,2)"; + $tableName = "catalog_category_product"; + $this->connectionMock->expects($this->any())->method('getId')->willReturn(1); $this->connectionMock->expects($this->exactly(2))->method('prepareSqlCondition')->withConsecutive( ['cat.category_id', $condition], ['e.entity_id', [$conditionType => $this->selectMock]] @@ -184,14 +236,19 @@ public function testAddMediaGalleryData() $rowId = 4; $linkField = 'row_id'; $mediaGalleriesMock = [[$linkField => $rowId]]; - /** @var ProductModel|\PHPUnit_Framework_MockObject_MockObject $itemMock */ - $itemMock = $this->getMockBuilder(ProductModel::class) + $itemMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) ->disableOriginalConstructor() ->setMethods(['getOrigData']) ->getMock(); - $attributeMock = $this->createMock(AbstractAttribute::class); - $selectMock = $this->createMock(DB\Select::class); - $metadataMock = $this->createMock(EntityMetadataInterface::class); + $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) + ->disableOriginalConstructor() + ->getMock(); + $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class) + ->disableOriginalConstructor() + ->getMock(); + $metadataMock = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class) + ->disableOriginalConstructor() + ->getMock(); $this->collection->addItem($itemMock); $this->galleryResourceMock->expects($this->once())->method('createBatchBaseSelect')->willReturn($selectMock); $attributeMock->expects($this->once())->method('getAttributeId')->willReturn($attributeId); @@ -221,15 +278,25 @@ public function testAddMediaGalleryData() public function testAddTierPriceDataByGroupId() { $customerGroupId = 2; - /** @var ProductModel|\PHPUnit_Framework_MockObject_MockObject $itemMock */ - $itemMock = $this->createMock(ProductModel::class); - $attributeMock = $this->getMockBuilder(AbstractAttribute::class) + $itemMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + ->disableOriginalConstructor() + ->setMethods(['getData']) + ->getMock(); + $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) ->disableOriginalConstructor() ->setMethods(['isScopeGlobal', 'getBackend']) ->getMock(); - $backend = $this->createMock(ProductModel\Attribute\Backend\Tierprice::class); - $resource = $this->createMock(ProductResource\Attribute\Backend\GroupPrice\AbstractGroupPrice::class); - $select = $this->createMock(DB\Select::class); + $backend = $this->getMockBuilder(\Magento\Catalog\Model\Product\Attribute\Backend\Tierprice::class) + ->disableOriginalConstructor() + ->getMock(); + $resource = $this->getMockBuilder( + \Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\GroupPrice\AbstractGroupPrice::class + ) + ->disableOriginalConstructor() + ->getMock(); + $select = $this->getMockBuilder(\Magento\Framework\DB\Select::class) + ->disableOriginalConstructor() + ->getMock(); $this->connectionMock->expects($this->once())->method('getAutoIncrementField')->willReturn('entity_id'); $this->collection->addItem($itemMock); $itemMock->expects($this->atLeastOnce())->method('getData')->with('entity_id')->willReturn(1); @@ -239,6 +306,7 @@ public function testAddTierPriceDataByGroupId() ->willReturn($attributeMock); $attributeMock->expects($this->atLeastOnce())->method('getBackend')->willReturn($backend); $attributeMock->expects($this->once())->method('isScopeGlobal')->willReturn(false); + $this->storeManager->expects($this->once())->method('getWebsiteId')->willReturn(1); $backend->expects($this->once())->method('getResource')->willReturn($resource); $resource->expects($this->once())->method('getSelect')->willReturn($select); $select->expects($this->once())->method('columns')->with(['product_id' => 'entity_id'])->willReturnSelf(); @@ -265,22 +333,25 @@ public function testAddTierPriceDataByGroupId() */ public function testAddTierPriceData() { - /** @var ProductModel|\PHPUnit_Framework_MockObject_MockObject $itemMock */ - $itemMock = $this->getMockBuilder(ProductModel::class) + $itemMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) ->disableOriginalConstructor() ->setMethods(['getData']) ->getMock(); - $attributeMock = $this->getMockBuilder(AbstractAttribute::class) + $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) ->disableOriginalConstructor() ->setMethods(['isScopeGlobal', 'getBackend']) ->getMock(); - $backend = $this->createMock(ProductModel\Attribute\Backend\Tierprice::class); + $backend = $this->getMockBuilder(\Magento\Catalog\Model\Product\Attribute\Backend\Tierprice::class) + ->disableOriginalConstructor() + ->getMock(); $resource = $this->getMockBuilder( - ProductResource\Attribute\Backend\GroupPrice\AbstractGroupPrice::class + \Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\GroupPrice\AbstractGroupPrice::class ) ->disableOriginalConstructor() ->getMock(); - $select = $this->createMock(DB\Select::class); + $select = $this->getMockBuilder(\Magento\Framework\DB\Select::class) + ->disableOriginalConstructor() + ->getMock(); $this->connectionMock->expects($this->once())->method('getAutoIncrementField')->willReturn('entity_id'); $this->collection->addItem($itemMock); $itemMock->expects($this->atLeastOnce())->method('getData')->with('entity_id')->willReturn(1); @@ -290,6 +361,7 @@ public function testAddTierPriceData() ->willReturn($attributeMock); $attributeMock->expects($this->atLeastOnce())->method('getBackend')->willReturn($backend); $attributeMock->expects($this->once())->method('isScopeGlobal')->willReturn(false); + $this->storeManager->expects($this->once())->method('getWebsiteId')->willReturn(1); $backend->expects($this->once())->method('getResource')->willReturn($resource); $resource->expects($this->once())->method('getSelect')->willReturn($select); $select->expects($this->once())->method('columns')->with(['product_id' => 'entity_id'])->willReturnSelf(); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Link/Product/CollectionTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Link/Product/CollectionTest.php index 80180d2033ce5..596148b627506 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Link/Product/CollectionTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Link/Product/CollectionTest.php @@ -7,8 +7,6 @@ use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitation; use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; -use Magento\Framework\Data\Collection\Db\FetchStrategyInterface; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -28,7 +26,7 @@ class CollectionTest extends \PHPUnit\Framework\TestCase /** @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $loggerMock; - /** @var FetchStrategyInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\Data\Collection\Db\FetchStrategyInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $fetchStrategyMock; /** @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ @@ -46,8 +44,8 @@ class CollectionTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Catalog\Model\ResourceModel\Helper|\PHPUnit_Framework_MockObject_MockObject */ protected $helperMock; - /** @var ResourceModelPoolInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $resourceModelPoolMock; + /** @var \Magento\Framework\Validator\UniversalFactory|\PHPUnit_Framework_MockObject_MockObject */ + protected $universalFactoryMock; /** @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $storeManagerMock; @@ -81,23 +79,29 @@ protected function setUp() $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->entityFactoryMock = $this->createMock(\Magento\Framework\Data\Collection\EntityFactory::class); $this->loggerMock = $this->createMock(\Psr\Log\LoggerInterface::class); - $this->fetchStrategyMock = $this->createMock(FetchStrategyInterface::class); + $this->fetchStrategyMock = $this->createMock( + \Magento\Framework\Data\Collection\Db\FetchStrategyInterface::class + ); $this->managerInterfaceMock = $this->createMock(\Magento\Framework\Event\ManagerInterface::class); $this->configMock = $this->createMock(\Magento\Eav\Model\Config::class); $this->resourceMock = $this->createMock(\Magento\Framework\App\ResourceConnection::class); $this->entityFactoryMock2 = $this->createMock(\Magento\Eav\Model\EntityFactory::class); $this->helperMock = $this->createMock(\Magento\Catalog\Model\ResourceModel\Helper::class); $entity = $this->createMock(\Magento\Eav\Model\Entity\AbstractEntity::class); - $select = $this->createMock(\Magento\Framework\DB\Select::class); - $connection = $this->createMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class); + $select = $this->getMockBuilder(\Magento\Framework\DB\Select::class) + ->disableOriginalConstructor() + ->getMock(); + $connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\Pdo\Mysql::class) + ->disableOriginalConstructor() + ->getMock(); $connection->expects($this->any()) ->method('select') ->willReturn($select); $entity->expects($this->any())->method('getConnection')->will($this->returnValue($connection)); $entity->expects($this->any())->method('getDefaultAttributes')->will($this->returnValue([])); - $this->resourceModelPoolMock = $this->createMock(ResourceModelPoolInterface::class); - $this->resourceModelPoolMock->expects($this->any())->method('get')->will($this->returnValue($entity)); - $this->storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); + $this->universalFactoryMock = $this->createMock(\Magento\Framework\Validator\UniversalFactory::class); + $this->universalFactoryMock->expects($this->any())->method('create')->will($this->returnValue($entity)); + $this->storeManagerMock = $this->getMockForAbstractClass(\Magento\Store\Model\StoreManagerInterface::class); $this->storeManagerMock ->expects($this->any()) ->method('getStore') @@ -114,7 +118,9 @@ function ($store) { $this->timezoneInterfaceMock = $this->createMock(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class); $this->sessionMock = $this->createMock(\Magento\Customer\Model\Session::class); $this->dateTimeMock = $this->createMock(\Magento\Framework\Stdlib\DateTime::class); - $productLimitationFactoryMock = $this->createPartialMock(ProductLimitationFactory::class, ['create']); + $productLimitationFactoryMock = $this->getMockBuilder( + ProductLimitationFactory::class + )->disableOriginalConstructor()->setMethods(['create'])->getMock(); $productLimitationFactoryMock->method('create') ->willReturn($this->createMock(ProductLimitation::class)); @@ -130,7 +136,7 @@ function ($store) { 'resource' => $this->resourceMock, 'eavEntityFactory' => $this->entityFactoryMock2, 'resourceHelper' => $this->helperMock, - 'resourceModelPool' => $this->resourceModelPoolMock, + 'universalFactory' => $this->universalFactoryMock, 'storeManager' => $this->storeManagerMock, 'catalogData' => $this->catalogHelperMock, 'catalogProductFlatState' => $this->stateMock, diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php index cd6565f32ed18..a2d81854607a0 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php @@ -154,38 +154,4 @@ public function modifyMetaLockedDataProvider() { return [[true], [false]]; } - - public function testModifyMetaWithCaching() - { - $this->arrayManagerMock->expects($this->exactly(2)) - ->method('findPath') - ->willReturn(true); - $cacheManager = $this->getMockBuilder(CacheInterface::class) - ->getMockForAbstractClass(); - $cacheManager->expects($this->once()) - ->method('load') - ->with(Categories::CATEGORY_TREE_ID . '_'); - $cacheManager->expects($this->once()) - ->method('save'); - - $modifier = $this->createModel(); - $cacheContextProperty = new \ReflectionProperty( - Categories::class, - 'cacheManager' - ); - $cacheContextProperty->setAccessible(true); - $cacheContextProperty->setValue($modifier, $cacheManager); - - $groupCode = 'test_group_code'; - $meta = [ - $groupCode => [ - 'children' => [ - 'category_ids' => [ - 'sortOrder' => 10, - ], - ], - ], - ]; - $modifier->modifyMeta($meta); - } } diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php index 681435851fbde..800ead0e4030c 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Ui\DataProvider\Product\Form\Modifier; use Magento\Catalog\Model\Locator\LocatorInterface; @@ -11,6 +13,7 @@ use Magento\Framework\App\CacheInterface; use Magento\Framework\DB\Helper as DbHelper; use Magento\Catalog\Model\Category as CategoryModel; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\UrlInterface; use Magento\Framework\Stdlib\ArrayManager; @@ -202,6 +205,7 @@ protected function createNewCategoryModal(array $meta) * * @param array $meta * @return array + * @throws LocalizedException * @since 101.0.0 */ protected function customizeCategoriesField(array $meta) @@ -306,20 +310,64 @@ protected function customizeCategoriesField(array $meta) * * @param string|null $filter * @return array + * @throws LocalizedException * @since 101.0.0 */ protected function getCategoriesTree($filter = null) { - $categoryTree = $this->getCacheManager()->load(self::CATEGORY_TREE_ID . '_' . $filter); - if ($categoryTree) { - return $this->serializer->unserialize($categoryTree); + $storeId = (int) $this->locator->getStore()->getId(); + + $cachedCategoriesTree = $this->getCacheManager() + ->load($this->getCategoriesTreeCacheId($storeId, (string) $filter)); + if (!empty($cachedCategoriesTree)) { + return $this->serializer->unserialize($cachedCategoriesTree); } - $storeId = $this->locator->getStore()->getId(); + $categoriesTree = $this->retrieveCategoriesTree( + $storeId, + $this->retrieveShownCategoriesIds($storeId, (string) $filter) + ); + + $this->getCacheManager()->save( + $this->serializer->serialize($categoriesTree), + $this->getCategoriesTreeCacheId($storeId, (string) $filter), + [ + \Magento\Catalog\Model\Category::CACHE_TAG, + \Magento\Framework\App\Cache\Type\Block::CACHE_TAG + ] + ); + + return $categoriesTree; + } + + /** + * Get cache id for categories tree. + * + * @param int $storeId + * @param string $filter + * @return string + */ + private function getCategoriesTreeCacheId(int $storeId, string $filter = '') : string + { + return self::CATEGORY_TREE_ID + . '_' . (string) $storeId + . '_' . $filter; + } + + /** + * Retrieve filtered list of categories id. + * + * @param int $storeId + * @param string $filter + * @return array + * @throws LocalizedException + */ + private function retrieveShownCategoriesIds(int $storeId, string $filter = '') : array + { /* @var $matchingNamesCollection \Magento\Catalog\Model\ResourceModel\Category\Collection */ $matchingNamesCollection = $this->categoryCollectionFactory->create(); - if ($filter !== null) { + if (!empty($filter)) { $matchingNamesCollection->addAttributeToFilter( 'name', ['like' => $this->dbHelper->addLikeEscape($filter, ['position' => 'any'])] @@ -339,6 +387,19 @@ protected function getCategoriesTree($filter = null) } } + return $shownCategoriesIds; + } + + /** + * Retrieve tree of categories with attributes. + * + * @param int $storeId + * @param array $shownCategoriesIds + * @return array|null + * @throws LocalizedException + */ + private function retrieveCategoriesTree(int $storeId, array $shownCategoriesIds) : ?array + { /* @var $collection \Magento\Catalog\Model\ResourceModel\Category\Collection */ $collection = $this->categoryCollectionFactory->create(); @@ -365,15 +426,6 @@ protected function getCategoriesTree($filter = null) $categoryById[$category->getParentId()]['optgroup'][] = &$categoryById[$category->getId()]; } - $this->getCacheManager()->save( - $this->serializer->serialize($categoryById[CategoryModel::TREE_ROOT_ID]['optgroup']), - self::CATEGORY_TREE_ID . '_' . $filter, - [ - \Magento\Catalog\Model\Category::CACHE_TAG, - \Magento\Framework\App\Cache\Type\Block::CACHE_TAG - ] - ); - return $categoryById[CategoryModel::TREE_ROOT_ID]['optgroup']; } } diff --git a/app/code/Magento/Catalog/ViewModel/Product/Checker/AddToCompareAvailability.php b/app/code/Magento/Catalog/ViewModel/Product/Checker/AddToCompareAvailability.php new file mode 100644 index 0000000000000..27829155af292 --- /dev/null +++ b/app/code/Magento/Catalog/ViewModel/Product/Checker/AddToCompareAvailability.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\ViewModel\Product\Checker; + +use Magento\Framework\View\Element\Block\ArgumentInterface; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\CatalogInventory\Api\StockConfigurationInterface; + +/** + * Check is available add to compare. + */ +class AddToCompareAvailability implements ArgumentInterface +{ + /** + * @var StockConfigurationInterface + */ + private $stockConfiguration; + + /** + * @param StockConfigurationInterface $stockConfiguration + */ + public function __construct(StockConfigurationInterface $stockConfiguration) + { + $this->stockConfiguration = $stockConfiguration; + } + + /** + * Is product available for comparison. + * + * @param ProductInterface $product + * @return bool + */ + public function isAvailableForCompare(ProductInterface $product): bool + { + return $this->isInStock($product) || $this->stockConfiguration->isShowOutOfStock(); + } + + /** + * Get is in stock status. + * + * @param ProductInterface $product + * @return bool + */ + private function isInStock(ProductInterface $product): bool + { + $quantityAndStockStatus = $product->getQuantityAndStockStatus(); + if (!$quantityAndStockStatus) { + return $product->isSalable(); + } + + return isset($quantityAndStockStatus['is_in_stock']) && $quantityAndStockStatus['is_in_stock']; + } +} diff --git a/app/code/Magento/Catalog/etc/frontend/di.xml b/app/code/Magento/Catalog/etc/frontend/di.xml index 793a2291f599c..ee9c5b29da894 100644 --- a/app/code/Magento/Catalog/etc/frontend/di.xml +++ b/app/code/Magento/Catalog/etc/frontend/di.xml @@ -120,5 +120,4 @@ <plugin name="catalog_app_action_dispatch_controller_context_plugin" type="Magento\Catalog\Plugin\Framework\App\Action\ContextPlugin" /> </type> - <preference for="Magento\Catalog\Model\Product\Type\Price" type="Magento\Catalog\Model\Product\Type\FrontSpecialPrice" /> </config> diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_block.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_block.xml index 44884897461a8..4e7396608826b 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_block.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.catalog.product.set.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">setGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\Eav\Model\ResourceModel\Entity\Attribute\Grid\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Eav\Model\ResourceModel\Entity\Attribute\Grid\Collection</argument> <argument name="default_sort" xsi:type="string">set_name</argument> <argument name="default_dir" xsi:type="string">ASC</argument> <argument name="save_parameters_in_session" xsi:type="string">1</argument> diff --git a/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml b/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml index 41a2a6142d506..13e2d998f6cdd 100644 --- a/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml +++ b/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml @@ -91,7 +91,11 @@ <container name="product.info.social" label="Product social links container" htmlTag="div" htmlClass="product-social-links"> <block class="Magento\Catalog\Block\Product\View" name="product.info.addto" as="addto" template="Magento_Catalog::product/view/addto.phtml"> <block class="Magento\Catalog\Block\Product\View\AddTo\Compare" name="view.addto.compare" after="view.addto.wishlist" - template="Magento_Catalog::product/view/addto/compare.phtml" /> + template="Magento_Catalog::product/view/addto/compare.phtml" > + <arguments> + <argument name="addToCompareViewModel" xsi:type="object">Magento\Catalog\ViewModel\Product\Checker\AddToCompareAvailability</argument> + </arguments> + </block> </block> <block class="Magento\Catalog\Block\Product\View" name="product.info.mailto" template="Magento_Catalog::product/view/mailto.phtml"/> </container> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml index f434402346087..ecc9700802d27 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml @@ -169,7 +169,7 @@ switch ($type = $block->getType()) { <?php if ($type == 'related' && $canItemsAddToCart): ?> <div class="block-actions"> <?= /* @escapeNotVerified */ __('Check items to add to the cart or') ?> - <button type="button" class="action select" role="select-all"><span><?= /* @escapeNotVerified */ __('select all') ?></span></button> + <button type="button" class="action select" role="button"><span><?= /* @escapeNotVerified */ __('select all') ?></span></button> </div> <?php endif; ?> <div class="products wrapper grid products-grid products-<?= /* @escapeNotVerified */ $type ?>"> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/addto/compare.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/addto/compare.phtml index adf0f44d0c831..194a472d81d58 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/addto/compare.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/addto/compare.phtml @@ -9,6 +9,10 @@ /** @var $block \Magento\Catalog\Block\Product\View\Addto\Compare */ ?> +<?php $viewModel = $block->getData('addToCompareViewModel'); ?> +<?php if ($viewModel->isAvailableForCompare($block->getProduct())): ?> <a href="#" data-post='<?= /* @escapeNotVerified */ $block->getPostDataParams() ?>' data-role="add-to-links" class="action tocompare"><span><?= /* @escapeNotVerified */ __('Add to Compare') ?></span></a> +<?php endif; ?> + diff --git a/app/code/Magento/Catalog/view/frontend/web/js/related-products.js b/app/code/Magento/Catalog/view/frontend/web/js/related-products.js index 0c37f9ff4f007..66df48c28bfab 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/related-products.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/related-products.js @@ -17,7 +17,7 @@ define([ relatedProductsField: '#related-products-field', // Hidden input field that stores related products. selectAllMessage: $.mage.__('select all'), unselectAllMessage: $.mage.__('unselect all'), - selectAllLink: '[role="select-all"]', + selectAllLink: '[role="button"]', elementsSelector: '.item.product' }, diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoriesIdentity.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoriesIdentity.php new file mode 100644 index 0000000000000..aba2d7b198dbd --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoriesIdentity.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver\Category; + +use Magento\Framework\GraphQl\Query\Resolver\IdentityInterface; + +/** + * Identity for multiple resolved categories + */ +class CategoriesIdentity implements IdentityInterface +{ + /** + * Get category IDs from resolved data + * + * @param array $resolvedData + * @return string[] + */ + public function getIdentities(array $resolvedData): array + { + $ids = []; + if (!empty($resolvedData)) { + foreach ($resolvedData as $category) { + $ids[] = $category['id']; + } + } + return $ids; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoryTreeIdentity.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoryTreeIdentity.php new file mode 100644 index 0000000000000..e4970f08b3eb7 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoryTreeIdentity.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver\Category; + +use Magento\Framework\GraphQl\Query\Resolver\IdentityInterface; + +/** + * Identity for resolved category + */ +class CategoryTreeIdentity implements IdentityInterface +{ + /** + * Get category ID from resolved data + * + * @param array $resolvedData + * @return string[] + */ + public function getIdentities(array $resolvedData): array + { + return empty($resolvedData['id']) ? [] : [$resolvedData['id']]; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Identity.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Identity.php new file mode 100644 index 0000000000000..198b1c112dca2 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Identity.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver\Product; + +use Magento\Framework\GraphQl\Query\Resolver\IdentityInterface; + +/** + * Identity for resolved products + */ +class Identity implements IdentityInterface +{ + /** + * Get product ids for cache tag + * + * @param array $resolvedData + * @return string[] + */ + public function getIdentities(array $resolvedData): array + { + $ids = []; + $items = $resolvedData['items'] ?? []; + foreach ($items as $item) { + $ids[] = $item['entity_id']; + } + + return $ids; + } +} diff --git a/app/code/Magento/CatalogGraphQl/composer.json b/app/code/Magento/CatalogGraphQl/composer.json index eb86ac634412e..950b496263ffd 100644 --- a/app/code/Magento/CatalogGraphQl/composer.json +++ b/app/code/Magento/CatalogGraphQl/composer.json @@ -14,6 +14,7 @@ }, "suggest": { "magento/module-graph-ql": "*", + "magento/module-graph-ql-cache": "*", "magento/module-store-graph-ql": "*" }, "license": [ diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index dc37b3ce76113..9f102a1c6a150 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -9,184 +9,11 @@ type Query { currentPage: Int = 1 @doc(description: "Specifies which page of results to return. The default value is 1."), sort: ProductSortInput @doc(description: "Specifies which attribute to sort on, and whether to return the results in ascending or descending order.") ): Products - @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Products") @doc(description: "The products query searches for products that match the criteria specified in the search and filter attributes") + @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Products") @doc(description: "The products query searches for products that match the criteria specified in the search and filter attributes") @cache(cacheTag: "cat_p", cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Identity") category ( id: Int @doc(description: "Id of the category") ): CategoryTree - @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryTree") -} - -enum CurrencyEnum @doc(description: "The list of available currency codes") { - AFN - ALL - AZN - DZD - AOA - ARS - AMD - AWG - AUD - BSD - BHD - BDT - BBD - BYR - BZD - BMD - BTN - BOB - BAM - BWP - BRL - GBP - BND - BGN - BUK - BIF - KHR - CAD - CVE - CZK - KYD - GQE - CLP - CNY - COP - KMF - CDF - CRC - HRK - CUP - DKK - DJF - DOP - XCD - EGP - SVC - ERN - EEK - ETB - EUR - FKP - FJD - GMD - GEK - GEL - GHS - GIP - GTQ - GNF - GYD - HTG - HNL - HKD - HUF - ISK - INR - IDR - IRR - IQD - ILS - JMD - JPY - JOD - KZT - KES - KWD - KGS - LAK - LVL - LBP - LSL - LRD - LYD - LTL - MOP - MKD - MGA - MWK - MYR - MVR - LSM - MRO - MUR - MXN - MDL - MNT - MAD - MZN - MMK - NAD - NPR - ANG - YTL - NZD - NIC - NGN - KPW - NOK - OMR - PKR - PAB - PGK - PYG - PEN - PHP - PLN - QAR - RHD - RON - RUB - RWF - SHP - STD - SAR - RSD - SCR - SLL - SGD - SKK - SBD - SOS - ZAR - KRW - LKR - SDG - SRD - SZL - SEK - CHF - SYP - TWD - TJS - TZS - THB - TOP - TTD - TND - TMM - USD - UGX - UAH - AED - UYU - UZS - VUV - VEB - VEF - VND - CHE - CHW - XOF - WST - YER - ZMK - ZWD - TRY - AZM - ROL - TRL - XPF + @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryTree") @doc(description: "The category query searches for categories that match the criteria specified in the search and filter attributes") @cache(cacheTag: "cat_c", cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoryTreeIdentity") } type Price @doc(description: "The Price object defines the price of a product as well as any tax-related adjustments.") { @@ -214,11 +41,6 @@ enum PriceTypeEnum @doc(description: "This enumeration the price type.") { DYNAMIC } -type Money @doc(description: "A Money object defines a monetary value, including a numeric value and a currency code.") { - value: Float @doc(description: "A number expressing a monetary value") - currency: CurrencyEnum @doc(description: "A three-letter currency code, such as USD or EUR") -} - type ProductPrices @doc(description: "The ProductPrices object contains the regular price of an item, as well as its minimum and maximum prices. Only composite products, which include bundle, configurable, and grouped products, can contain a minimum and maximum price.") { minimalPrice: Price @doc(description: "The lowest possible final price for all the options defined within a composite product. If you are specifying a price range, this would be the from value.") maximalPrice: Price @doc(description: "The highest possible final price for all the options defined within a composite product. If you are specifying a price range, this would be the to value.") @@ -275,7 +97,7 @@ interface ProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\ price: ProductPrices @doc(description: "A ProductPrices object, indicating the price of an item") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Price") gift_message_available: String @doc(description: "Indicates whether a gift message is available") manufacturer: Int @doc(description: "A number representing the product's manufacturer") - categories: [CategoryInterface] @doc(description: "The categories assigned to a product") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Categories") + categories: [CategoryInterface] @doc(description: "The categories assigned to a product") @cache(cacheTag: "cat_c", cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoriesIdentity") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Categories") canonical_url: String @doc(description: "Canonical URL") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CanonicalUrl") } @@ -396,7 +218,7 @@ interface CategoryInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model pageSize: Int = 20 @doc(description: "Specifies the maximum number of results to return at once. This attribute is optional."), currentPage: Int = 1 @doc(description: "Specifies which page of results to return. The default value is 1."), sort: ProductSortInput @doc(description: "Specifies which attribute to sort on, and whether to return the results in ascending or descending order.") - ): CategoryProducts @doc(description: "The list of products assigned to the category") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\Products") + ): CategoryProducts @doc(description: "The list of products assigned to the category") @cache(cacheTag: "cat_p", cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Identity") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\Products") breadcrumbs: [Breadcrumb] @doc(description: "Breadcrumbs, parent categories info") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\Breadcrumbs") } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 404c31296e4dd..edeb955b19c9b 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -1247,6 +1247,7 @@ protected function _prepareRowForDb(array $rowData) * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * phpcs:disable Generic.Metrics.NestingLevel */ protected function _saveLinks() { @@ -1256,7 +1257,7 @@ protected function _saveLinks() $nextLinkId = $this->_resourceHelper->getNextAutoincrement($mainTable); // pre-load 'position' attributes ID for each link type once - foreach ($this->_linkNameToId as $linkName => $linkId) { + foreach ($this->_linkNameToId as $linkId) { $select = $this->_connection->select()->from( $resource->getTable('catalog_product_link_attribute'), ['id' => 'product_link_attribute_id'] @@ -1374,6 +1375,7 @@ protected function _saveLinks() } return $this; } + // phpcs:enable /** * Save product attributes. @@ -1608,6 +1610,7 @@ public function getImagesFromRow(array $rowData) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.UnusedLocalVariable) * @throws LocalizedException + * phpcs:disable Generic.Metrics.NestingLevel */ protected function _saveProducts() { @@ -1798,7 +1801,13 @@ protected function _saveProducts() $uploadedImages[$columnImage] = $uploadedFile; } else { unset($rowData[$column]); - $this->skipRow($rowNum, ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE); + $this->addRowError( + ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE, + $rowNum, + null, + null, + ProcessingError::ERROR_LEVEL_NOT_CRITICAL + ); } } else { $uploadedFile = $uploadedImages[$columnImage]; @@ -1974,6 +1983,7 @@ protected function _saveProducts() return $this; } + // phpcs:enable /** * Prepare array with image states (visible or hidden from product page) diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/DisplayOutOfStockProductActionGroup.xml b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/DisplayOutOfStockProductActionGroup.xml index c7c9126f46803..2850b8d069201 100644 --- a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/DisplayOutOfStockProductActionGroup.xml +++ b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/DisplayOutOfStockProductActionGroup.xml @@ -21,6 +21,7 @@ <actionGroup name="noDisplayOutOfStockProduct"> <amOnPage url="{{InventoryConfigurationPage.url}}" stepKey="navigateToInventoryConfigurationPage"/> <waitForPageLoad stepKey="waitForConfigPageToLoad"/> + <conditionalClick stepKey="expandProductStockOptions" selector="{{InventoryConfigSection.ProductStockOptionsTab}}" dependentSelector="{{InventoryConfigSection.CheckIfProductStockOptionsTabExpanded}}" visible="true" /> <uncheckOption selector="{{InventoryConfigSection.DisplayOutOfStockSystemValue}}" stepKey="uncheckUseSystemValue"/> <waitForElementVisible selector="{{InventoryConfigSection.DisplayOutOfStockDropdown}}" stepKey="waitForSwitcherDropdown" /> <selectOption selector="{{InventoryConfigSection.DisplayOutOfStockDropdown}}" userInput="No" stepKey="switchToNo" /> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminOpenNewCatalogPriceRuleFormPageActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminOpenNewCatalogPriceRuleFormPageActionGroup.xml new file mode 100644 index 0000000000000..072e8b24b0336 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminOpenNewCatalogPriceRuleFormPageActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenNewCatalogPriceRuleFormPageActionGroup"> + <amOnPage url="{{CatalogRuleNewPage.url}}" stepKey="openNewCatalogPriceRulePage" /> + <waitForPageLoad stepKey="waitForPageLoad" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCustomerGroupNotOnCatalogPriceRuleFormActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCustomerGroupNotOnCatalogPriceRuleFormActionGroup.xml new file mode 100644 index 0000000000000..93a2a8a610951 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCustomerGroupNotOnCatalogPriceRuleFormActionGroup.xml @@ -0,0 +1,20 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCustomerGroupNotOnCatalogPriceRuleFormActionGroup"> + <arguments> + <argument name="customerGroup" type="entity" /> + </arguments> + <grabMultiple selector="{{AdminNewCatalogPriceRule.customerGroupsOptions}}" stepKey="customerGroups" /> + <assertNotContains stepKey="assertCustomerGroupNotInOptions"> + <actualResult type="variable">customerGroups</actualResult> + <expectedResult type="string">{{customerGroup.code}}</expectedResult> + </assertNotContains> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/CatalogRule/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..eb9cac1401c36 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,21 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuMarketing"> + <data key="pageTitle">Marketing</data> + <data key="title">Marketing</data> + <data key="dataUiId">magento-backend-marketing</data> + </entity> + <entity name="AdminMenuMarketingPromotionsCatalogPriceRule"> + <data key="pageTitle">Catalog Price Rule</data> + <data key="title">Catalog Price Rule</data> + <data key="dataUiId">magento-catalogrule-promo-catalog</data> + </entity> +</entities> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Page/CatalogRuleNewPage.xml b/app/code/Magento/CatalogRule/Test/Mftf/Page/CatalogRuleNewPage.xml new file mode 100644 index 0000000000000..ad3e40b37c5b0 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Page/CatalogRuleNewPage.xml @@ -0,0 +1,14 @@ +<?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="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="CatalogRuleNewPage" url="catalog_rule/promo_catalog/new/" module="Magento_CatalogRule" area="admin"> + <section name="AdminNewCatalogPriceRule"/> + </page> +</pages> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminMarketingCatalogPriceRuleNavigateMenuTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminMarketingCatalogPriceRuleNavigateMenuTest.xml new file mode 100644 index 0000000000000..0fe35419aaf3e --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminMarketingCatalogPriceRuleNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminMarketingCatalogPriceRuleNavigateMenuTest"> + <annotations> + <features value="CatalogRule"/> + <stories value="Menu Navigation"/> + <title value="Admin marketing catalog price rule navigate menu test"/> + <description value="Admin should be able to navigate to Marketing > Catalog Price Rule"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14134"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToCatalogPriceRulePage"> + <argument name="menuUiId" value="{{AdminMenuMarketing.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuMarketingPromotionsCatalogPriceRule.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuMarketingPromotionsCatalogPriceRule.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/DeleteCustomerGroupTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/DeleteCustomerGroupTest.xml new file mode 100644 index 0000000000000..75223fcfc4c4b --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/DeleteCustomerGroupTest.xml @@ -0,0 +1,17 @@ +<?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="DeleteCustomerGroupTest"> + <actionGroup ref="AdminOpenNewCatalogPriceRuleFormPageActionGroup" stepKey="openNewCatalogPriceRuleForm" /> + <actionGroup ref="AssertCustomerGroupNotOnCatalogPriceRuleFormActionGroup" stepKey="assertCustomerGroupNotOnCatalogPriceRuleForm"> + <argument name="customerGroup" value="$$customerGroup$$" /> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CatalogRule/etc/mview.xml b/app/code/Magento/CatalogRule/etc/mview.xml index 35efe33461afc..9e5a1c866a842 100644 --- a/app/code/Magento/CatalogRule/etc/mview.xml +++ b/app/code/Magento/CatalogRule/etc/mview.xml @@ -16,7 +16,6 @@ <table name="catalog_product_entity" entity_column="entity_id" /> <table name="catalog_product_entity_datetime" entity_column="entity_id" /> <table name="catalog_product_entity_decimal" entity_column="entity_id" /> - <table name="catalog_product_entity_gallery" entity_column="entity_id" /> <table name="catalog_product_entity_int" entity_column="entity_id" /> <table name="catalog_product_entity_text" entity_column="entity_id" /> <table name="catalog_product_entity_tier_price" entity_column="entity_id" /> diff --git a/app/code/Magento/CatalogRule/view/adminhtml/layout/catalog_rule_promo_catalog_block.xml b/app/code/Magento/CatalogRule/view/adminhtml/layout/catalog_rule_promo_catalog_block.xml index 99d64ed7a635f..f38f6e0fcd850 100644 --- a/app/code/Magento/CatalogRule/view/adminhtml/layout/catalog_rule_promo_catalog_block.xml +++ b/app/code/Magento/CatalogRule/view/adminhtml/layout/catalog_rule_promo_catalog_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="promo.catalog.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">promo_catalog_grid</argument> - <argument name="dataSource" xsi:type="object">Magento\CatalogRule\Model\ResourceModel\Grid\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\CatalogRule\Model\ResourceModel\Grid\Collection</argument> <argument name="default_sort" xsi:type="string">name</argument> <argument name="default_dir" xsi:type="string">ASC</argument> <argument name="save_parameters_in_session" xsi:type="string">1</argument> diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php index 2ffa63098cdee..c758e773f43c1 100644 --- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php @@ -8,7 +8,9 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\CatalogSearch\Model\Search\FilterMapper\VisibilityFilter; use Magento\CatalogSearch\Model\Search\TableMapper; +use Magento\Customer\Model\Session; use Magento\Eav\Model\Config; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ObjectManager; @@ -20,10 +22,11 @@ use Magento\Framework\Search\Adapter\Mysql\Filter\PreprocessorInterface; use Magento\Framework\Search\Request\FilterInterface; use Magento\Store\Model\Store; -use Magento\Customer\Model\Session; -use Magento\CatalogSearch\Model\Search\FilterMapper\VisibilityFilter; /** + * ElasticSearch search filter pre-processor. + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @deprecated * @see \Magento\ElasticSearch @@ -128,7 +131,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function process(FilterInterface $filter, $isNegation, $query) { @@ -136,10 +139,13 @@ public function process(FilterInterface $filter, $isNegation, $query) } /** + * Process query with field. + * * @param FilterInterface $filter * @param bool $isNegation * @param string $query * @return string + * @throws \Magento\Framework\Exception\LocalizedException */ private function processQueryWithField(FilterInterface $filter, $isNegation, $query) { @@ -170,7 +176,7 @@ private function processQueryWithField(FilterInterface $filter, $isNegation, $qu } elseif ($filter->getField() === VisibilityFilter::VISIBILITY_FILTER_FIELD) { return ''; } elseif ($filter->getType() === FilterInterface::TYPE_TERM && - in_array($attribute->getFrontendInput(), ['select', 'multiselect'], true) + in_array($attribute->getFrontendInput(), ['select', 'multiselect', 'boolean'], true) ) { $resultQuery = $this->processTermSelect($filter, $isNegation); } elseif ($filter->getType() === FilterInterface::TYPE_RANGE && @@ -204,19 +210,23 @@ private function processQueryWithField(FilterInterface $filter, $isNegation, $qu ->where('main_table.store_id = ?', Store::DEFAULT_STORE_ID) ->having($query); - $resultQuery = 'search_index.entity_id IN ( - select entity_id from ' . $this->conditionManager->wrapBrackets($select) . ' as filter - )'; + $resultQuery = 'search_index.entity_id IN (' + . 'select entity_id from ' + . $this->conditionManager->wrapBrackets($select) + . ' as filter)'; } return $resultQuery; } /** + * Process range numeric. + * * @param FilterInterface $filter * @param string $query * @param Attribute $attribute * @return string + * @throws \Exception */ private function processRangeNumeric(FilterInterface $filter, $query, $attribute) { @@ -238,14 +248,17 @@ private function processRangeNumeric(FilterInterface $filter, $query, $attribute ->where('main_table.store_id = ?', $currentStoreId) ->having($query); - $resultQuery = 'search_index.entity_id IN ( - select entity_id from ' . $this->conditionManager->wrapBrackets($select) . ' as filter - )'; + $resultQuery = 'search_index.entity_id IN (' + . 'select entity_id from ' + . $this->conditionManager->wrapBrackets($select) + . ' as filter)'; return $resultQuery; } /** + * Process term select. + * * @param FilterInterface $filter * @param bool $isNegation * @return string diff --git a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php index e61a886a41d6f..e9fb1070fedd5 100644 --- a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php +++ b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php @@ -111,12 +111,9 @@ protected function _getItemsData() $from = ''; } if ($to == '*') { - $to = ''; + $to = null; } - $label = $this->renderRangeLabel( - empty($from) ? 0 : $from, - empty($to) ? 0 : $to - ); + $label = $this->renderRangeLabel(empty($from) ? 0 : $from, $to); $value = $from . '-' . $to; $data[] = [ @@ -141,7 +138,7 @@ protected function _getItemsData() protected function renderRangeLabel($fromPrice, $toPrice) { $formattedFromPrice = $this->priceCurrency->format($fromPrice); - if ($toPrice === '') { + if ($toPrice === null) { return __('%1 and above', $formattedFromPrice); } else { if ($fromPrice != $toPrice) { diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php index e3f61af771f8c..7791dc761ae39 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php @@ -6,8 +6,6 @@ namespace Magento\CatalogSearch\Model\ResourceModel\Advanced; -use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; -use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; use Magento\Catalog\Model\Product; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface; @@ -21,8 +19,6 @@ use Magento\Framework\Api\Search\SearchResultFactory; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Indexer\DimensionFactory; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; use Magento\Framework\Search\Request\EmptyRequestDataException; use Magento\Framework\Search\Request\NonExistingRequestNameException; use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; @@ -139,10 +135,6 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection * @param SearchResultFactory|null $searchResultFactory * @param ProductLimitationFactory|null $productLimitationFactory * @param MetadataPool|null $metadataPool - * @param TableMaintainer|null $tableMaintainer - * @param PriceTableResolver|null $priceTableResolver - * @param DimensionFactory|null $dimensionFactory - * @param ResourceModelPoolInterface|null $resourceModelPool * @param string $searchRequestName * @param SearchCriteriaResolverFactory|null $searchCriteriaResolverFactory * @param SearchResultApplierFactory|null $searchResultApplierFactory @@ -177,10 +169,6 @@ public function __construct( SearchResultFactory $searchResultFactory = null, ProductLimitationFactory $productLimitationFactory = null, MetadataPool $metadataPool = null, - TableMaintainer $tableMaintainer = null, - PriceTableResolver $priceTableResolver = null, - DimensionFactory $dimensionFactory = null, - ResourceModelPoolInterface $resourceModelPool = null, $searchRequestName = 'advanced_search_container', SearchCriteriaResolverFactory $searchCriteriaResolverFactory = null, SearchResultApplierFactory $searchResultApplierFactory = null, @@ -225,11 +213,7 @@ public function __construct( $groupManagement, $connection, $productLimitationFactory, - $metadataPool, - $tableMaintainer, - $priceTableResolver, - $dimensionFactory, - $resourceModelPool + $metadataPool ); } diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php index 2c36b150fed07..59f6cd1c6e7eb 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php @@ -6,8 +6,6 @@ namespace Magento\CatalogSearch\Model\ResourceModel\Fulltext; -use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; -use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverInterface; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverFactory; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface; @@ -21,8 +19,6 @@ use Magento\CatalogSearch\Model\Search\RequestGenerator; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Exception\StateException; -use Magento\Framework\Indexer\DimensionFactory; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; use Magento\Framework\Search\Response\QueryResponse; use Magento\Framework\Search\Request\EmptyRequestDataException; use Magento\Framework\Search\Request\NonExistingRequestNameException; @@ -41,6 +37,7 @@ * @since 100.0.2 * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection { @@ -167,10 +164,6 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection * @param SearchResultFactory|null $searchResultFactory * @param ProductLimitationFactory|null $productLimitationFactory * @param MetadataPool|null $metadataPool - * @param TableMaintainer|null $tableMaintainer - * @param PriceTableResolver|null $priceTableResolver - * @param DimensionFactory|null $dimensionFactory - * @param ResourceModelPoolInterface|null $resourceModelPool * @param \Magento\Search\Api\SearchInterface|null $search * @param \Magento\Framework\Api\Search\SearchCriteriaBuilder|null $searchCriteriaBuilder * @param \Magento\Framework\Api\FilterBuilder|null $filterBuilder @@ -210,10 +203,6 @@ public function __construct( SearchResultFactory $searchResultFactory = null, ProductLimitationFactory $productLimitationFactory = null, MetadataPool $metadataPool = null, - TableMaintainer $tableMaintainer = null, - PriceTableResolver $priceTableResolver = null, - DimensionFactory $dimensionFactory = null, - ResourceModelPoolInterface $resourceModelPool = null, \Magento\Search\Api\SearchInterface $search = null, \Magento\Framework\Api\Search\SearchCriteriaBuilder $searchCriteriaBuilder = null, \Magento\Framework\Api\FilterBuilder $filterBuilder = null, @@ -249,11 +238,7 @@ public function __construct( $groupManagement, $connection, $productLimitationFactory, - $metadataPool, - $tableMaintainer, - $priceTableResolver, - $dimensionFactory, - $resourceModelPool + $metadataPool ); $this->requestBuilder = $requestBuilder; $this->searchEngine = $searchEngine; diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Search/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Search/Collection.php index fd948616c005b..e625ccbe51fe3 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Search/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Search/Collection.php @@ -6,17 +6,11 @@ namespace Magento\CatalogSearch\Model\ResourceModel\Search; -use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; -use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; -use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; -use Magento\Framework\EntityManager\MetadataPool; -use Magento\Framework\Indexer\DimensionFactory; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; - /** * Search collection * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @api * @since 100.0.2 */ @@ -67,12 +61,6 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection * @param \Magento\Customer\Api\GroupManagementInterface $groupManagement * @param \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory $attributeCollectionFactory * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection - * @param ProductLimitationFactory|null $productLimitationFactory - * @param MetadataPool|null $metadataPool - * @param TableMaintainer|null $tableMaintainer - * @param PriceTableResolver|null $priceTableResolver - * @param DimensionFactory|null $dimensionFactory - * @param ResourceModelPoolInterface|null $resourceModelPool * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -96,13 +84,7 @@ public function __construct( \Magento\Framework\Stdlib\DateTime $dateTime, \Magento\Customer\Api\GroupManagementInterface $groupManagement, \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory $attributeCollectionFactory, - \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, - ProductLimitationFactory $productLimitationFactory = null, - MetadataPool $metadataPool = null, - TableMaintainer $tableMaintainer = null, - PriceTableResolver $priceTableResolver = null, - DimensionFactory $dimensionFactory = null, - ResourceModelPoolInterface $resourceModelPool = null + \Magento\Framework\DB\Adapter\AdapterInterface $connection = null ) { $this->_attributeCollectionFactory = $attributeCollectionFactory; parent::__construct( @@ -125,13 +107,7 @@ public function __construct( $customerSession, $dateTime, $groupManagement, - $connection, - $productLimitationFactory, - $metadataPool, - $tableMaintainer, - $priceTableResolver, - $dimensionFactory, - $resourceModelPool + $connection ); } @@ -293,6 +269,7 @@ protected function _getSearchEntityIdsSql($query, $searchOnlyInCurrentStore = tr $sql = $this->_getSearchInOptionSql($query); if ($sql) { + // phpcs:ignore Magento2.SQL.RawQuery $selects[] = "SELECT * FROM ({$sql}) AS inoptionsql"; // inherent unions may be inside } diff --git a/app/code/Magento/CatalogSearch/Model/Search/CustomAttributeFilterCheck.php b/app/code/Magento/CatalogSearch/Model/Search/CustomAttributeFilterCheck.php index bcd4080b30b14..657c8540d7c68 100644 --- a/app/code/Magento/CatalogSearch/Model/Search/CustomAttributeFilterCheck.php +++ b/app/code/Magento/CatalogSearch/Model/Search/CustomAttributeFilterCheck.php @@ -44,7 +44,7 @@ public function isCustom(FilterInterface $filter) return $attribute && $filter->getType() === FilterInterface::TYPE_TERM - && in_array($attribute->getFrontendInput(), ['select', 'multiselect'], true); + && in_array($attribute->getFrontendInput(), ['select', 'multiselect', 'boolean'], true); } /** diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml index 4b52b2c669edf..067d76821d687 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml @@ -20,6 +20,57 @@ <see userInput="Search results for: '{{phrase}}'" selector="{{StorefrontCatalogSearchMainSection.SearchTitle}}" stepKey="assertQuickSearchName"/> </actionGroup> + <!-- Quick search the phrase and check if the result page contains correct information, usable with type="string" --> + <actionGroup name="StorefrontCheckQuickSearchStringActionGroup"> + <arguments> + <argument name="phrase" type="string"/> + </arguments> + <fillField stepKey="fillInput" selector="{{StorefrontQuickSearchResultsSection.searchTextBox}}" userInput="{{phrase}}"/> + <submitForm selector="{{StorefrontQuickSearchResultsSection.searchTextBox}}" parameterArray="[]" stepKey="submitQuickSearch" /> + <seeInCurrentUrl url="{{StorefrontCatalogSearchPage.url}}" stepKey="checkUrl"/> + <dontSeeInCurrentUrl url="form_key=" stepKey="checkUrlFormKey"/> + <seeInTitle userInput="Search results for: '{{phrase}}'" stepKey="assertQuickSearchTitle"/> + <see userInput="Search results for: '{{phrase}}'" selector="{{StorefrontCatalogSearchMainSection.SearchTitle}}" stepKey="assertQuickSearchName"/> + </actionGroup> + + <!-- Opens product from QuickSearch and performs assertions--> + <actionGroup name="StorefrontOpenProductFromQuickSearch"> + <arguments> + <argument name="productName" type="string"/> + <argument name="productUrlKey" type="string"/> + </arguments> + <click stepKey="openProduct" selector="{{StorefrontQuickSearchResultsSection.productByName(productName)}}"/> + <waitForPageLoad stepKey="waitForProductLoad"/> + <seeInCurrentUrl url="{{productUrlKey}}" stepKey="checkUrl"/> + <see stepKey="checkName" selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{productName}}"/> + </actionGroup> + + <!-- Adds product from Quicksearch page and perform assertions--> + <actionGroup name="StorefrontAddToCartFromQuickSearch"> + <arguments> + <argument name="productName" type="string"/> + </arguments> + <moveMouseOver stepKey="hoverOverProduct" selector="{{StorefrontQuickSearchResultsSection.productByIndex('1')}}"/> + <click selector="{{StorefrontQuickSearchResultsSection.productByName(productName)}} {{StorefrontQuickSearchResultsSection.addToCartBtn}}" stepKey="addToCart"/> + <waitForElementVisible selector="{{StorefrontQuickSearchResultsSection.messageSection}}" time="30" stepKey="waitForProductAdded"/> + <see selector="{{StorefrontQuickSearchResultsSection.messageSection}}" userInput="You added {{productName}} to your shopping cart." stepKey="seeAddedToCartMessage"/> + </actionGroup> + + <actionGroup name="StorefrontQuickSearchCheckProductNameInGrid"> + <arguments> + <argument name="productName" type="string"/> + <argument name="index" type="string"/> + </arguments> + <see selector="{{StorefrontQuickSearchResultsSection.productByIndex(index)}}" userInput="{{productName}}" stepKey="seeProductName"/> + </actionGroup> + + <actionGroup name="StorefrontQuickSearchCheckProductNameNotInGrid"> + <arguments> + <argument name="productName" type="string"/> + </arguments> + <dontSee selector="{{StorefrontQuickSearchResultsSection.allResults}}" userInput="{{productName}}" stepKey="dontSeeProductName"/> + </actionGroup> + <!-- Open advanced search page --> <actionGroup name="StorefrontOpenAdvancedSearchActionGroup"> <click selector="{{StorefrontFooterSection.AdvancedSearch}}" stepKey="clickAdvancedSearchLink" /> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..df1c3db6e5661 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,21 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuMarketingSEOAndSearchSearchTerms"> + <data key="pageTitle">Search Terms</data> + <data key="title">Search Terms</data> + <data key="dataUiId">magento-search-search-terms</data> + </entity> + <entity name="AdminMenuReportsMarketingSearchTerms"> + <data key="pageTitle">Search Terms Report</data> + <data key="title">Search Terms</data> + <data key="dataUiId">magento-search-report-search-term</data> + </entity> +</entities> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdminMarketingSearchTermsNavigateMenuTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdminMarketingSearchTermsNavigateMenuTest.xml new file mode 100644 index 0000000000000..bc255020d98b3 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdminMarketingSearchTermsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminMarketingSearchTermsNavigateMenuTest"> + <annotations> + <features value="CatalogSearch"/> + <stories value="Menu Navigation"/> + <title value="Admin marketing search terms navigate menu test"/> + <description value="Admin should be able to navigate to Marketing > Search Terms"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14135"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToMarketingSearchTermsPage"> + <argument name="menuUiId" value="{{AdminMenuMarketing.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuMarketingSEOAndSearchSearchTerms.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuMarketingSEOAndSearchSearchTerms.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdminReportsSearchTermsNavigateMenuTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdminReportsSearchTermsNavigateMenuTest.xml new file mode 100644 index 0000000000000..85cf0e3ba90ed --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdminReportsSearchTermsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsSearchTermsNavigateMenuTest"> + <annotations> + <features value="CatalogSearch"/> + <stories value="Menu Navigation"/> + <title value="Admin reports search terms navigate menu test"/> + <description value="Admin should be able to navigate to Reports > Search Terms"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14136"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportSearchTermsPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsMarketingSearchTerms.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsMarketingSearchTerms.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest.xml new file mode 100644 index 0000000000000..19db201e91f40 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest.xml @@ -0,0 +1,629 @@ +<?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="QuickSearchProductBySku"> + <annotations> + <stories value="Search Product on Storefront"/> + <title value="User should be able to use Quick Search to find products"/> + <description value="Use Quick Search to find a product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-14783"/> + <group value="CatalogSearch"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData stepKey="deleteProduct" createDataKey="createSimpleProduct"/> + <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> + </after> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToFrontPage"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="searchStorefront"> + <argument name="phrase" value="$createSimpleProduct.sku$"/> + </actionGroup> + <actionGroup ref="StorefrontOpenProductFromQuickSearch" stepKey="openAndCheckProduct"> + <argument name="productName" value="$createSimpleProduct.name$"/> + <argument name="productUrlKey" value="$createSimpleProduct.custom_attributes[url_key]$"/> + </actionGroup> + </test> + <test name="QuickSearchProductByName" extends="QuickSearchProductBySku"> + <annotations> + <stories value="Search Product on Storefront"/> + <title value="User should be able to use Quick Search to find products via Name"/> + <description value="Use Quick Search to find a product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-14791"/> + <group value="CatalogSearch"/> + <group value="mtf_migrated"/> + </annotations> + <!-- Overwrite search to use name --> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="searchStorefront"> + <argument name="phrase" value="$createSimpleProduct.name$"/> + </actionGroup> + </test> + <test name="QuickSearchProductByNameWithSpecialChars" extends="QuickSearchProductBySku"> + <annotations> + <stories value="Search Product on Storefront"/> + <title value="Quick Search can find products with names that contain special characters"/> + <description value="Use Quick Search to find a product by name"/> + <severity value="MAJOR"/> + <testCaseId value="MC-14792"/> + <group value="CatalogSearch"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData entity="productWithSpecialCharacters" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <!-- Overwrite search to use name --> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="searchStorefront"> + <argument name="phrase" value="$createSimpleProduct.name$"/> + </actionGroup> + </test> + <test name="QuickSearchEmptyResults"> + <annotations> + <stories value="Search Product on Storefront"/> + <title value="User should not get search results on query that doesn't return anything"/> + <description value="Use invalid query to return no products"/> + <severity value="MAJOR"/> + <testCaseId value="MC-14793"/> + <group value="CatalogSearch"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData stepKey="deleteProduct" createDataKey="createSimpleProduct"/> + <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> + </after> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToFrontPage"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="searchStorefront"> + <argument name="phrase" value="ThisShouldn'tReturnAnything"/> + </actionGroup> + <actionGroup ref="StorefrontCheckSearchIsEmpty" stepKey="checkEmpty"/> + </test> + <test name="QuickSearchWithTwoCharsEmptyResults" extends="QuickSearchEmptyResults"> + <annotations> + <stories value="Search Product on Storefront"/> + <title value="User should not get search results on query that only contains two characters"/> + <description value="Use of 2 character query to return no products"/> + <severity value="MAJOR"/> + <testCaseId value="MC-14794"/> + <group value="CatalogSearch"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MC-15827"/> + </skip> + </annotations> + <executeJS function="var s = '$createSimpleProduct.name$'; var ret=s.substring(0,2); return ret;" stepKey="getFirstTwoLetters" before="searchStorefront"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="searchStorefront"> + <argument name="phrase" value="{$getFirstTwoLetters}"/> + </actionGroup> + </test> + <test name="QuickSearchProductByNameWithThreeLetters" extends="QuickSearchProductBySku"> + <annotations> + <stories value="Search Product on Storefront"/> + <title value="User should be able to use Quick Search to find products by their first three letters"/> + <description value="Use Quick Search to find a product using only first three letters"/> + <severity value="MAJOR"/> + <testCaseId value="MC-15034"/> + <group value="CatalogSearch"/> + <group value="mtf_migrated"/> + </annotations> + <executeJS function="var s = '$createSimpleProduct.name$'; var ret=s.substring(0,3); return ret;" stepKey="getFirstThreeLetters" before="searchStorefront"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="searchStorefront"> + <argument name="phrase" value="{$getFirstThreeLetters}"/> + </actionGroup> + </test> + <test name="QuickSearchProductBy128CharQuery" extends="QuickSearchProductBySku"> + <annotations> + <stories value="Search Product on Storefront"/> + <title value="User should be able to use Quick Search product with long names, using first 128 letters"/> + <description value="Use Quick Search to find a product with name of 130 length with query of only 128"/> + <severity value="MAJOR"/> + <testCaseId value="MC-14795"/> + <group value="CatalogSearch"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData entity="productWith130CharName" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <executeJS function="var s = '$createSimpleProduct.name$'; var ret=s.substring(0,128); return ret;" stepKey="get128Letters" before="searchStorefront"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="searchStorefront"> + <argument name="phrase" value="{$get128Letters}"/> + </actionGroup> + </test> + + <test name="QuickSearchTwoProductsWithSameWeight"> + <annotations> + <stories value="Search Product on Storefront"/> + <title value="Quick Search should sort products with the same weight appropriately"/> + <description value="Use Quick Search to find a two products with the same weight"/> + <severity value="MAJOR"/> + <testCaseId value="MC-14796"/> + <group value="CatalogSearch"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="productAlphabeticalA" stepKey="product1"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="productAlphabeticalB" stepKey="product2"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> + + + <!-- Create and Assign Attribute to product1--> + <actionGroup ref="goToProductPageViaID" stepKey="goToProduct1"> + <argument name="productId" value="$product1.id$"/> + </actionGroup> + <actionGroup ref="AdminCreateAttributeWithSearchWeight" stepKey="createProduct1Attribute"> + <argument name="attributeType" value="Text Field"/> + <argument name="attributeName" value="$product1.name$"/> + <argument name="attributeSetName" value="$product1.name$"/> + <argument name="weight" value="1"/> + <argument name="defaultValue" value="{{_defaultProduct.name}}"/> + </actionGroup> + <actionGroup ref="AdminProductPageSelectAttributeSet" stepKey="selectAttributeSet1"> + <argument name="attributeSetName" value="$product1.name$"/> + </actionGroup> + <!--fill in default--> + <actionGroup ref="saveProductForm" stepKey="saveProduct1a"/> + <actionGroup ref="AdminProductPageFillTextAttributeValueByName" stepKey="fillDefault1"> + <argument name="attributeName" value="$product1.name$"/> + <argument name="value" value="{{_defaultProduct.name}}"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProduct1b"/> + <!-- Create and Assign Attribute to product2--> + <actionGroup ref="goToProductPageViaID" stepKey="goToProduct2"> + <argument name="productId" value="$product2.id$"/> + </actionGroup> + <actionGroup ref="AdminCreateAttributeWithSearchWeight" stepKey="createProduct2Attribute"> + <argument name="attributeType" value="Text Field"/> + <argument name="attributeName" value="$product2.name$"/> + <argument name="attributeSetName" value="$product2.name$"/> + <argument name="weight" value="1"/> + <argument name="defaultValue" value="{{_defaultProduct.name}}"/> + </actionGroup> + <actionGroup ref="AdminProductPageSelectAttributeSet" stepKey="selectAttributeSet2"> + <argument name="attributeSetName" value="$product2.name$"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProduct2a"/> + <!--fill in default--> + <actionGroup ref="AdminProductPageFillTextAttributeValueByName" stepKey="fillDefault2"> + <argument name="attributeName" value="$product2.name$"/> + <argument name="value" value="{{_defaultProduct.name}}"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProduct2b"/> + </before> + <after> + <deleteData stepKey="deleteProduct1" createDataKey="product1"/> + <deleteData stepKey="deleteProduct2" createDataKey="product2"/> + <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> + </after> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToFrontPage"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="searchStorefront"> + <argument name="phrase" value="{{_defaultProduct.name}}"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchCheckProductNameInGrid" stepKey="assertProduct1Position"> + <argument name="productName" value="$product1.name$"/> + <argument name="index" value="2"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchCheckProductNameInGrid" stepKey="assertProduct2Position"> + <argument name="productName" value="$product2.name$"/> + <argument name="index" value="1"/> + </actionGroup> + </test> + <test name="QuickSearchTwoProductsWithDifferentWeight" extends="QuickSearchTwoProductsWithSameWeight"> + <annotations> + <stories value="Search Product on Storefront"/> + <title value="Quick Search should sort products with the different weight appropriately"/> + <description value="Use Quick Search to find a two products with the different weight"/> + <severity value="MAJOR"/> + <testCaseId value="MC-14797"/> + <group value="CatalogSearch"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="AdminCreateAttributeWithSearchWeight" stepKey="createProduct1Attribute"> + <argument name="attributeType" value="Text Field"/> + <argument name="attributeName" value="$product1.name$"/> + <argument name="attributeSetName" value="$product1.name$"/> + <argument name="weight" value="5"/> + <argument name="defaultValue" value="{{_defaultProduct.name}}"/> + </actionGroup> + </before> + <actionGroup ref="StorefrontQuickSearchCheckProductNameInGrid" stepKey="assertProduct1Position"> + <argument name="productName" value="$product1.name$"/> + <argument name="index" value="1"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchCheckProductNameInGrid" stepKey="assertProduct2Position"> + <argument name="productName" value="$product2.name$"/> + <argument name="index" value="2"/> + </actionGroup> + </test> + + <test name="QuickSearchAndAddToCart"> + <annotations> + <stories value="Search Product on Storefront"/> + <title value="User should be able to use Quick Search to find a simple product and add it to cart"/> + <description value="Use Quick Search to find simple Product and Add to Cart"/> + <severity value="MAJOR"/> + <testCaseId value="MC-14784"/> + <group value="CatalogSearch"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData stepKey="deleteProduct" createDataKey="createSimpleProduct"/> + <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> + </after> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToFrontPage"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="searchStorefront"> + <argument name="phrase" value="$createSimpleProduct.name$"/> + </actionGroup> + <actionGroup ref="StorefrontAddToCartFromQuickSearch" stepKey="addProductToCart"> + <argument name="productName" value="$createSimpleProduct.name$"/> + </actionGroup> + </test> + <test name="QuickSearchAndAddToCartVirtual"> + <annotations> + <stories value="Search Product on Storefront"/> + <title value="User should be able to use Quick Search to find a virtual product and add it to cart"/> + <description value="Use Quick Search to find virtual Product and Add to Cart"/> + <severity value="MAJOR"/> + <testCaseId value="MC-14785"/> + <group value="CatalogSearch"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="VirtualProduct" stepKey="createVirtualProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData stepKey="deleteProduct" createDataKey="createVirtualProduct"/> + <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> + </after> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToFrontPage"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="searchStorefront"> + <argument name="phrase" value="$createVirtualProduct.name$"/> + </actionGroup> + <actionGroup ref="StorefrontAddToCartFromQuickSearch" stepKey="addProductToCart"> + <argument name="productName" value="$createVirtualProduct.name$"/> + </actionGroup> + </test> + <test name="QuickSearchAndAddToCartConfigurable"> + <annotations> + <stories value="Search Product on Storefront"/> + <title value="User should be able to use Quick Search to find a configurable product and add it to cart"/> + <description value="Use Quick Search to find configurable Product and Add to Cart"/> + <severity value="MAJOR"/> + <testCaseId value="MC-14786"/> + <group value="CatalogSearch"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> + <actionGroup ref="createConfigurableProduct" stepKey="createProduct"> + <argument name="product" value="_defaultProduct"/> + <argument name="category" value="$$createCategory$$"/> + </actionGroup> + </before> + <after> + <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> + <actionGroup ref="deleteProductBySku" stepKey="deleteProduct"> + <argument name="sku" value="{{_defaultProduct.sku}}"/> + </actionGroup> + </after> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToFrontPage"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="searchStorefront"> + <argument name="phrase" value="{{_defaultProduct.name}}"/> + </actionGroup> + <actionGroup ref="StorefrontOpenProductFromQuickSearch" stepKey="openAndCheckProduct"> + <argument name="productName" value="{{_defaultProduct.name}}"/> + <argument name="productUrlKey" value="{{_defaultProduct.urlKey}}"/> + </actionGroup> + <actionGroup ref="SelectSingleAttributeAndAddToCart" stepKey="addProductToCart"> + <argument name="productName" value="{{_defaultProduct.name}}"/> + <argument name="attributeCode" value="{{colorProductAttribute.default_label}}"/> + <argument name="optionName" value="{{colorProductAttribute1.name}}"/> + </actionGroup> + </test> + <test name="QuickSearchAndAddToCartDownloadable"> + <annotations> + <stories value="Search Product on Storefront"/> + <title value="User should be able to use Quick Search to find a downloadable product and add it to cart"/> + <description value="Use Quick Search to find downloadable Product and Add to Cart"/> + <severity value="MAJOR"/> + <testCaseId value="MC-14787"/> + <group value="CatalogSearch"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="DownloadableProductWithOneLink" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="downloadableLink1" stepKey="addDownloadableLink1"> + <requiredEntity createDataKey="createProduct"/> + </createData> + </before> + <after> + <deleteData stepKey="deleteProduct" createDataKey="createProduct"/> + <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> + </after> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToFrontPage"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="searchStorefront"> + <argument name="phrase" value="$createProduct.name$"/> + </actionGroup> + <actionGroup ref="StorefrontAddToCartFromQuickSearch" stepKey="addProductToCart"> + <argument name="productName" value="$createProduct.name$"/> + </actionGroup> + </test> + <test name="QuickSearchAndAddToCartGrouped"> + <annotations> + <stories value="Search Product on Storefront"/> + <title value="User should be able to use Quick Search to find a grouped product and add it to cart"/> + <description value="Use Quick Search to find grouped Product and Add to Cart"/> + <severity value="MAJOR"/> + <testCaseId value="MC-14788"/> + <group value="CatalogSearch"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1"/> + <createData entity="ApiGroupedProduct" stepKey="createProduct"/> + <createData entity="OneSimpleProductLink" stepKey="addProductOne"> + <requiredEntity createDataKey="createProduct"/> + <requiredEntity createDataKey="simple1"/> + </createData> + </before> + <after> + <deleteData stepKey="deleteProduct" createDataKey="createProduct"/> + </after> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToFrontPage"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="searchStorefront"> + <argument name="phrase" value="$createProduct.name$"/> + </actionGroup> + <actionGroup ref="StorefrontAddToCartFromQuickSearch" stepKey="addProductToCart"> + <argument name="productName" value="$createProduct.name$"/> + </actionGroup> + </test> + <test name="QuickSearchAndAddToCartBundleDynamic"> + <annotations> + <stories value="Search Product on Storefront"/> + <title value="User should be able to use Quick Search to find a Bundle Dynamic product and add it to cart"/> + <description value="Use Quick Search to find Bundle Dynamic Product and Add to Cart"/> + <severity value="MAJOR"/> + <testCaseId value="MC-14789"/> + <group value="CatalogSearch"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!--Create dynamic product--> + <createData entity="ApiBundleProductPriceViewRange" stepKey="createBundleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="createBundleProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink1"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="createProduct"/> + <field key="qty">10</field> + </createData> + <!--Finish bundle creation--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <amOnPage url="{{AdminProductEditPage.url($$createBundleProduct.id$$)}}" stepKey="goToProductEditPage"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + </before> + <after> + <deleteData stepKey="deleteBundleProduct" createDataKey="createBundleProduct"/> + <deleteData stepKey="deleteProduct" createDataKey="createProduct"/> + <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> + </after> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToFrontPage"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="searchStorefront"> + <argument name="phrase" value="$createBundleProduct.name$"/> + </actionGroup> + <actionGroup ref="StorefrontOpenProductFromQuickSearch" stepKey="openAndCheckProduct"> + <argument name="productName" value="$createBundleProduct.name$"/> + <argument name="productUrlKey" value="$createBundleProduct.custom_attributes[url_key]$"/> + </actionGroup> + <actionGroup ref="StorefrontAddBundleProductFromProductToCartActionGroup" stepKey="addProductToCart"> + <argument name="productName" value="$createBundleProduct.name$"/> + </actionGroup> + </test> + <test name="QuickSearchAndAddToCartBundleFixed"> + <annotations> + <stories value="Search Product on Storefront"/> + <title value="User should be able to use Quick Search to find a Bundle Fixed product and add it to cart"/> + <description value="Use Quick Search to find Bundle Fixed Product and Add to Cart"/> + <severity value="MAJOR"/> + <testCaseId value="MC-14790"/> + <group value="CatalogSearch"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!--Create fixed product--> + <!--Create 2 simple products--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> + <!-- Create the bundle product based --> + <createData entity="ApiFixedBundleProduct" stepKey="createBundleProduct"/> + <createData entity="MultipleSelectOption" stepKey="createBundleOption1_1"> + <requiredEntity createDataKey="createBundleProduct"/> + <field key="required">false</field> + </createData> + <createData entity="CheckboxOption" stepKey="createBundleOption1_2"> + <requiredEntity createDataKey="createBundleProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct2"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct2"/> + </createData> + + <!--Finish bundle creation--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <amOnPage url="{{AdminProductEditPage.url($$createBundleProduct.id$$)}}" stepKey="goToProductEditPage"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + </before> + <after> + <deleteData stepKey="deleteBundleProduct" createDataKey="createBundleProduct"/> + <deleteData stepKey="deleteProduct" createDataKey="createProduct"/> + <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> + </after> + <comment userInput="$simpleProduct1.name$" stepKey="asdf"/> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToFrontPage"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="searchStorefront"> + <argument name="phrase" value="$createBundleProduct.name$"/> + </actionGroup> + <actionGroup ref="StorefrontOpenProductFromQuickSearch" stepKey="openAndCheckProduct"> + <argument name="productName" value="$createBundleProduct.name$"/> + <argument name="productUrlKey" value="$createBundleProduct.custom_attributes[url_key]$"/> + </actionGroup> + <actionGroup ref="StorefrontAddBundleProductFromProductToCartWithMultiOption" stepKey="addProductToCart"> + <argument name="productName" value="$createBundleProduct.name$"/> + <argument name="optionName" value="$createBundleOption1_1.name$"/> + <argument name="value" value="$simpleProduct1.name$"/> + </actionGroup> + </test> + + <test name="QuickSearchConfigurableChildren"> + <annotations> + <stories value="Search Product on Storefront"/> + <title value="User should be able to use Quick Search to a configurable product's child products"/> + <description value="Use Quick Search to find a configurable product with enabled/disable children"/> + <severity value="MAJOR"/> + <testCaseId value="MC-14798"/> + <group value="CatalogSearch"/> + <group value="mtf_migrated"/> + <skip> + <issueId value="MC-15101"/> + </skip> + </annotations> + <before> + <!-- Create the category --> + <createData entity="ApiCategory" stepKey="createCategory"/> + + <!-- Create blank AttributeSet--> + <createData entity="CatalogAttributeSet" stepKey="attributeSet"/> + + <!-- Create an attribute with two options to be used in the first child product --> + <createData entity="hiddenDropdownAttributeWithOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <!-- Assign attribute to set --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="goToAttributeGridPage" stepKey="goToPage"/> + <actionGroup ref="goToAttributeSetByName" stepKey="goToSet"> + <argument name="name" value="$attributeSet.attribute_set_name$"/> + </actionGroup> + <actionGroup ref="AssignAttributeToGroup" stepKey="assignToAttributeSetAndGroup"> + <argument name="group" value="Product Details"/> + <argument name="attribute" value="$createConfigProductAttribute.attribute_code$"/> + </actionGroup> + <actionGroup ref="SaveAttributeSet" stepKey="savePage"/> + + <!-- Get the first option of the attribute we created --> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Create a simple product,give it the attributeSet and attribute with the first option --> + <createData entity="ApiSimpleOneHidden" stepKey="createConfigChildProduct1"> + <field key="attribute_set_id">$attributeSet.attribute_set_id$</field> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + <updateData entity="ApiSimpleProductUpdateDescription" stepKey="updateSimpleProduct1" createDataKey="createConfigChildProduct1"/> + + <!-- Create the configurable product, give it the attributeSet and add it to the category --> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <field key="attribute_set_id">$attributeSet.attribute_set_id$</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + <!-- Create the configurable product --> + <createData entity="ConfigurableProductOneOption" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + <!-- Add the first simple product to the configurable product --> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + </before> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToFrontPage"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="searchStorefront"> + <argument name="phrase" value="$createConfigProduct.name$"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchCheckProductNameInGrid" stepKey="seeProductInGrid"> + <argument name="productName" value="$createConfigProduct.name$"/> + <argument name="index" value="1"/> + </actionGroup> + + <!-- Disable Child Product --> + <actionGroup ref="goToProductPageViaID" stepKey="goToChildProduct"> + <argument name="productId" value="$createConfigChildProduct1.id$"/> + </actionGroup> + <actionGroup ref="toggleProductEnabled" stepKey="disableProduct"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToFrontPageAgain"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="searchStorefrontAgain"> + <argument name="phrase" value="$createConfigProduct.name$"/> + </actionGroup> + <actionGroup ref="StorefrontQuickSearchCheckProductNameNotInGrid" stepKey="dontSeeProductAnymore"> + <argument name="productName" value="$createConfigProduct.name$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php index fe29fa1ece011..683070c286239 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php @@ -67,7 +67,7 @@ protected function setUp() $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->eavConfig = $this->createMock(\Magento\Eav\Model\Config::class); $storeManager = $this->getStoreManager(); - $resourceModelPool = $this->getResourceModelPool(); + $universalFactory = $this->getUniversalFactory(); $this->criteriaBuilder = $this->getCriteriaBuilder(); $this->filterBuilder = $this->createMock(\Magento\Framework\Api\FilterBuilder::class); $this->temporaryStorageFactory = $this->createMock( @@ -126,7 +126,7 @@ protected function setUp() [ 'eavConfig' => $this->eavConfig, 'storeManager' => $storeManager, - 'resourceModelPool' => $resourceModelPool, + 'universalFactory' => $universalFactory, 'searchCriteriaBuilder' => $this->criteriaBuilder, 'filterBuilder' => $this->filterBuilder, 'temporaryStorageFactory' => $this->temporaryStorageFactory, diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/BaseCollection.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/BaseCollection.php index 5a5106593af8b..9ea103e23d2a7 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/BaseCollection.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/BaseCollection.php @@ -5,8 +5,6 @@ */ namespace Magento\CatalogSearch\Test\Unit\Model\ResourceModel; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; - /** * Base class for Collection tests. * @@ -44,17 +42,19 @@ protected function getStoreManager() } /** - * Get mock for ResourceModelPool so Collection can be used. + * Get mock for UniversalFactory so Collection can be used. * - * @return \PHPUnit_Framework_MockObject_MockObject|ResourceModelPoolInterface + * @return \PHPUnit_Framework_MockObject_MockObject */ - protected function getResourceModelPool() + protected function getUniversalFactory() { $connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\Pdo\Mysql::class) ->disableOriginalConstructor() ->setMethods(['select']) ->getMockForAbstractClass(); - $select = $this->createMock(\Magento\Framework\DB\Select::class); + $select = $this->getMockBuilder(\Magento\Framework\DB\Select::class) + ->disableOriginalConstructor() + ->getMock(); $connection->expects($this->any())->method('select')->willReturn($select); $entity = $this->getMockBuilder(\Magento\Eav\Model\Entity\AbstractEntity::class) @@ -74,14 +74,14 @@ protected function getResourceModelPool() ->method('getEntityTable') ->willReturn('table'); - $resourceModelPool = $this->getMockBuilder(ResourceModelPoolInterface::class) - ->setMethods(['get']) + $universalFactory = $this->getMockBuilder(\Magento\Framework\Validator\UniversalFactory::class) + ->setMethods(['create']) ->disableOriginalConstructor() ->getMock(); - $resourceModelPool->expects($this->once()) - ->method('get') + $universalFactory->expects($this->once()) + ->method('create') ->willReturn($entity); - return $resourceModelPool; + return $universalFactory; } } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php index f1c2161a052e0..9170b81dc3182 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php @@ -49,7 +49,7 @@ class CollectionTest extends BaseCollection /** * @var MockObject */ - private $resourceModelPool; + private $universalFactory; /** * @var MockObject @@ -78,7 +78,7 @@ protected function setUp() { $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->storeManager = $this->getStoreManager(); - $this->resourceModelPool = $this->getResourceModelPool(); + $this->universalFactory = $this->getUniversalFactory(); $this->scopeConfig = $this->getScopeConfig(); $this->criteriaBuilder = $this->getCriteriaBuilder(); $this->filterBuilder = $this->getFilterBuilder(); @@ -143,7 +143,7 @@ protected function setUp() \Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection::class, [ 'storeManager' => $this->storeManager, - 'resourceModelPool' => $this->resourceModelPool, + 'universalFactory' => $this->universalFactory, 'scopeConfig' => $this->scopeConfig, 'temporaryStorageFactory' => $temporaryStorageFactory, 'productLimitationFactory' => $productLimitationFactoryMock, diff --git a/app/code/Magento/Checkout/Block/Checkout/LayoutProcessor.php b/app/code/Magento/Checkout/Block/Checkout/LayoutProcessor.php index 3f6f638db5b82..557f143352446 100644 --- a/app/code/Magento/Checkout/Block/Checkout/LayoutProcessor.php +++ b/app/code/Magento/Checkout/Block/Checkout/LayoutProcessor.php @@ -122,6 +122,7 @@ private function convertElementsToSelect($elements, $attributesToConvert) if (!in_array($code, $codes)) { continue; } + // phpcs:ignore Magento2.Functions.DiscouragedFunction $options = call_user_func($attributesToConvert[$code]); if (!is_array($options)) { continue; @@ -287,8 +288,14 @@ private function getBillingAddressComponent($paymentCode, $elements) 'provider' => 'checkoutProvider', 'deps' => 'checkoutProvider', 'dataScopePrefix' => 'billingAddress' . $paymentCode, + 'billingAddressListProvider' => '${$.name}.billingAddressList', 'sortOrder' => 1, 'children' => [ + 'billingAddressList' => [ + 'component' => 'Magento_Checkout/js/view/billing-address/list', + 'displayArea' => 'billing-address-list', + 'template' => 'Magento_Checkout/billing-address/list' + ], 'form-fields' => [ 'component' => 'uiComponent', 'displayArea' => 'additional-fieldsets', diff --git a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php index f30bd73deeae2..470d4a3aca561 100644 --- a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php +++ b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php @@ -10,6 +10,7 @@ use Magento\Checkout\Model\Session as CheckoutSession; use Magento\Customer\Api\AddressMetadataInterface; use Magento\Customer\Api\CustomerRepositoryInterface as CustomerRepository; +use Magento\Customer\Model\Address\CustomerAddressDataProvider; use Magento\Customer\Model\Context as CustomerContext; use Magento\Customer\Model\Session as CustomerSession; use Magento\Customer\Model\Url as CustomerUrlManager; @@ -34,6 +35,7 @@ * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class DefaultConfigProvider implements ConfigProviderInterface { @@ -177,6 +179,11 @@ class DefaultConfigProvider implements ConfigProviderInterface */ private $addressMetadata; + /** + * @var CustomerAddressDataProvider + */ + private $customerAddressData; + /** * @param CheckoutHelper $checkoutHelper * @param Session $checkoutSession @@ -206,6 +213,7 @@ class DefaultConfigProvider implements ConfigProviderInterface * @param UrlInterface $urlBuilder * @param AddressMetadataInterface $addressMetadata * @param AttributeOptionManagementInterface $attributeOptionManager + * @param CustomerAddressDataProvider|null $customerAddressData * @codeCoverageIgnore * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -237,7 +245,8 @@ public function __construct( \Magento\Quote\Api\PaymentMethodManagementInterface $paymentMethodManagement, UrlInterface $urlBuilder, AddressMetadataInterface $addressMetadata = null, - AttributeOptionManagementInterface $attributeOptionManager = null + AttributeOptionManagementInterface $attributeOptionManager = null, + CustomerAddressDataProvider $customerAddressData = null ) { $this->checkoutHelper = $checkoutHelper; $this->checkoutSession = $checkoutSession; @@ -268,6 +277,8 @@ public function __construct( $this->addressMetadata = $addressMetadata ?: ObjectManager::getInstance()->get(AddressMetadataInterface::class); $this->attributeOptionManager = $attributeOptionManager ?? ObjectManager::getInstance()->get(AttributeOptionManagementInterface::class); + $this->customerAddressData = $customerAddressData ?: + ObjectManager::getInstance()->get(CustomerAddressDataProvider::class); } /** @@ -359,57 +370,18 @@ private function isAutocompleteEnabled() * * @return array */ - private function getCustomerData() + private function getCustomerData(): array { $customerData = []; if ($this->isCustomerLoggedIn()) { + /** @var \Magento\Customer\Api\Data\CustomerInterface $customer */ $customer = $this->customerRepository->getById($this->customerSession->getCustomerId()); $customerData = $customer->__toArray(); - foreach ($customer->getAddresses() as $key => $address) { - $customerData['addresses'][$key]['inline'] = $this->getCustomerAddressInline($address); - if ($address->getCustomAttributes()) { - $customerData['addresses'][$key]['custom_attributes'] = $this->filterNotVisibleAttributes( - $customerData['addresses'][$key]['custom_attributes'] - ); - } - } + $customerData['addresses'] = $this->customerAddressData->getAddressDataByCustomer($customer); } return $customerData; } - /** - * Filter not visible on storefront custom attributes. - * - * @param array $attributes - * @return array - */ - private function filterNotVisibleAttributes(array $attributes) - { - $attributesMetadata = $this->addressMetadata->getAllAttributesMetadata(); - foreach ($attributesMetadata as $attributeMetadata) { - if (!$attributeMetadata->isVisible()) { - unset($attributes[$attributeMetadata->getAttributeCode()]); - } - } - - return $this->setLabelsToAttributes($attributes); - } - - /** - * Set additional customer address data - * - * @param \Magento\Customer\Api\Data\AddressInterface $address - * @return string - */ - private function getCustomerAddressInline($address) - { - $builtOutputAddressData = $this->addressMapper->toFlatArray($address); - return $this->addressConfig - ->getFormatByCode(\Magento\Customer\Model\Address\Config::DEFAULT_ADDRESS_FORMAT) - ->getRenderer() - ->renderArray($builtOutputAddressData); - } - /** * Retrieve quote data * @@ -726,61 +698,6 @@ private function getPaymentMethods() return $paymentMethods; } - /** - * Set Labels to custom Attributes - * - * @param array $customAttributes - * @return array $customAttributes - * @throws \Magento\Framework\Exception\InputException - * @throws \Magento\Framework\Exception\StateException - */ - private function setLabelsToAttributes(array $customAttributes) : array - { - if (!empty($customAttributes)) { - foreach ($customAttributes as $customAttributeCode => $customAttribute) { - $attributeOptionLabels = $this->getAttributeLabels($customAttribute, $customAttributeCode); - if (!empty($attributeOptionLabels)) { - $customAttributes[$customAttributeCode]['label'] = implode(', ', $attributeOptionLabels); - } - } - } - - return $customAttributes; - } - - /** - * Get Labels by CustomAttribute and CustomAttributeCode - * - * @param array $customAttribute - * @param string|integer $customAttributeCode - * @return array $attributeOptionLabels - * @throws \Magento\Framework\Exception\InputException - * @throws \Magento\Framework\Exception\StateException - */ - private function getAttributeLabels(array $customAttribute, string $customAttributeCode) : array - { - $attributeOptionLabels = []; - - if (!empty($customAttribute['value'])) { - $customAttributeValues = explode(',', $customAttribute['value']); - $attributeOptions = $this->attributeOptionManager->getItems( - \Magento\Customer\Model\Indexer\Address\AttributeProvider::ENTITY, - $customAttributeCode - ); - - if (!empty($attributeOptions)) { - foreach ($attributeOptions as $attributeOption) { - $attributeOptionValue = $attributeOption->getValue(); - if (in_array($attributeOptionValue, $customAttributeValues)) { - $attributeOptionLabels[] = $attributeOption->getLabel() ?? $attributeOptionValue; - } - } - } - } - - return $attributeOptionLabels; - } - /** * Get notification messages for the quote items * diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontEmailNoteMessageOnCheckoutActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontEmailNoteMessageOnCheckoutActionGroup.xml new file mode 100644 index 0000000000000..c4fc753e73713 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontEmailNoteMessageOnCheckoutActionGroup.xml @@ -0,0 +1,18 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontEmailNoteMessageOnCheckoutActionGroup"> + <arguments> + <argument name="message" type="string" defaultValue="You can create an account after checkout." /> + </arguments> + <waitForElementVisible selector="{{StorefrontCheckoutCheckoutCustomerLoginSection.emailNoteMessage}}" stepKey="waitForFormValidation"/> + <see selector="{{StorefrontCheckoutCheckoutCustomerLoginSection.emailNoteMessage}}" userInput="{{message}}" stepKey="seeTheNoteMessageIsDisplayed"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontEmailTooltipContentOnCheckoutActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontEmailTooltipContentOnCheckoutActionGroup.xml new file mode 100644 index 0000000000000..f9c6771262ccc --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontEmailTooltipContentOnCheckoutActionGroup.xml @@ -0,0 +1,19 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontEmailTooltipContentOnCheckoutActionGroup"> + <arguments> + <argument name="content" type="string" defaultValue="We'll send your order confirmation here." /> + </arguments> + <waitForElementVisible selector="{{StorefrontCheckoutCheckoutCustomerLoginSection.emailTooltipButton}}" stepKey="waitForTooltipButtonVisible" /> + <click selector="{{StorefrontCheckoutCheckoutCustomerLoginSection.emailTooltipButton}}" stepKey="clickEmailTooltipButton" /> + <see selector="{{StorefrontCheckoutCheckoutCustomerLoginSection.emailTooltipContent}}" userInput="{{content}}" stepKey="seeEmailTooltipContent" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontEmailValidationMessageOnCheckoutActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontEmailValidationMessageOnCheckoutActionGroup.xml new file mode 100644 index 0000000000000..14b96ed46ce6b --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontEmailValidationMessageOnCheckoutActionGroup.xml @@ -0,0 +1,18 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontEmailValidationMessageOnCheckoutActionGroup"> + <arguments> + <argument name="message" type="string" defaultValue="Please enter a valid email address (Ex: johndoe@domain.com)." /> + </arguments> + <waitForElementVisible selector="{{StorefrontCheckoutCheckoutCustomerLoginSection.emailErrorMessage}}" stepKey="waitForFormValidation"/> + <see selector="{{StorefrontCheckoutCheckoutCustomerLoginSection.emailErrorMessage}}" userInput="{{message}}" stepKey="seeTheErrorMessageIsDisplayed"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml index b67b7451d5968..2e4b742ece8ec 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml @@ -110,7 +110,7 @@ <argument name="paymentMethod" type="string"/> </arguments> <remove keyForRemoval="checkMessage"/> - <dontsee selector="{{CheckoutPaymentSection.paymentMethodByName(paymentMethod)}}" parametrized="true" stepKey="paymentMethodDoesNotAvailable"/> + <dontSee selector="{{CheckoutPaymentSection.paymentMethodByName(paymentMethod)}}" stepKey="paymentMethodDoesNotAvailable"/> </actionGroup> <!-- Logged in user checkout filling shipping section --> <actionGroup name="LoggedInUserCheckoutFillingShippingSectionActionGroup"> @@ -241,6 +241,21 @@ <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskAfterPaymentMethodSelection"/> </actionGroup> + <!-- Check selected shipping address information on shipping information step --> + <actionGroup name="CheckSelectedShippingAddressInCheckoutActionGroup"> + <arguments> + <argument name="customerVar"/> + <argument name="customerAddressVar"/> + </arguments> + <waitForElement selector="{{CheckoutShippingSection.shippingTab}}" time="30" stepKey="waitForShippingSectionLoaded"/> + <see stepKey="VerifyFirstNameInSelectedAddress" selector="{{CheckoutShippingSection.selectedShippingAddress}}" userInput="{{customerVar.firstname}}" /> + <see stepKey="VerifyLastNameInSelectedAddress" selector="{{CheckoutShippingSection.selectedShippingAddress}}" userInput="{{customerVar.lastname}}" /> + <see stepKey="VerifyStreetInSelectedAddress" selector="{{CheckoutShippingSection.selectedShippingAddress}}" userInput="{{customerAddressVar.street[0]}}" /> + <see stepKey="VerifyCityInSelectedAddress" selector="{{CheckoutShippingSection.selectedShippingAddress}}" userInput="{{customerAddressVar.city}}" /> + <see stepKey="VerifyZipInSelectedAddress" selector="{{CheckoutShippingSection.selectedShippingAddress}}" userInput="{{customerAddressVar.postcode}}" /> + <see stepKey="VerifyPhoneInSelectedAddress" selector="{{CheckoutShippingSection.selectedShippingAddress}}" userInput="{{customerAddressVar.telephone}}" /> + </actionGroup> + <!-- Check billing address in checkout --> <actionGroup name="CheckBillingAddressInCheckoutActionGroup"> <arguments> @@ -257,13 +272,29 @@ <see userInput="{{customerAddressVar.telephone}}" selector="{{CheckoutPaymentSection.billingAddress}}" stepKey="assertBillingAddressTelephone"/> </actionGroup> + <!-- Check billing address in checkout with billing address on payment page --> + <actionGroup name="CheckBillingAddressInCheckoutWithBillingAddressOnPaymentPageActionGroup"> + <arguments> + <argument name="customerVar"/> + <argument name="customerAddressVar"/> + </arguments> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/> + <see userInput="{{customerVar.firstName}}" selector="{{CheckoutPaymentWithDisplayBillingAddressOnPaymentPageSection.billingAddressDetails}}" stepKey="assertBillingAddressDetailsFirstName"/> + <see userInput="{{customerVar.lastName}}" selector="{{CheckoutPaymentWithDisplayBillingAddressOnPaymentPageSection.billingAddressDetails}}" stepKey="assertBillingAddressDetailsLastName"/> + <see userInput="{{customerAddressVar.street[0]}}" selector="{{CheckoutPaymentWithDisplayBillingAddressOnPaymentPageSection.billingAddressDetails}}" stepKey="assertBillingAddressDetailsStreet"/> + <see userInput="{{customerAddressVar.city}}" selector="{{CheckoutPaymentWithDisplayBillingAddressOnPaymentPageSection.billingAddressDetails}}" stepKey="assertBillingAddressDetailsCity"/> + <see userInput="{{customerAddressVar.state}}" selector="{{CheckoutPaymentWithDisplayBillingAddressOnPaymentPageSection.billingAddressDetails}}" stepKey="assertBillingAddressDetailsState"/> + <see userInput="{{customerAddressVar.postcode}}" selector="{{CheckoutPaymentWithDisplayBillingAddressOnPaymentPageSection.billingAddressDetails}}" stepKey="assertBillingAddressDetailsPostcode"/> + <see userInput="{{customerAddressVar.telephone}}" selector="{{CheckoutPaymentWithDisplayBillingAddressOnPaymentPageSection.billingAddressDetails}}" stepKey="assertBillingAddressDetailsTelephone"/> + </actionGroup> + <!-- Checkout place order --> <actionGroup name="CheckoutPlaceOrderActionGroup"> <arguments> <argument name="orderNumberMessage"/> <argument name="emailYouMessage"/> </arguments> - <waitForElement selector="{{CheckoutPaymentSection.placeOrder}}" time="30" stepKey="waitForPlaceOrderButton"/> + <waitForElementVisible selector="{{CheckoutPaymentSection.placeOrder}}" time="30" stepKey="waitForPlaceOrderButton"/> <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> <see selector="{{CheckoutSuccessMainSection.success}}" userInput="{{orderNumberMessage}}" stepKey="seeOrderNumber"/> <see selector="{{CheckoutSuccessMainSection.success}}" userInput="{{emailYouMessage}}" stepKey="seeEmailYou"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/FillNewShippingAddressModalActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/FillNewShippingAddressModalActionGroup.xml new file mode 100644 index 0000000000000..7035855cc0ed3 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/FillNewShippingAddressModalActionGroup.xml @@ -0,0 +1,18 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FillNewShippingAddressModalActionGroup" extends="FillShippingAddressOneStreetActionGroup"> + <arguments> + <argument name="address"/> + </arguments> + <selectOption stepKey="selectRegion" selector="{{CheckoutShippingSection.region}}" + userInput="{{address.state}}" after="fillCityName"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertCartEstimateShippingAndTaxActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertCartEstimateShippingAndTaxActionGroup.xml new file mode 100644 index 0000000000000..0d6f34098c048 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertCartEstimateShippingAndTaxActionGroup.xml @@ -0,0 +1,20 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Assert Estimate Shipping and Tax Data in Cart --> + <actionGroup name="StorefrontAssertCartEstimateShippingAndTaxActionGroup"> + <arguments> + <argument name="customerData" defaultValue="Simple_US_CA_Customer_For_Shipment"/> + </arguments> + <seeInField selector="{{CheckoutCartSummarySection.country}}" userInput="{{customerData.country}}" stepKey="assertCountryFieldInCartEstimateShippingAndTaxSection"/> + <seeInField selector="{{CheckoutCartSummarySection.stateProvinceInput}}" userInput="{{customerData.region}}" stepKey="assertStateProvinceInCartEstimateShippingAndTaxSection"/> + <seeInField selector="{{CheckoutCartSummarySection.postcode}}" userInput="{{customerData.postcode}}" stepKey="assertZipPostalCodeInCartEstimateShippingAndTaxSection"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertCartShippingMethodSelectedActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertCartShippingMethodSelectedActionGroup.xml new file mode 100644 index 0000000000000..4061f97821cd0 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertCartShippingMethodSelectedActionGroup.xml @@ -0,0 +1,19 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Assert Shipping Method Is Checked on Cart --> + <actionGroup name="StorefrontAssertCartShippingMethodSelectedActionGroup"> + <arguments> + <argument name="carrierCode" type="string"/> + <argument name="methodCode" type="string"/> + </arguments> + <seeCheckboxIsChecked selector="{{CheckoutCartSummarySection.shippingMethodElementId(carrierCode, methodCode)}}" stepKey="assertShippingMethodIsChecked"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertCheckoutEstimateShippingInformationActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertCheckoutEstimateShippingInformationActionGroup.xml new file mode 100644 index 0000000000000..82d7e12105b8c --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertCheckoutEstimateShippingInformationActionGroup.xml @@ -0,0 +1,20 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Assert Estimate Shipping and Tax Data on Checkout --> + <actionGroup name="StorefrontAssertCheckoutEstimateShippingInformationActionGroup"> + <arguments> + <argument name="customerData" defaultValue="Simple_US_CA_Customer_For_Shipment"/> + </arguments> + <seeInField selector="{{CheckoutShippingGuestInfoSection.country}}" userInput="{{customerData.country}}" stepKey="assertCountryField"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.region}}" userInput="{{customerData.region}}" stepKey="assertStateProvinceField"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.postcode}}" userInput="{{customerData.postcode}}" stepKey="assertZipPostalCodeField"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertCheckoutShippingMethodSelectedActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertCheckoutShippingMethodSelectedActionGroup.xml new file mode 100644 index 0000000000000..33f2852f1f0ad --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertCheckoutShippingMethodSelectedActionGroup.xml @@ -0,0 +1,18 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Assert Shipping Method by Name Is Checked on Checkout --> + <actionGroup name="StorefrontAssertCheckoutShippingMethodSelectedActionGroup"> + <arguments> + <argument name="shippingMethod"/> + </arguments> + <seeCheckboxIsChecked selector="{{CheckoutShippingMethodsSection.checkShippingMethodByName('shippingMethod')}}" stepKey="assertShippingMethodByNameIsChecked"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertGuestShippingInfoActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertGuestShippingInfoActionGroup.xml new file mode 100644 index 0000000000000..02c362bf34058 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertGuestShippingInfoActionGroup.xml @@ -0,0 +1,28 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Assert guest shipping info on checkout --> + <actionGroup name="StorefrontAssertGuestShippingInfoActionGroup"> + <arguments> + <argument name="customerData" defaultValue="Simple_UK_Customer_For_Shipment"/> + </arguments> + <seeInField selector="{{CheckoutShippingGuestInfoSection.email}}" userInput="{{customerData.email}}" stepKey="assertEmailAddress"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.firstName}}" userInput="{{customerData.firstName}}" stepKey="assertFirstName"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.lastName}}" userInput="{{customerData.lastName}}" stepKey="assertLastName"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.company}}" userInput="{{customerData.company}}" stepKey="assertCompany"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.street}}" userInput="{{customerData.streetFirstLine}}" stepKey="assertAddressFirstLine"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.street2}}" userInput="{{customerData.streetSecondLine}}" stepKey="assertAddressSecondLine"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.city}}" userInput="{{customerData.city}}" stepKey="assertCity"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.country}}" userInput="{{customerData.country}}" stepKey="assertCountry"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.regionInput}}" userInput="{{customerData.region}}" stepKey="assertStateProvince"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.postcode}}" userInput="{{customerData.postcode}}" stepKey="assertZipPostalCode"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.telephone}}" userInput="{{customerData.telephone}}" stepKey="assertPhoneNumber"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertShippingMethodPresentInCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertShippingMethodPresentInCartActionGroup.xml new file mode 100644 index 0000000000000..3d8530ae83704 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertShippingMethodPresentInCartActionGroup.xml @@ -0,0 +1,18 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Assert shipping method is present in cart --> + <actionGroup name="StorefrontAssertShippingMethodPresentInCartActionGroup"> + <arguments> + <argument name="shippingMethod" type="string"/> + </arguments> + <see selector="{{CheckoutCartSummarySection.shippingMethodLabel}}" userInput="{{shippingMethod}}" stepKey="assertShippingMethodIsPresentInCart"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCartEstimateShippingAndTaxActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCartEstimateShippingAndTaxActionGroup.xml new file mode 100644 index 0000000000000..4176859f99f70 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCartEstimateShippingAndTaxActionGroup.xml @@ -0,0 +1,25 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Fill Estimate Shipping and Tax fields --> + <actionGroup name="StorefrontCartEstimateShippingAndTaxActionGroup"> + <arguments> + <argument name="estimateAddress" defaultValue="EstimateAddressCalifornia"/> + </arguments> + <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="clickOnEstimateShippingAndTax"/> + <waitForElementVisible selector="{{CheckoutCartSummarySection.country}}" stepKey="waitForCountrySelectorIsVisible"/> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="{{estimateAddress.country}}" stepKey="selectCountry"/> + <waitForLoadingMaskToDisappear stepKey="waitForCountryLoadingMaskDisappear"/> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{estimateAddress.state}}" stepKey="selectStateProvince"/> + <waitForLoadingMaskToDisappear stepKey="waitForStateLoadingMaskDisappear"/> + <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="{{estimateAddress.zipCode}}" stepKey="fillZipPostalCodeField"/> + <waitForLoadingMaskToDisappear stepKey="waitForZipLoadingMaskDisappear"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontFillEmailFieldOnCheckoutActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontFillEmailFieldOnCheckoutActionGroup.xml new file mode 100644 index 0000000000000..fcac780a36776 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontFillEmailFieldOnCheckoutActionGroup.xml @@ -0,0 +1,19 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontFillEmailFieldOnCheckoutActionGroup"> + <arguments> + <argument name="email" type="string" /> + </arguments> + <fillField selector="{{StorefrontCheckoutCheckoutCustomerLoginSection.email}}" userInput="{{email}}" stepKey="fillCustomerEmailField"/> + <doubleClick selector="{{StorefrontCheckoutCheckoutCustomerLoginSection.emailTooltipButton}}" stepKey="clickToMoveFocusFromEmailInput" /> + <waitForPageLoad stepKey="waitForPageLoad" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontFillGuestShippingInfoActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontFillGuestShippingInfoActionGroup.xml new file mode 100644 index 0000000000000..e7669d62c79a0 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontFillGuestShippingInfoActionGroup.xml @@ -0,0 +1,25 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Fill data in checkout shipping section --> + <actionGroup name="StorefrontFillGuestShippingInfoActionGroup"> + <arguments> + <argument name="customerData" defaultValue="Simple_UK_Customer_For_Shipment"/> + </arguments> + <fillField selector="{{CheckoutShippingGuestInfoSection.email}}" userInput="{{customerData.email}}" stepKey="fillEmailAddressField"/> + <fillField selector="{{CheckoutShippingGuestInfoSection.firstName}}" userInput="{{customerData.firstName}}" stepKey="fillFirstNameField"/> + <fillField selector="{{CheckoutShippingGuestInfoSection.lastName}}" userInput="{{customerData.lastName}}" stepKey="fillLastNameField"/> + <fillField selector="{{CheckoutShippingGuestInfoSection.company}}" userInput="{{customerData.company}}" stepKey="fillCompanyField"/> + <fillField selector="{{CheckoutShippingGuestInfoSection.street}}" userInput="{{customerData.streetFirstLine}}" stepKey="fillStreetAddressFirstLineField"/> + <fillField selector="{{CheckoutShippingGuestInfoSection.street2}}" userInput="{{customerData.streetSecondLine}}" stepKey="fillStreetAddressSecondLineField"/> + <fillField selector="{{CheckoutShippingGuestInfoSection.city}}" userInput="{{customerData.city}}" stepKey="fillCityField"/> + <fillField selector="{{CheckoutShippingGuestInfoSection.telephone}}" userInput="{{customerData.telephone}}" stepKey="fillPhoneNumberField"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontOpenCheckoutPageActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontOpenCheckoutPageActionGroup.xml new file mode 100644 index 0000000000000..b18d476c02c65 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontOpenCheckoutPageActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontOpenCheckoutPageActionGroup"> + <amOnPage url="{{CheckoutPage.url}}" stepKey="openCheckoutPage" /> + <waitForPageLoad stepKey="waitForCheckoutPageLoaded" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/Data/EstimateAndTaxData.xml b/app/code/Magento/Checkout/Test/Mftf/Data/EstimateAndTaxData.xml new file mode 100644 index 0000000000000..36dea5a521a04 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Data/EstimateAndTaxData.xml @@ -0,0 +1,16 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="EstimateAddressCalifornia"> + <data key="country">United States</data> + <data key="state">California</data> + <data key="zipCode">90240</data> + </entity> +</entities> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml index 8d14a9a561900..3100fae3b119b 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml @@ -20,9 +20,12 @@ <element name="shippingHeading" type="button" selector="#block-shipping-heading"/> <element name="postcode" type="input" selector="input[name='postcode']" timeout="10"/> <element name="stateProvince" type="select" selector="select[name='region_id']" timeout="10"/> + <element name="stateProvinceInput" type="input" selector="input[name='region']"/> <element name="country" type="select" selector="select[name='country_id']" timeout="10"/> <element name="countryParameterized" type="select" selector="select[name='country_id'] > option:nth-child({{var}})" timeout="10" parameterized="true"/> <element name="estimateShippingAndTax" type="text" selector="#block-shipping-heading" timeout="5"/> <element name="flatRateShippingMethod" type="input" selector="#s_method_flatrate_flatrate" timeout="30"/> + <element name="shippingMethodLabel" type="text" selector="#co-shipping-method-form dl dt span"/> + <element name="shippingMethodElementId" type="radio" selector="#s_method_{{carrierCode}}_{{methodCode}}" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml index 0206c18b819c2..cbe71e9cffa60 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml @@ -54,5 +54,6 @@ <element name="addressBook" type="button" selector="//a[text()='Address Book']"/> <element name="noQuotes" type="text" selector=".no-quotes-block"/> <element name="paymentMethodByName" type="text" selector="//*[@id='checkout-payment-method-load']//*[contains(@class, 'payment-group')]//label[normalize-space(.)='{{var1}}']" parameterized="true"/> + <element name="billingAddressSelectShared" type="select" selector=".checkout-billing-address select[name='billing_address_id']"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentWithDisplayBillingAddressOnPaymentPageSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentWithDisplayBillingAddressOnPaymentPageSection.xml new file mode 100644 index 0000000000000..42decd8d43220 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentWithDisplayBillingAddressOnPaymentPageSection.xml @@ -0,0 +1,14 @@ +<?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="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="CheckoutPaymentWithDisplayBillingAddressOnPaymentPageSection"> + <element name="billingAddressDetails" type="text" selector="div.billing-address-details"/> + </section> +</sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml index 6838824400b96..b19e365f2e32c 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml @@ -9,13 +9,17 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CheckoutShippingGuestInfoSection"> - <element name="email" type="input" selector="#customer-email"/> + <element name="email" type="input" selector="#checkout-customer-email"/> <element name="firstName" type="input" selector="input[name=firstname]"/> <element name="lastName" type="input" selector="input[name=lastname]"/> <element name="street" type="input" selector="input[name='street[0]']"/> + <element name="street2" type="input" selector="input[name='street[1]']"/> <element name="city" type="input" selector="input[name=city]"/> <element name="region" type="select" selector="select[name=region_id]"/> + <element name="regionInput" type="input" selector="input[name=region]"/> <element name="postcode" type="input" selector="input[name=postcode]"/> + <element name="country" type="select" selector="select[name=country_id]"/> + <element name="company" type="input" selector="input[name=company]"/> <element name="telephone" type="input" selector="input[name=telephone]"/> <element name="next" type="button" selector="button.button.action.continue.primary" timeout="30"/> <element name="firstShippingMethod" type="radio" selector=".row:nth-of-type(1) .col-method .radio"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingMethodsSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingMethodsSection.xml index ab4b59fd67d03..77d903eab3934 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingMethodsSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingMethodsSection.xml @@ -17,5 +17,6 @@ <element name="shippingMethodRowByName" type="text" selector="//div[@id='checkout-shipping-method-load']//td[contains(., '{{var1}}')]/.." parameterized="true"/> <element name="shipHereButton" type="button" selector="//div/following-sibling::div/button[contains(@class, 'action-select-shipping-item')]"/> <element name="shippingMethodLoader" type="button" selector="//div[contains(@class, 'checkout-shipping-method')]/following-sibling::div[contains(@class, 'loading-mask')]"/> + <element name="freeShippingShippingMethod" type="input" selector="#s_method_freeshipping_freeshipping" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml index d825e10395145..9676f16f3a5c6 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml @@ -15,7 +15,7 @@ <element name="editAddressButton" type="button" selector=".action-edit-address" timeout="30"/> <element name="addressDropdown" type="select" selector="[name=billing_address_id]"/> <element name="newAddressButton" type="button" selector=".action-show-popup" timeout="30"/> - <element name="email" type="input" selector="#customer-email"/> + <element name="email" type="input" selector="#checkout-customer-email"/> <element name="firstName" type="input" selector="input[name=firstname]"/> <element name="lastName" type="input" selector="input[name=lastname]"/> <element name="company" type="input" selector="input[name=company]"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutCheckoutCustomerLoginSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutCheckoutCustomerLoginSection.xml new file mode 100644 index 0000000000000..9772fa1993acb --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutCheckoutCustomerLoginSection.xml @@ -0,0 +1,22 @@ +<?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="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCheckoutCheckoutCustomerLoginSection"> + <element name="email" type="input" selector="form[data-role='email-with-possible-login'] input[name='username']" /> + <element name="emailNoteMessage" type="text" selector="//form[@data-role='email-with-possible-login']//div[input[@name='username']]//*[contains(@class, 'note')]" /> + <element name="emailErrorMessage" type="text" selector="//form[@data-role='email-with-possible-login']//div[input[@name='username']]//*[@id='checkout-customer-email-error']" /> + <element name="emailTooltipButton" type="button" selector="//form[@data-role='email-with-possible-login']//div[input[@name='username']]//*[contains(@class, 'action-help')]" /> + <element name="emailTooltipContent" type="text" selector="//form[@data-role='email-with-possible-login']//div[input[@name='username']]//*[contains(@class, 'field-tooltip-content')]" /> + <element name="password" type="input" selector="form[data-role='email-with-possible-login'] input[name='password']" /> + <element name="passwordNoteMessage" type="text" selector="//form[@data-role='email-with-possible-login']//div[input[@name='password']]//*[contains(@class, 'note')]" /> + <element name="submit" type="button" selector="form[data-role='email-with-possible-login'] button[type='submit']" /> + <element name="forgotPassword" type="button" selector="form[data-role='email-with-possible-login'] a.remind" /> + </section> +</sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutPaymentMethodSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutPaymentMethodSection.xml index 55c4385706ba9..9d9a96d2ea5e6 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutPaymentMethodSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutPaymentMethodSection.xml @@ -12,5 +12,6 @@ <element name="billingAddress" type="text" selector=".checkout-billing-address"/> <element name="checkPaymentMethodByName" type="radio" selector="//div[@id='checkout-payment-method-load']//div[@class='payment-method']//label//span[contains(., '{{methodName}}')]/../..//input" parameterized="true"/> <element name="billingAddressSameAsShipping" type="checkbox" selector=".payment-method._active [name='billing-address-same-as-shipping']"/> + <element name="billingAddressSameAsShippingShared" type="checkbox" selector="#billing-address-same-as-shipping-shared"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/DefaultBillingAddressShouldBeCheckedOnPaymentPageTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/DefaultBillingAddressShouldBeCheckedOnPaymentPageTest.xml new file mode 100644 index 0000000000000..166f5022d5aeb --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/DefaultBillingAddressShouldBeCheckedOnPaymentPageTest.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="DefaultBillingAddressShouldBeCheckedOnPaymentPageTest"> + <annotations> + <features value="Checkout"/> + <stories value="Checkout via the Storefront"/> + <title value="The default billing address should be used on checkout"/> + <description value="Default billing address should be preselected on payments page on checkout if it exist"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-98892"/> + <useCaseId value="MAGETWO-70996"/> + <group value="checkout"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + <!--Go to Storefront as Customer--> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="customerLogin"> + <argument name="Customer" value="$$createCustomer$$" /> + </actionGroup> + </before> + <after> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <!--Logout from customer account--> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutCustomer"/> + </after> + <!-- Add simple product to cart and go to checkout--> + <actionGroup ref="AddSimpleProductToCart" stepKey="addProductToCart"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + <!-- Click "+ New Address" and Fill new address--> + <click selector="{{CheckoutShippingSection.newAddressButton}}" stepKey="addAddress"/> + <actionGroup ref="LoggedInCheckoutWithOneAddressFieldWithoutStateField" stepKey="changeAddress"> + <argument name="Address" value="UK_Not_Default_Address"/> + <argument name="classPrefix" value="._show"/> + </actionGroup> + <!--Click "Save Addresses" --> + <click selector="{{CheckoutShippingSection.saveAddress}}" stepKey="saveAddress"/> + <waitForPageLoad stepKey="waitForAddressSaved"/> + <dontSeeElement selector="{{StorefrontCheckoutAddressPopupSection.newAddressModalPopup}}" stepKey="dontSeeModalPopup"/> + <!--Select Shipping Rate "Flat Rate" and click "Next" button--> + <actionGroup ref="CheckoutSelectFlatRateShippingMethodActionGroup" stepKey="selectFlatRateShipping"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask2"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext"/> + <!--Verify that "My billing and shipping address are the same" is unchecked and billing address is preselected--> + <dontSeeCheckboxIsChecked selector="{{CheckoutPaymentSection.billingAddressNotSameCheckbox}}" stepKey="shippingAndBillingAddressIsSameUnchecked"/> + <see selector="{{CheckoutPaymentSection.billingAddress}}" userInput="{{US_Address_TX.street[0]}}" stepKey="assertBillingAddress"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml index e7c2ad3dd28a4..fadc9ec50ad8d 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml @@ -186,20 +186,20 @@ <see selector="{{StorefrontMinicartSection.quantity}}" userInput="1" stepKey="seeCartQuantity2"/> <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart2" /> - <click stepKey="changeShippingAddress" selector="{{CheckoutShippingMethodsSection.shipHereButton}}"/> - <waitForElementNotVisible stepKey="waitForShippingMethodLoaderNotVisible" selector="{{CheckoutShippingMethodsSection.shippingMethodLoader}}" time="30"/> - <waitForElementVisible stepKey="waitForShippingMethodRadioToBeVisible" selector="{{CheckoutShippingMethodsSection.firstShippingMethod}}" time="30"/> + <click selector="{{CheckoutShippingMethodsSection.shipHereButton}}" stepKey="changeShippingAddress"/> + <waitForElementNotVisible selector="{{CheckoutShippingMethodsSection.shippingMethodLoader}}" time="30" stepKey="waitForShippingMethodLoaderNotVisible"/> + <waitForElementVisible selector="{{CheckoutShippingMethodsSection.firstShippingMethod}}" time="30" stepKey="waitForShippingMethodRadioToBeVisible"/> <waitForPageLoad stepKey="waitForPageLoad23"/> - <click stepKey="selectFirstShippingMethod2" selector="{{CheckoutShippingMethodsSection.firstShippingMethod}}"/> - <waitForElement stepKey="waitForShippingMethodSelect2" selector="{{CheckoutShippingMethodsSection.next}}" time="30"/> - <click stepKey="clickNextOnShippingMethodLoad2" selector="{{CheckoutShippingMethodsSection.next}}" /> + <click selector="{{CheckoutShippingMethodsSection.firstShippingMethod}}" stepKey="selectFirstShippingMethod2"/> + <waitForElement selector="{{CheckoutShippingMethodsSection.next}}" time="30" stepKey="waitForShippingMethodSelect2"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNextOnShippingMethodLoad2"/> <!-- Checkout select Check/Money Order payment --> <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment2"/> - <waitForElement stepKey="waitForPlaceOrderButton2" selector="{{CheckoutPaymentSection.placeOrder}}" time="30" /> - <see stepKey="seeBillingAddressIsCorrect2" selector="{{CheckoutPaymentSection.billingAddress}}" userInput="{{UK_Not_Default_Address.street[0]}}" /> - <click stepKey="clickPlaceOrderButton2" selector="{{CheckoutPaymentSection.placeOrder}}" /> + <waitForElement selector="{{CheckoutPaymentSection.placeOrder}}" time="30" stepKey="waitForPlaceOrderButton2"/> + <see selector="{{CheckoutPaymentSection.billingAddress}}" userInput="{{US_Address_NY.street[0]}}" stepKey="seeBillingAddressIsCorrect2" /> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrderButton2"/> <waitForPageLoad stepKey="waitForOrderSuccessPage2"/> - <see stepKey="seeSuccessMessage2" selector="{{CheckoutSuccessMainSection.success}}" userInput="Your order number is:" /> + <see selector="{{CheckoutSuccessMainSection.success}}" userInput="Your order number is:" stepKey="seeSuccessMessage2"/> </test> <test name="StorefrontCustomerCheckoutTestWithRestrictedCountriesForPayment"> <annotations> @@ -216,16 +216,18 @@ <createData entity="ApiSimpleProduct" stepKey="createProduct"> <requiredEntity createDataKey="createCategory"/> </createData> - <magentoCLI stepKey="allowSpecificValue" command="config:set payment/checkmo/allowspecific 1" /> - <magentoCLI stepKey="specificCountryValue" command="config:set payment/checkmo/specificcountry GB" /> + <magentoCLI command="config:set checkout/options/display_billing_address_on 1" stepKey="setShowBillingAddressOnPaymentPage" /> + <magentoCLI command="config:set payment/checkmo/allowspecific 1" stepKey="allowSpecificValue" /> + <magentoCLI command="config:set payment/checkmo/specificcountry GB" stepKey="specificCountryValue" /> <createData entity="Simple_US_Customer" stepKey="simpleuscustomer"/> </before> <after> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> - <magentoCLI stepKey="allowSpecificValue" command="config:set payment/checkmo/allowspecific 0" /> - <magentoCLI stepKey="specificCountryValue" command="config:set payment/checkmo/specificcountry ''" /> + <magentoCLI command="config:set payment/checkmo/allowspecific 0" stepKey="allowSpecificValue" /> + <magentoCLI command="config:set payment/checkmo/specificcountry ''" stepKey="specificCountryValue" /> + <magentoCLI command="config:set checkout/options/display_billing_address_on 0" stepKey="setDisplayBillingAddressOnPaymentMethod" /> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutCustomer"/> </after> <!-- Login as Customer --> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="customerLogin"> @@ -253,16 +255,18 @@ <dontsee selector="{{CheckoutPaymentSection.paymentMethodByName('Check / Money order')}}" stepKey="paymentMethodDoesNotAvailable"/> <!-- Fill UK Address and verify that payment available and checkout successful --> - <click selector="{{CheckoutHeaderSection.shippingMethodStep}}" stepKey="goToShipping" /> - <click selector="{{CheckoutShippingSection.newAddressButton}}" stepKey="fillNewAddress" /> - <actionGroup ref="LoggedInUserCheckoutAddNewShippingSectionWithoutRegionActionGroup" stepKey="customerCheckoutFillingShippingSectionUK"> - <argument name="customerVar" value="CustomerEntityOne" /> - <argument name="customerAddressVar" value="UK_Not_Default_Address" /> + <uncheckOption selector="{{StorefrontCheckoutPaymentMethodSection.billingAddressSameAsShippingShared}}" stepKey="uncheckBillingAddressSameAsShippingCheckCheckBox"/> + <selectOption selector="{{CheckoutPaymentSection.billingAddressSelectShared}}" userInput="New Address" stepKey="clickOnNewAddress"/> + <waitForPageLoad stepKey="waitNewAddressBillingForm"/> + <actionGroup ref="LoggedInCheckoutFillNewBillingAddressActionGroup" stepKey="changeAddress"> + <argument name="Address" value="updateCustomerUKAddress"/> + <argument name="classPrefix" value="[aria-hidden=false]"/> </actionGroup> + <click selector="{{CheckoutPaymentSection.addressAction('Update')}}" stepKey="clickUpdateBillingAddressButton" /> <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="customerSelectCheckMoneyOrderPayment" /> <actionGroup ref="CheckoutPlaceOrderActionGroup" stepKey="customerPlaceorder"> <argument name="orderNumberMessage" value="CONST.successCheckoutOrderNumberMessage" /> <argument name="emailYouMessage" value="CONST.successCheckoutEmailYouMessage" /> </actionGroup> </test> -</tests> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml index 8537e10ce5a03..651c5bd8d4375 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml @@ -71,6 +71,7 @@ <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext"/> <!--Refresh Page and Place Order--> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment"/> <reloadPage stepKey="reloadPage"/> <waitForElement selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="waitForPlaceOrderButton"/> <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> @@ -89,7 +90,7 @@ <amOnPage url="{{StorefrontCustomerOrderViewPage.url({$grabOrderNumber})}}" stepKey="goToOrderReviewPage"/> <see userInput="{{UK_Not_Default_Address.street[0]}} {{UK_Not_Default_Address.city}}, {{UK_Not_Default_Address.postcode}}" selector="{{StorefrontCustomerOrderViewSection.shippingAddress}}" stepKey="checkShippingAddress"/> - <see userInput="{{UK_Not_Default_Address.street[0]}} {{UK_Not_Default_Address.city}}, {{UK_Not_Default_Address.postcode}}" + <see userInput="{{US_Address_TX_Default_Billing.street[0]}}" selector="{{StorefrontCustomerOrderViewSection.billingAddress}}" stepKey="checkBillingAddress"/> </test> </tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontPersistentDataForGuestCustomerWithPhysicalQuoteTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontPersistentDataForGuestCustomerWithPhysicalQuoteTest.xml new file mode 100644 index 0000000000000..20ff67a076e1e --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontPersistentDataForGuestCustomerWithPhysicalQuoteTest.xml @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontPersistentDataForGuestCustomerWithPhysicalQuoteTest"> + <annotations> + <features value="Checkout"/> + <stories value="Checkout via Guest Checkout"/> + <title value="Persistent Data for Guest Customer with physical quote"/> + <description value="One can use Persistent Data for Guest Customer with physical quote"/> + <severity value="MAJOR"/> + <testCaseId value="MC-13479"/> + <group value="checkout"/> + </annotations> + <before> + <createData entity="SimpleProduct2" stepKey="createProduct"> + <field key="price">10</field> + </createData> + <createData entity="FreeShippinMethodConfig" stepKey="enableFreeShipping"/> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStorefront"/> + <executeJS function="window.localStorage.clear();" stepKey="clearLocalStorage"/> + </before> + <after> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShipping"/> + </after> + <!-- 1. Add simple product to cart and go to checkout--> + <actionGroup ref="AddSimpleProductToCart" stepKey="addSimpleProductToCart"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <!-- 2. Go to Shopping Cart --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckoutCartIndexPage"/> + <!-- 3. Open "Estimate Shipping and Tax" section and input data --> + <actionGroup ref="StorefrontCartEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxSection"/> + <actionGroup ref="StorefrontAssertShippingMethodPresentInCartActionGroup" stepKey="assertShippingMethodFlatRateIsPresentInCart"> + <argument name="shippingMethod" value="Flat Rate"/> + </actionGroup> + <actionGroup ref="StorefrontAssertShippingMethodPresentInCartActionGroup" stepKey="assertShippingMethodFreeShippingIsPresentInCart"> + <argument name="shippingMethod" value="Free Shipping"/> + </actionGroup> + <!-- 4. Select Flat Rate as shipping --> + <checkOption selector="{{CheckoutCartSummarySection.flatRateShippingMethod}}" stepKey="selectFlatRateShippingMethod"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappearAfterFlatRateSelection"/> + <see selector="{{CheckoutCartSummarySection.total}}" userInput="15" stepKey="assertOrderTotalField"/> + <!-- 5. Refresh browser page (F5) --> + <reloadPage stepKey="reloadPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="StorefrontAssertCartEstimateShippingAndTaxActionGroup" stepKey="assertCartEstimateShippingAndTaxAfterPageReload"/> + <actionGroup ref="StorefrontAssertCartShippingMethodSelectedActionGroup" stepKey="assertFlatRateShippingMethodIsChecked"> + <argument name="carrierCode" value="flatrate"/> + <argument name="methodCode" value="flatrate"/> + </actionGroup> + <!-- 6. Go to Checkout --> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> + <actionGroup ref="StorefrontAssertCheckoutEstimateShippingInformationActionGroup" stepKey="assertCheckoutEstimateShippingInformationAfterGoingToCheckout"/> + <actionGroup ref="StorefrontAssertCheckoutShippingMethodSelectedActionGroup" stepKey="assertFlatRateShippingMethodIsCheckedAfterGoingToCheckout"> + <argument name="shippingMethod" value="Flat Rate"/> + </actionGroup> + <!-- 7. Change persisted data --> + <selectOption selector="{{CheckoutShippingGuestInfoSection.country}}" userInput="United Kingdom" stepKey="changeCountryField"/> + <fillField selector="{{CheckoutShippingGuestInfoSection.regionInput}}" userInput="" stepKey="changeStateProvinceField"/> + <fillField selector="{{CheckoutShippingGuestInfoSection.postcode}}" userInput="KW1 7NQ" stepKey="changeZipPostalCodeField"/> + <!-- 8. Change shipping rate, select Free Shipping --> + <checkOption selector="{{CheckoutShippingMethodsSection.checkShippingMethodByName('Free Shipping')}}" stepKey="checkFreeShippingAsShippingMethod"/> + <!-- 9. Fill other fields --> + <actionGroup ref="StorefrontFillGuestShippingInfoActionGroup" stepKey="fillOtherFieldsInCheckoutShippingSection"/> + <!-- 10. Refresh browser page(F5) --> + <reloadPage stepKey="reloadCheckoutPage"/> + <waitForPageLoad stepKey="waitForCheckoutPageLoad"/> + <actionGroup ref="StorefrontAssertGuestShippingInfoActionGroup" stepKey="assertGuestShippingPersistedInfoAfterReloadingCheckoutShippingPage"/> + <actionGroup ref="StorefrontAssertCheckoutShippingMethodSelectedActionGroup" stepKey="assertFreeShippingShippingMethodIsChecked"> + <argument name="shippingMethod" value="Free Shipping"/> + </actionGroup> + <!-- 11. Go back to the shopping cart --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckoutCartIndexPage1"/> + <actionGroup ref="StorefrontAssertCartEstimateShippingAndTaxActionGroup" stepKey="assertCartEstimateShippingAndTaxAfterGoingBackToShoppingCart"> + <argument name="customerData" value="Simple_UK_Customer_For_Shipment"/> + </actionGroup> + <actionGroup ref="StorefrontAssertCartShippingMethodSelectedActionGroup" stepKey="assertFreeShippingShippingMethodIsCheckedAfterGoingBackToShoppingCart"> + <argument name="carrierCode" value="freeshipping"/> + <argument name="methodCode" value="freeshipping"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontValidateEmailOnCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontValidateEmailOnCheckoutTest.xml new file mode 100644 index 0000000000000..1b27e1d53adad --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontValidateEmailOnCheckoutTest.xml @@ -0,0 +1,54 @@ +<?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="StorefrontValidateEmailOnCheckoutTest"> + <annotations> + <features value="Checkout"/> + <title value="Email validation for Guest on checkout flow"/> + <description value="Email validation for Guest on checkout flow"/> + <stories value="Guest Checkout"/> + <testCaseId value="MC-14695" /> + <group value="checkout"/> + <group value="shoppingCart"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData entity="SimpleTwo" stepKey="simpleProduct"/> + </before> + + <after> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + </after> + + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductStorefront"> + <argument name="productUrl" value="$$simpleProduct.custom_attributes[url_key]$$" /> + </actionGroup> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage" /> + + <actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="openCheckoutPage" /> + <actionGroup ref="AssertStorefrontEmailTooltipContentOnCheckoutActionGroup" stepKey="assertEmailTooltipContent" /> + <actionGroup ref="AssertStorefrontEmailNoteMessageOnCheckoutActionGroup" stepKey="assertEmailNoteMessage" /> + + <actionGroup ref="StorefrontFillEmailFieldOnCheckoutActionGroup" stepKey="fillIncorrectEmailFirstAttempt"> + <argument name="email" value="John" /> + </actionGroup> + <actionGroup ref="AssertStorefrontEmailValidationMessageOnCheckoutActionGroup" stepKey="verifyValidationErrorMessageFirstAttempt" /> + + <actionGroup ref="StorefrontFillEmailFieldOnCheckoutActionGroup" stepKey="fillIncorrectEmailSecondAttempt"> + <argument name="email" value="johndoe#example.com" /> + </actionGroup> + <actionGroup ref="AssertStorefrontEmailValidationMessageOnCheckoutActionGroup" stepKey="verifyValidationErrorMessageSecondAttempt" /> + + <actionGroup ref="StorefrontFillEmailFieldOnCheckoutActionGroup" stepKey="fillIncorrectEmailThirdAttempt"> + <argument name="email" value="johndoe@example.c" /> + </actionGroup> + <actionGroup ref="AssertStorefrontEmailValidationMessageOnCheckoutActionGroup" stepKey="verifyValidationErrorMessageThirdAttempt" /> + </test> +</tests> diff --git a/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml b/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml index 64b70e80bd84f..a305413bcf1f3 100644 --- a/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml +++ b/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml @@ -105,7 +105,7 @@ <item name="trigger" xsi:type="string">opc-new-shipping-address</item> <item name="buttons" xsi:type="array"> <item name="save" xsi:type="array"> - <item name="text" xsi:type="string" translate="true">Save Address</item> + <item name="text" xsi:type="string" translate="true">Ship here</item> <item name="class" xsi:type="string">action primary action-save-address</item> </item> <item name="cancel" xsi:type="array"> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml index 454031279d882..d15794fb761bb 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml @@ -84,20 +84,20 @@ $canApplyMsrp = $helper->isShowBeforeOrderConfirm($product) && $helper->isMinima <?php endif; ?> <td class="col qty" data-th="<?= $block->escapeHtml(__('Qty')) ?>"> <div class="field qty"> - <label class="label" for="cart-<?= /* @escapeNotVerified */ $_item->getId() ?>-qty"> - <span><?= /* @escapeNotVerified */ __('Qty') ?></span> - </label> <div class="control qty"> - <input id="cart-<?= /* @escapeNotVerified */ $_item->getId() ?>-qty" - name="cart[<?= /* @escapeNotVerified */ $_item->getId() ?>][qty]" - data-cart-item-id="<?= $block->escapeHtml($_item->getSku()) ?>" - value="<?= /* @escapeNotVerified */ $block->getQty() ?>" - type="number" - size="4" - title="<?= $block->escapeHtml(__('Qty')) ?>" - class="input-text qty" - data-validate="{required:true,'validate-greater-than-zero':true}" - data-role="cart-item-qty"/> + <label for="cart-<?= /* @escapeNotVerified */ $_item->getId() ?>-qty"> + <span class="label"><?= /* @escapeNotVerified */ __('Qty') ?></span> + <input id="cart-<?= /* @escapeNotVerified */ $_item->getId() ?>-qty" + name="cart[<?= /* @escapeNotVerified */ $_item->getId() ?>][qty]" + data-cart-item-id="<?= $block->escapeHtml($_item->getSku()) ?>" + value="<?= /* @escapeNotVerified */ $block->getQty() ?>" + type="number" + size="4" + title="<?= $block->escapeHtml(__('Qty')) ?>" + class="input-text qty" + data-validate="{required:true,'validate-greater-than-zero':true}" + data-role="cart-item-qty"/> + </label> </div> </div> </td> diff --git a/app/code/Magento/Checkout/view/frontend/web/js/action/create-billing-address.js b/app/code/Magento/Checkout/view/frontend/web/js/action/create-billing-address.js index 7db0dc5ce7473..c601bb8acf125 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/action/create-billing-address.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/action/create-billing-address.js @@ -12,6 +12,17 @@ define([ 'use strict'; return function (addressData) { - return addressConverter.formAddressDataToQuoteAddress(addressData); + var address = addressConverter.formAddressDataToQuoteAddress(addressData); + + /** + * Returns new customer billing address type. + * + * @returns {String} + */ + address.getType = function () { + return 'new-customer-billing-address'; + }; + + return address; }; }); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/checkout-data-resolver.js b/app/code/Magento/Checkout/view/frontend/web/js/model/checkout-data-resolver.js index 9cc60a3645d58..bc0ab59b622a2 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/checkout-data-resolver.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/checkout-data-resolver.js @@ -216,11 +216,11 @@ define([ newCustomerBillingAddressData = checkoutData.getNewCustomerBillingAddress(); if (selectedBillingAddress) { - if (selectedBillingAddress == 'new-customer-address' && newCustomerBillingAddressData) { //eslint-disable-line + if (selectedBillingAddress === 'new-customer-billing-address' && newCustomerBillingAddressData) { selectBillingAddress(createBillingAddress(newCustomerBillingAddressData)); } else { addressList.some(function (address) { - if (selectedBillingAddress == address.getKey()) { //eslint-disable-line eqeqeq + if (selectedBillingAddress === address.getKey()) { selectBillingAddress(address); } }); @@ -243,7 +243,7 @@ define([ return; } - if (quote.isVirtual()) { + if (quote.isVirtual() || !quote.billingAddress()) { isBillingAddressInitialized = addressList.some(function (addrs) { if (addrs.isDefaultBilling()) { selectBillingAddress(addrs); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js index d68b0682eb511..a552aa01da061 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js @@ -40,30 +40,23 @@ function ( 'use strict'; var lastSelectedBillingAddress = null, - newAddressOption = { - /** - * Get new address label - * @returns {String} - */ - getAddressInline: function () { - return $t('New Address'); - }, - customerAddressId: null - }, countryData = customerData.get('directory-data'), addressOptions = addressList().filter(function (address) { - return address.getType() == 'customer-address'; //eslint-disable-line eqeqeq + return address.getType() === 'customer-address'; }); - addressOptions.push(newAddressOption); - return Component.extend({ defaults: { - template: 'Magento_Checkout/billing-address' + template: 'Magento_Checkout/billing-address', + actionsTemplate: 'Magento_Checkout/billing-address/actions', + formTemplate: 'Magento_Checkout/billing-address/form', + detailsTemplate: 'Magento_Checkout/billing-address/details', + links: { + isAddressFormVisible: '${$.billingAddressListProvider}:isNewAddressSelected' + } }, currentBillingAddress: quote.billingAddress, - addressOptions: addressOptions, - customerHasAddresses: addressOptions.length > 1, + customerHasAddresses: addressOptions.length > 0, /** * Init component @@ -84,7 +77,7 @@ function ( .observe({ selectedAddress: null, isAddressDetailsVisible: quote.billingAddress() != null, - isAddressFormVisible: !customer.isLoggedIn() || addressOptions.length === 1, + isAddressFormVisible: !customer.isLoggedIn() || !addressOptions.length, isAddressSameAsShipping: false, saveInAddressBook: 1 }); @@ -147,7 +140,7 @@ function ( updateAddress: function () { var addressData, newBillingAddress; - if (this.selectedAddress() && this.selectedAddress() != newAddressOption) { //eslint-disable-line eqeqeq + if (this.selectedAddress() && !this.isAddressFormVisible()) { selectBillingAddress(this.selectedAddress()); checkoutData.setSelectedBillingAddress(this.selectedAddress().getKey()); } else { @@ -218,13 +211,6 @@ function ( } }, - /** - * @param {Object} address - */ - onAddressChange: function (address) { - this.isAddressFormVisible(address == newAddressOption); //eslint-disable-line eqeqeq - }, - /** * @param {Number} countryId * @return {*} diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address/list.js b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address/list.js new file mode 100644 index 0000000000000..ca3a267c01671 --- /dev/null +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address/list.js @@ -0,0 +1,77 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'uiComponent', + 'Magento_Customer/js/model/address-list', + 'mage/translate', + 'Magento_Customer/js/model/customer' +], function (Component, addressList, $t, customer) { + 'use strict'; + + var newAddressOption = { + /** + * Get new address label + * @returns {String} + */ + getAddressInline: function () { + return $t('New Address'); + }, + customerAddressId: null + }, + addressOptions = addressList().filter(function (address) { + return address.getType() === 'customer-address'; + }); + + return Component.extend({ + defaults: { + template: 'Magento_Checkout/billing-address', + selectedAddress: null, + isNewAddressSelected: false, + addressOptions: addressOptions, + exports: { + selectedAddress: '${ $.parentName }:selectedAddress' + } + }, + + /** + * @returns {Object} Chainable. + */ + initConfig: function () { + this._super(); + this.addressOptions.push(newAddressOption); + + return this; + }, + + /** + * @return {exports.initObservable} + */ + initObservable: function () { + this._super() + .observe('selectedAddress isNewAddressSelected') + .observe({ + isNewAddressSelected: !customer.isLoggedIn() || !addressOptions.length + }); + + return this; + }, + + /** + * @param {Object} address + * @return {*} + */ + addressOptionsText: function (address) { + return address.getAddressInline(); + }, + + /** + * @param {Object} address + */ + onAddressChange: function (address) { + this.isNewAddressSelected(address === newAddressOption); + } + }); +}); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/payment.js b/app/code/Magento/Checkout/view/frontend/web/js/view/payment.js index c17e5e40d5c98..e8994c61b7221 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/payment.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/payment.js @@ -66,9 +66,21 @@ define([ navigate: function () { var self = this; - getPaymentInformation().done(function () { - self.isVisible(true); - }); + if (!self.hasShippingMethod()) { + this.isVisible(false); + stepNavigator.setHash('shipping'); + } else { + getPaymentInformation().done(function () { + self.isVisible(true); + }); + } + }, + + /** + * @return {Boolean} + */ + hasShippingMethod: function () { + return window.checkoutConfig.selectedShippingMethod !== null; }, /** diff --git a/app/code/Magento/Checkout/view/frontend/web/template/authentication.html b/app/code/Magento/Checkout/view/frontend/web/template/authentication.html index 406a7d899b67a..5b8dde81dd93e 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/authentication.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/authentication.html @@ -31,15 +31,15 @@ <div class="block block-customer-login" data-bind="attr: {'data-label': $t('or')}"> <div class="block-title"> - <strong id="block-customer-login-heading" - role="heading" - aria-level="2" - data-bind="i18n: 'Sign In'"></strong> + <strong id="block-customer-login-heading-checkout" + role="heading" + aria-level="2" + data-bind="i18n: 'Sign In'"></strong> </div> <!-- ko foreach: getRegion('messages') --> <!-- ko template: getTemplate() --><!-- /ko --> <!--/ko--> - <div class="block-content" aria-labelledby="block-customer-login-heading"> + <div class="block-content" aria-labelledby="block-customer-login-heading-checkout"> <form data-role="login" data-bind="submit:login" method="post"> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/billing-address.html b/app/code/Magento/Checkout/view/frontend/web/template/billing-address.html index 63edb5057b933..cabfcc9b3db03 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/billing-address.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/billing-address.html @@ -5,28 +5,18 @@ */ --> <div class="checkout-billing-address"> - <div class="billing-address-same-as-shipping-block field choice" data-bind="visible: canUseShippingAddress()"> <input type="checkbox" name="billing-address-same-as-shipping" data-bind="checked: isAddressSameAsShipping, click: useShippingAddress, attr: {id: 'billing-address-same-as-shipping-' + getCode($parent)}"/> <label data-bind="attr: {for: 'billing-address-same-as-shipping-' + getCode($parent)}"><span data-bind="i18n: 'My billing and shipping address are the same'"></span></label> </div> - - <!-- ko template: 'Magento_Checkout/billing-address/details' --><!-- /ko --> + <render args="detailsTemplate"/> <fieldset class="fieldset" data-bind="visible: !isAddressDetailsVisible()"> - <!-- ko template: 'Magento_Checkout/billing-address/list' --><!-- /ko --> - <!-- ko template: 'Magento_Checkout/billing-address/form' --><!-- /ko --> - <div class="actions-toolbar"> - <div class="primary"> - <button class="action action-update" type="button" data-bind="click: updateAddress"> - <span data-bind="i18n: 'Update'"></span> - </button> - <button class="action action-cancel" type="button" data-bind="click: cancelAddressEdit, visible: canUseCancelBillingAddress()"> - <span data-bind="i18n: 'Cancel'"></span> - </button> - </div> + <each args="getRegion('billing-address-list')" render="" /> + <div data-bind="fadeVisible: isAddressFormVisible"> + <render args="formTemplate"/> </div> + <render args="actionsTemplate"/> </fieldset> - </div> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/actions.html b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/actions.html new file mode 100644 index 0000000000000..860f340d3f7ca --- /dev/null +++ b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/actions.html @@ -0,0 +1,21 @@ +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<div class="actions-toolbar"> + <div class="primary"> + <button class="action action-update" + type="button" + click="updateAddress"> + <span translate="'Update'"/> + </button> + <button class="action action-cancel" + type="button" + click="cancelAddressEdit" + visible="canUseCancelBillingAddress()"> + <span translate="'Cancel'"/> + </button> + </div> +</div> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/form.html b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/form.html index 54fe9a1f59394..e29ed99d17be1 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/form.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/form.html @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ --> -<div class="billing-address-form" data-bind="fadeVisible: isAddressFormVisible"> +<div class="billing-address-form"> <!-- ko foreach: getRegion('before-fields') --> <!-- ko template: getTemplate() --><!-- /ko --> <!--/ko--> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/form/element/email.html b/app/code/Magento/Checkout/view/frontend/web/template/form/element/email.html index 8d6142e07fcf0..6a784fa7a04c4 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/form/element/email.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/form/element/email.html @@ -14,7 +14,7 @@ method="post"> <fieldset id="customer-email-fieldset" class="fieldset" data-bind="blockLoader: isLoading"> <div class="field required"> - <label class="label" for="customer-email"> + <label class="label" for="checkout-customer-email"> <span data-bind="i18n: 'Email Address'"></span> </label> <div class="control _with-tooltip"> @@ -26,7 +26,7 @@ mageInit: {'mage/trim-input':{}}" name="username" data-validate="{required:true, 'validate-email':true}" - id="customer-email" /> + id="checkout-customer-email" /> <!-- ko template: 'ui/form/element/helper/tooltip' --><!-- /ko --> <span class="note" data-bind="fadeVisible: isPasswordVisible() == false"><!-- ko i18n: 'You can create an account after checkout.'--><!-- /ko --></span> </div> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping.html index a1a5aa67a9688..1fcfa4b3b1343 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/shipping.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping.html @@ -16,12 +16,14 @@ <!-- Address form pop up --> <if args="!isFormInline"> - <button type="button" - class="action action-show-popup" - click="showFormPopUp" - visible="!isNewAddressAdded()"> - <span translate="'New Address'" /> - </button> + <div class="new-address-popup"> + <button type="button" + class="action action-show-popup" + click="showFormPopUp" + visible="!isNewAddressAdded()"> + <span translate="'New Address'" /> + </button> + </div> <div id="opc-new-shipping-address" visible="isFormPopUpVisible()" render="shippingFormTemplate" /> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/summary/cart-items.html b/app/code/Magento/Checkout/view/frontend/web/template/summary/cart-items.html index 34ec91aa43c72..fc74a4691a2e7 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/summary/cart-items.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/summary/cart-items.html @@ -6,7 +6,7 @@ --> <div class="block items-in-cart" data-bind="mageInit: {'collapsible':{'openedState': 'active', 'active': isItemsBlockExpanded()}}"> <div class="title" data-role="title"> - <strong role="heading"> + <strong role="heading" aria-level="1"> <translate args="maxCartItemsToDisplay" if="maxCartItemsToDisplay < getCartLineItemsCount()"/> <translate args="'of'" if="maxCartItemsToDisplay < getCartLineItemsCount()"/> <span data-bind="text: getCartLineItemsCount()"></span> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Data/AdminModuleData.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Data/AdminModuleData.xml new file mode 100644 index 0000000000000..d42c2c8139425 --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Data/AdminModuleData.xml @@ -0,0 +1,16 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuStoresSettingsTermsAndConditions"> + <data key="pageTitle">Terms and Conditions</data> + <data key="title">Terms and Conditions</data> + <data key="dataUiId">magento-checkoutagreements-sales-checkoutagreement</data> + </entity> +</entities> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminStoresTermsAndConditionsNavigateMenuTest.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminStoresTermsAndConditionsNavigateMenuTest.xml new file mode 100644 index 0000000000000..d2d4cb9138bd5 --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminStoresTermsAndConditionsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminStoresTermsAndConditionsNavigateMenuTest"> + <annotations> + <features value="CheckoutAgreements"/> + <stories value="Menu Navigation"/> + <title value="Admin stores terms and conditions navigate menu test"/> + <description value="Admin should be able to navigate to Stores > Terms and Conditions"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14148"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToStoresTermsAndConditionsPage"> + <argument name="menuUiId" value="{{AdminMenuStores.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuStoresSettingsTermsAndConditions.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuStoresSettingsTermsAndConditions.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Cms/Block/Block.php b/app/code/Magento/Cms/Block/Block.php index d0d75ea691195..c611f4b1e9f05 100644 --- a/app/code/Magento/Cms/Block/Block.php +++ b/app/code/Magento/Cms/Block/Block.php @@ -84,4 +84,14 @@ public function getIdentities() { return [\Magento\Cms\Model\Block::CACHE_TAG . '_' . $this->getBlockId()]; } + + /** + * @inheritdoc + */ + public function getCacheKeyInfo() + { + $cacheKeyInfo = parent::getCacheKeyInfo(); + $cacheKeyInfo[] = $this->_storeManager->getStore()->getId(); + return $cacheKeyInfo; + } } diff --git a/app/code/Magento/Cms/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Cms/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..3e227df56c909 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,26 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuContent"> + <data key="pageTitle">Content</data> + <data key="title">Content</data> + <data key="dataUiId">magento-backend-content</data> + </entity> + <entity name="AdminMenuContentElementsPages"> + <data key="pageTitle">Pages</data> + <data key="title">Pages</data> + <data key="dataUiId">magento-cms-cms-page</data> + </entity> + <entity name="AdminMenuContentElementsBlocks"> + <data key="pageTitle">Blocks</data> + <data key="title">Blocks</data> + <data key="dataUiId">magento-cms-cms-block</data> + </entity> +</entities> diff --git a/app/code/Magento/Cms/Test/Mftf/Data/CmsPageData.xml b/app/code/Magento/Cms/Test/Mftf/Data/CmsPageData.xml index 2ec2eccba2344..2f8efac37cecf 100644 --- a/app/code/Magento/Cms/Test/Mftf/Data/CmsPageData.xml +++ b/app/code/Magento/Cms/Test/Mftf/Data/CmsPageData.xml @@ -50,6 +50,7 @@ <data key="file_type">Upload File</data> <data key="shareable">Yes</data> <data key="file">magento-again.jpg</data> + <data key="fileName">magento-again</data> <data key="value">magento-again.jpg</data> <data key="content">Image content. Yeah.</data> <data key="height">1000</data> @@ -71,6 +72,7 @@ <data key="file_type">Upload File</data> <data key="shareable">Yes</data> <data key="value">magento3.jpg</data> + <data key="file">magento3.jpg</data> <data key="fileName">magento3</data> <data key="extension">jpg</data> <data key="content">Image content. Yeah.</data> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminContentBlocksNavigateMenuTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminContentBlocksNavigateMenuTest.xml new file mode 100644 index 0000000000000..19f501d6aa209 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminContentBlocksNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminContentBlocksNavigateMenuTest"> + <annotations> + <features value="Cms"/> + <stories value="Menu Navigation"/> + <title value="Admin content blocks navigate menu test"/> + <description value="Admin should be able to navigate to Content > Blocks"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14129"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToContentBlocksPage"> + <argument name="menuUiId" value="{{AdminMenuContent.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuContentElementsBlocks.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuContentElementsBlocks.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminContentPagesNavigateMenuTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminContentPagesNavigateMenuTest.xml new file mode 100644 index 0000000000000..323a1de7b9a4e --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminContentPagesNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminContentPagesNavigateMenuTest"> + <annotations> + <features value="Cms"/> + <stories value="Menu Navigation"/> + <title value="Admin content pages navigate menu test"/> + <description value="Admin should be able to navigate to Content > Pages"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14128"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToContentPagesPage"> + <argument name="menuUiId" value="{{AdminMenuContent.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuContentElementsPages.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuContentElementsPages.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CmsGraphQl/Model/Resolver/Block/Identity.php b/app/code/Magento/CmsGraphQl/Model/Resolver/Block/Identity.php new file mode 100644 index 0000000000000..a40d23968c3c6 --- /dev/null +++ b/app/code/Magento/CmsGraphQl/Model/Resolver/Block/Identity.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CmsGraphQl\Model\Resolver\Block; + +use Magento\Cms\Api\Data\BlockInterface; +use Magento\Framework\GraphQl\Query\Resolver\IdentityInterface; + +/** + * Identity for resolved CMS block + */ +class Identity implements IdentityInterface +{ + /** + * Get block identities from resolved data + * + * @param array $resolvedData + * @return string[] + */ + public function getIdentities(array $resolvedData): array + { + $ids = []; + $items = $resolvedData['items'] ?? []; + foreach ($items as $item) { + if (is_array($item) && !empty($item[BlockInterface::BLOCK_ID])) { + $ids[] = $item[BlockInterface::BLOCK_ID]; + $ids[] = $item[BlockInterface::IDENTIFIER]; + } + } + + return $ids; + } +} diff --git a/app/code/Magento/CmsGraphQl/Model/Resolver/DataProvider/Block.php b/app/code/Magento/CmsGraphQl/Model/Resolver/DataProvider/Block.php index 47a2439c4fad0..fa4944381b858 100644 --- a/app/code/Magento/CmsGraphQl/Model/Resolver/DataProvider/Block.php +++ b/app/code/Magento/CmsGraphQl/Model/Resolver/DataProvider/Block.php @@ -59,6 +59,7 @@ public function getData(string $blockIdentifier): array $renderedContent = $this->widgetFilter->filter($block->getContent()); $blockData = [ + BlockInterface::BLOCK_ID => $block->getId(), BlockInterface::IDENTIFIER => $block->getIdentifier(), BlockInterface::TITLE => $block->getTitle(), BlockInterface::CONTENT => $renderedContent, diff --git a/app/code/Magento/CmsGraphQl/Model/Resolver/DataProvider/Page.php b/app/code/Magento/CmsGraphQl/Model/Resolver/DataProvider/Page.php index e943ba0c2fd5e..2cee9e8e9e44c 100644 --- a/app/code/Magento/CmsGraphQl/Model/Resolver/DataProvider/Page.php +++ b/app/code/Magento/CmsGraphQl/Model/Resolver/DataProvider/Page.php @@ -43,6 +43,8 @@ public function __construct( } /** + * Get the page data + * * @param int $pageId * @return array * @throws NoSuchEntityException @@ -58,6 +60,7 @@ public function getData(int $pageId): array $renderedContent = $this->widgetFilter->filter($page->getContent()); $pageData = [ + PageInterface::PAGE_ID => $page->getId(), 'url_key' => $page->getIdentifier(), PageInterface::TITLE => $page->getTitle(), PageInterface::CONTENT => $renderedContent, diff --git a/app/code/Magento/CmsGraphQl/Model/Resolver/Page/Identity.php b/app/code/Magento/CmsGraphQl/Model/Resolver/Page/Identity.php new file mode 100644 index 0000000000000..abc306451e309 --- /dev/null +++ b/app/code/Magento/CmsGraphQl/Model/Resolver/Page/Identity.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CmsGraphQl\Model\Resolver\Page; + +use Magento\Cms\Api\Data\PageInterface; +use Magento\Framework\GraphQl\Query\Resolver\IdentityInterface; + +/** + * Identity for resolved CMS page + */ +class Identity implements IdentityInterface +{ + /** + * Get page ID from resolved data + * + * @param array $resolvedData + * @return string[] + */ + public function getIdentities(array $resolvedData): array + { + return empty($resolvedData[PageInterface::PAGE_ID]) ? [] : [$resolvedData[PageInterface::PAGE_ID]]; + } +} diff --git a/app/code/Magento/CmsGraphQl/composer.json b/app/code/Magento/CmsGraphQl/composer.json index bea7ee1356d80..e0e8481d59b7b 100644 --- a/app/code/Magento/CmsGraphQl/composer.json +++ b/app/code/Magento/CmsGraphQl/composer.json @@ -11,6 +11,7 @@ }, "suggest": { "magento/module-graph-ql": "*", + "magento/module-graph-ql-cache": "*", "magento/module-store-graph-ql": "*" }, "license": [ diff --git a/app/code/Magento/CmsGraphQl/etc/schema.graphqls b/app/code/Magento/CmsGraphQl/etc/schema.graphqls index 85bff91dea8a2..26689c5d4c91d 100644 --- a/app/code/Magento/CmsGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CmsGraphQl/etc/schema.graphqls @@ -14,10 +14,10 @@ type Query { cmsPage ( id: Int @doc(description: "Id of the CMS page") @deprecated(reason: "Use `identifier`") @doc(description: "The CMS page query returns information about a CMS page") identifier: String @doc(description: "Identifier of the CMS page") - ): CmsPage @resolver(class: "Magento\\CmsGraphQl\\Model\\Resolver\\Page") @doc(description: "The CMS page query returns information about a CMS page") + ): CmsPage @resolver(class: "Magento\\CmsGraphQl\\Model\\Resolver\\Page") @doc(description: "The CMS page query returns information about a CMS page") @cache(cacheTag: "cms_p", cacheIdentity: "Magento\\CmsGraphQl\\Model\\Resolver\\Page\\Identity") cmsBlocks ( identifiers: [String] @doc(description: "Identifiers of the CMS blocks") - ): CmsBlocks @resolver(class: "Magento\\CmsGraphQl\\Model\\Resolver\\Blocks") @doc(description: "The CMS block query returns information about CMS blocks") + ): CmsBlocks @resolver(class: "Magento\\CmsGraphQl\\Model\\Resolver\\Blocks") @doc(description: "The CMS block query returns information about CMS blocks") @cache(cacheTag: "cms_b", cacheIdentity: "Magento\\CmsGraphQl\\Model\\Resolver\\Block\\Identity") } type CmsPage @doc(description: "CMS page defines all CMS page information") { diff --git a/app/code/Magento/Config/Model/Config.php b/app/code/Magento/Config/Model/Config.php index bd38d1451e1b6..b5dbf97f7953d 100644 --- a/app/code/Magento/Config/Model/Config.php +++ b/app/code/Magento/Config/Model/Config.php @@ -115,7 +115,7 @@ class Config extends \Magento\Framework\DataObject private $scopeTypeNormalizer; /** - * @var \Magento\MessageQueue\Api\PoisonPillPutInterface + * @var \Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface */ private $pillPut; @@ -131,7 +131,7 @@ class Config extends \Magento\Framework\DataObject * @param array $data * @param ScopeResolverPool|null $scopeResolverPool * @param ScopeTypeNormalizer|null $scopeTypeNormalizer - * @param \Magento\MessageQueue\Api\PoisonPillPutInterface|null $pillPut + * @param \Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface|null $pillPut * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -146,7 +146,7 @@ public function __construct( array $data = [], ScopeResolverPool $scopeResolverPool = null, ScopeTypeNormalizer $scopeTypeNormalizer = null, - \Magento\MessageQueue\Api\PoisonPillPutInterface $pillPut = null + \Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface $pillPut = null ) { parent::__construct($data); $this->_eventManager = $eventManager; @@ -163,7 +163,7 @@ public function __construct( $this->scopeTypeNormalizer = $scopeTypeNormalizer ?? ObjectManager::getInstance()->get(ScopeTypeNormalizer::class); $this->pillPut = $pillPut ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\MessageQueue\Api\PoisonPillPutInterface::class); + ->get(\Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface::class); } /** diff --git a/app/code/Magento/Config/Model/Config/Backend/Admin/Usecustom.php b/app/code/Magento/Config/Model/Config/Backend/Admin/Usecustom.php index 9a483de6a695b..f5d568f2f36be 100644 --- a/app/code/Magento/Config/Model/Config/Backend/Admin/Usecustom.php +++ b/app/code/Magento/Config/Model/Config/Backend/Admin/Usecustom.php @@ -10,6 +10,8 @@ namespace Magento\Config\Model\Config\Backend\Admin; /** + * Process custom admin url during configuration value save process. + * * @api * @since 100.0.2 */ @@ -56,8 +58,9 @@ public function beforeSave() { $value = $this->getValue(); if ($value == 1) { - $customUrl = $this->getData('groups/url/fields/custom/value'); - if (empty($customUrl)) { + $customUrlField = $this->getData('groups/url/fields/custom/value'); + $customUrlConfig = $this->_config->getValue('admin/url/custom'); + if (empty($customUrlField) && empty($customUrlConfig)) { throw new \Magento\Framework\Exception\LocalizedException(__('Please specify the admin custom URL.')); } } diff --git a/app/code/Magento/Config/Model/Config/Backend/Encrypted.php b/app/code/Magento/Config/Model/Config/Backend/Encrypted.php index 1a91e403a679d..ea3b1d4c74a5f 100644 --- a/app/code/Magento/Config/Model/Config/Backend/Encrypted.php +++ b/app/code/Magento/Config/Model/Config/Backend/Encrypted.php @@ -9,6 +9,8 @@ namespace Magento\Config\Model\Config\Backend; /** + * Backend model for encrypted values. + * * @api * @since 100.0.2 */ @@ -48,9 +50,14 @@ public function __construct( * Magic method called during class serialization * * @return string[] + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $properties = parent::__sleep(); return array_diff($properties, ['_encryptor']); } @@ -59,9 +66,14 @@ public function __sleep() * Magic method called during class un-serialization * * @return void + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + parent::__wakeup(); $this->_encryptor = \Magento\Framework\App\ObjectManager::getInstance()->get( \Magento\Framework\Encryption\EncryptorInterface::class diff --git a/app/code/Magento/Config/Model/Config/Backend/Serialized.php b/app/code/Magento/Config/Model/Config/Backend/Serialized.php index 3d5713357c39c..6e0b6275db836 100644 --- a/app/code/Magento/Config/Model/Config/Backend/Serialized.php +++ b/app/code/Magento/Config/Model/Config/Backend/Serialized.php @@ -9,6 +9,8 @@ use Magento\Framework\Serialize\Serializer\Json; /** + * Serialized backend model + * * @api * @since 100.0.2 */ @@ -46,17 +48,32 @@ public function __construct( } /** + * Processing object after load data + * * @return void */ protected function _afterLoad() { $value = $this->getValue(); if (!is_array($value)) { - $this->setValue(empty($value) ? false : $this->serializer->unserialize($value)); + try { + $this->setValue(empty($value) ? false : $this->serializer->unserialize($value)); + } catch (\Exception $e) { + $this->_logger->critical( + sprintf( + 'Failed to unserialize %s config value. The error is: %s', + $this->getPath(), + $e->getMessage() + ) + ); + $this->setValue(false); + } } } /** + * Processing object before save data + * * @return $this */ public function beforeSave() diff --git a/app/code/Magento/Config/Model/Config/Source/Locale/Currency.php b/app/code/Magento/Config/Model/Config/Source/Locale/Currency.php index b3474674cf76d..5beff0d043ade 100644 --- a/app/code/Magento/Config/Model/Config/Source/Locale/Currency.php +++ b/app/code/Magento/Config/Model/Config/Source/Locale/Currency.php @@ -4,12 +4,15 @@ * See COPYING.txt for license details. */ -/** - * Locale currency source - */ namespace Magento\Config\Model\Config\Source\Locale; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Locale\ListsInterface; + /** + * Locale currency source. + * * @api * @since 100.0.2 */ @@ -21,27 +24,70 @@ class Currency implements \Magento\Framework\Option\ArrayInterface protected $_options; /** - * @var \Magento\Framework\Locale\ListsInterface + * @var ListsInterface */ protected $_localeLists; /** - * @param \Magento\Framework\Locale\ListsInterface $localeLists + * @var ScopeConfigInterface */ - public function __construct(\Magento\Framework\Locale\ListsInterface $localeLists) - { + private $config; + + /** + * @var array + */ + private $installedCurrencies; + + /** + * @param ListsInterface $localeLists + * @param ScopeConfigInterface $config + */ + public function __construct( + ListsInterface $localeLists, + ScopeConfigInterface $config = null + ) { $this->_localeLists = $localeLists; + $this->config = $config ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); } /** - * @return array + * @inheritdoc */ public function toOptionArray() { if (!$this->_options) { $this->_options = $this->_localeLists->getOptionCurrencies(); } - $options = $this->_options; + + $selected = array_flip($this->getInstalledCurrencies()); + + $options = array_filter( + $this->_options, + function ($option) use ($selected) { + return isset($selected[$option['value']]); + } + ); + return $options; } + + /** + * Retrieve Installed Currencies. + * + * @return array + */ + private function getInstalledCurrencies() + { + if (!$this->installedCurrencies) { + $this->installedCurrencies = explode( + ',', + $this->config->getValue( + 'system/currency/installed', + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ) + ); + } + + return $this->installedCurrencies; + } } diff --git a/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml b/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml index 8a56c2777084e..b5bfe9cc2ea05 100644 --- a/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml +++ b/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml @@ -11,5 +11,7 @@ <element name="generalTab" type="text" selector="//div[@class='admin__page-nav-title title _collapsible']//strong[text()='General']"/> <element name="generalTabClosed" type="text" selector="//div[@class='admin__page-nav-title title _collapsible' and @aria-expanded='false' or @aria-expanded='0']//strong[text()='General']"/> <element name="generalTabOpened" type="text" selector="//div[@class='admin__page-nav-title title _collapsible' and @aria-expanded='true' or @aria-expanded='1']//strong[text()='General']"/> + <element name="defaultConfigButton" type="button" selector="#store-change-button" timeout="30"/> + <element name="defaultConfigDropdown" type="button" selector="//ul[@class='dropdown-menu']" timeout="30"/> </section> -</sections> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/Backend/SerializedTest.php b/app/code/Magento/Config/Test/Unit/Model/Config/Backend/SerializedTest.php index bb1e0e0225901..c2685e0a265cd 100644 --- a/app/code/Magento/Config/Test/Unit/Model/Config/Backend/SerializedTest.php +++ b/app/code/Magento/Config/Test/Unit/Model/Config/Backend/SerializedTest.php @@ -9,7 +9,11 @@ use Magento\Framework\Model\Context; use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Psr\Log\LoggerInterface; +/** + * Class SerializedTest + */ class SerializedTest extends \PHPUnit\Framework\TestCase { /** @var \Magento\Config\Model\Config\Backend\Serialized */ @@ -18,14 +22,20 @@ class SerializedTest extends \PHPUnit\Framework\TestCase /** @var Json|\PHPUnit_Framework_MockObject_MockObject */ private $serializerMock; + /** @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $loggerMock; + protected function setUp() { $objectManager = new ObjectManager($this); $this->serializerMock = $this->createMock(Json::class); + $this->loggerMock = $this->createMock(LoggerInterface::class); $contextMock = $this->createMock(Context::class); $eventManagerMock = $this->createMock(\Magento\Framework\Event\ManagerInterface::class); $contextMock->method('getEventDispatcher') ->willReturn($eventManagerMock); + $contextMock->method('getLogger') + ->willReturn($this->loggerMock); $this->serializedConfig = $objectManager->getObject( Serialized::class, [ @@ -72,6 +82,20 @@ public function afterLoadDataProvider() ]; } + public function testAfterLoadWithException() + { + $value = '{"key":'; + $expected = false; + $this->serializedConfig->setValue($value); + $this->serializerMock->expects($this->once()) + ->method('unserialize') + ->willThrowException(new \Exception()); + $this->loggerMock->expects($this->once()) + ->method('critical'); + $this->serializedConfig->afterLoad(); + $this->assertEquals($expected, $this->serializedConfig->getValue()); + } + /** * @param string $expected * @param int|double|string|array|boolean|null $value diff --git a/app/code/Magento/Config/composer.json b/app/code/Magento/Config/composer.json index 3312fb630ccda..57c067d2cae27 100644 --- a/app/code/Magento/Config/composer.json +++ b/app/code/Magento/Config/composer.json @@ -7,7 +7,6 @@ "require": { "php": "~7.1.3||~7.2.0", "magento/framework": "*", - "magento/module-message-queue": "*", "magento/module-backend": "*", "magento/module-cron": "*", "magento/module-deploy": "*", diff --git a/app/code/Magento/ConfigurableProduct/Model/LinkManagement.php b/app/code/Magento/ConfigurableProduct/Model/LinkManagement.php index 79c2dd812acf1..2f07f8b90ce7e 100644 --- a/app/code/Magento/ConfigurableProduct/Model/LinkManagement.php +++ b/app/code/Magento/ConfigurableProduct/Model/LinkManagement.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -11,6 +10,11 @@ use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Exception\StateException; +/** + * Configurable product link management. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class LinkManagement implements \Magento\ConfigurableProduct\Api\LinkManagementInterface { /** @@ -68,7 +72,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getChildren($sku) { @@ -107,11 +111,15 @@ public function getChildren($sku) } /** - * {@inheritdoc} + * @inheritdoc + * @throws InputException + * @throws NoSuchEntityException + * @throws StateException + * @throws \Magento\Framework\Exception\CouldNotSaveException */ public function addChild($sku, $childSku) { - $product = $this->productRepository->get($sku); + $product = $this->productRepository->get($sku, true); $child = $this->productRepository->get($childSku); $childrenIds = array_values($this->configurableType->getChildrenIds($product->getId())[0]); @@ -150,7 +158,11 @@ public function addChild($sku, $childSku) } /** - * {@inheritdoc} + * @inheritdoc + * @throws InputException + * @throws NoSuchEntityException + * @throws StateException + * @throws \Magento\Framework\Exception\CouldNotSaveException */ public function removeChild($sku, $childSku) { diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Attribute.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Attribute.php index 7306942c3c49b..4ead9ffe0fe70 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Attribute.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Attribute.php @@ -12,6 +12,8 @@ use Magento\Framework\EntityManager\MetadataPool; /** + * Configurable product attribute model. + * * @method Attribute setProductAttribute(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute $value) * @method \Magento\Eav\Model\Entity\Attribute\AbstractAttribute getProductAttribute() */ @@ -86,7 +88,7 @@ public function getOptions() } /** - * {@inheritdoc} + * @inheritdoc */ public function getLabel() { @@ -112,10 +114,10 @@ public function afterSave() } /** - * Load configurable attribute by product and product's attribute + * Load configurable attribute by product and product's attribute. * * @param \Magento\Catalog\Model\Product $product - * @param \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute + * @param \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute * @return void */ public function loadByProductAndAttribute($product, $attribute) @@ -144,7 +146,7 @@ public function deleteByProduct($product) } /** - * {@inheritdoc} + * @inheritdoc * @codeCoverageIgnore */ public function getAttributeId() @@ -153,7 +155,7 @@ public function getAttributeId() } /** - * {@inheritdoc} + * @inheritdoc * @codeCoverageIgnore */ public function getPosition() @@ -162,7 +164,7 @@ public function getPosition() } /** - * {@inheritdoc} + * @inheritdoc * @codeCoverageIgnore */ public function getIsUseDefault() @@ -171,7 +173,7 @@ public function getIsUseDefault() } /** - * {@inheritdoc} + * @inheritdoc * @codeCoverageIgnore */ public function getValues() @@ -182,8 +184,7 @@ public function getValues() //@codeCoverageIgnoreStart /** - * @param string $attributeId - * @return $this + * @inheritdoc */ public function setAttributeId($attributeId) { @@ -191,8 +192,7 @@ public function setAttributeId($attributeId) } /** - * @param string $label - * @return $this + * @inheritdoc */ public function setLabel($label) { @@ -200,8 +200,7 @@ public function setLabel($label) } /** - * @param int $position - * @return $this + * @inheritdoc */ public function setPosition($position) { @@ -209,8 +208,7 @@ public function setPosition($position) } /** - * @param bool $isUseDefault - * @return $this + * @inheritdoc */ public function setIsUseDefault($isUseDefault) { @@ -218,8 +216,7 @@ public function setIsUseDefault($isUseDefault) } /** - * @param \Magento\ConfigurableProduct\Api\Data\OptionValueInterface[] $values - * @return $this + * @inheritdoc */ public function setValues(array $values = null) { @@ -227,7 +224,7 @@ public function setValues(array $values = null) } /** - * {@inheritdoc} + * @inheritdoc * * @return \Magento\ConfigurableProduct\Api\Data\OptionExtensionInterface|null */ @@ -237,7 +234,7 @@ public function getExtensionAttributes() } /** - * {@inheritdoc} + * @inheritdoc * * @param \Magento\ConfigurableProduct\Api\Data\OptionExtensionInterface $extensionAttributes * @return $this @@ -249,7 +246,7 @@ public function setExtensionAttributes( } /** - * {@inheritdoc} + * @inheritdoc */ public function getProductId() { @@ -257,7 +254,7 @@ public function getProductId() } /** - * {@inheritdoc} + * @inheritdoc */ public function setProductId($value) { @@ -268,9 +265,14 @@ public function setProductId($value) /** * @inheritdoc + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + return array_diff( parent::__sleep(), ['metadataPool'] @@ -279,9 +281,14 @@ public function __sleep() /** * @inheritdoc + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + parent::__wakeup(); $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); $this->metadataPool = $objectManager->get(MetadataPool::class); diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Attribute/Collection.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Attribute/Collection.php index 3c40d326be77f..81cbbd06c523c 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Attribute/Collection.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Attribute/Collection.php @@ -18,6 +18,8 @@ use Magento\Catalog\Api\Data\ProductInterface; /** + * Collection of configurable product attributes. + * * @api * @SuppressWarnings(PHPMD.LongVariable) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -302,6 +304,8 @@ protected function _loadLabels() } /** + * Load related options' data. + * * @return void */ protected function loadOptions() @@ -354,9 +358,14 @@ protected function getIncludedOptions(array $usedProducts, AbstractAttribute $pr /** * @inheritdoc * @since 100.0.6 + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + return array_diff( parent::__sleep(), [ @@ -373,9 +382,14 @@ public function __sleep() /** * @inheritdoc * @since 100.0.6 + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + parent::__wakeup(); $objectManager = ObjectManager::getInstance(); $this->_storeManager = $objectManager->get(\Magento\Store\Model\StoreManagerInterface::class); diff --git a/app/code/Magento/ConfigurableProduct/Setup/Patch/Data/InstallInitialConfigurableAttributes.php b/app/code/Magento/ConfigurableProduct/Setup/Patch/Data/InstallInitialConfigurableAttributes.php index f69d8529fb801..c6b173453f5ec 100644 --- a/app/code/Magento/ConfigurableProduct/Setup/Patch/Data/InstallInitialConfigurableAttributes.php +++ b/app/code/Magento/ConfigurableProduct/Setup/Patch/Data/InstallInitialConfigurableAttributes.php @@ -6,16 +6,16 @@ namespace Magento\ConfigurableProduct\Setup\Patch\Data; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; use Magento\Eav\Setup\EavSetup; use Magento\Eav\Setup\EavSetupFactory; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; -use Magento\ConfigurableProduct\Model\Product\Type\Configurable; /** * Class InstallInitialConfigurableAttributes + * * @package Magento\ConfigurableProduct\Setup\Patch */ class InstallInitialConfigurableAttributes implements DataPatchInterface, PatchVersionInterface @@ -24,6 +24,7 @@ class InstallInitialConfigurableAttributes implements DataPatchInterface, PatchV * @var ModuleDataSetupInterface */ private $moduleDataSetup; + /** * @var EavSetupFactory */ @@ -43,7 +44,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { @@ -64,24 +65,27 @@ public function apply() 'color' ]; foreach ($attributes as $attributeCode) { - $relatedProductTypes = explode( - ',', - $eavSetup->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $attributeCode, 'apply_to') - ); - if (!in_array(Configurable::TYPE_CODE, $relatedProductTypes)) { - $relatedProductTypes[] = Configurable::TYPE_CODE; - $eavSetup->updateAttribute( - \Magento\Catalog\Model\Product::ENTITY, - $attributeCode, - 'apply_to', - implode(',', $relatedProductTypes) + $attribute = $eavSetup->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $attributeCode, 'apply_to'); + if ($attribute) { + $relatedProductTypes = explode( + ',', + $attribute ); + if (!in_array(Configurable::TYPE_CODE, $relatedProductTypes)) { + $relatedProductTypes[] = Configurable::TYPE_CODE; + $eavSetup->updateAttribute( + \Magento\Catalog\Model\Product::ENTITY, + $attributeCode, + 'apply_to', + implode(',', $relatedProductTypes) + ); + } } } } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -89,7 +93,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -97,7 +101,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml index 43dae2d70d416..5a172ca5eabdf 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml @@ -131,6 +131,22 @@ <click selector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" stepKey="clickOnConfirmInPopup"/> </actionGroup> + <actionGroup name="createConfigurationsForAttributeWithImages" extends="generateConfigurationsByAttributeCode"> + <arguments> + <argument name="attributeCode" type="string" defaultValue="SomeString"/> + <argument name="image" defaultValue="ProductImage"/> + </arguments> + + <click selector="{{AdminCreateProductConfigurationsPanel.applySingleSetOfImages}}" stepKey="clickOnApplySingleImageSetToAllSku" after="enterAttributeQuantity"/> + <waitForElementVisible selector="{{AdminCreateProductConfigurationsPanel.imageUploadButton}}" stepKey="seeImageSectionIsReady" after="clickOnApplySingleImageSetToAllSku"/> + <attachFile selector="{{AdminCreateProductConfigurationsPanel.imageFileUpload}}" userInput="{{image.file}}" stepKey="uploadFile" after="seeImageSectionIsReady"/> + <waitForElementNotVisible selector="{{AdminCreateProductConfigurationsPanel.uploadProgressBar}}" stepKey="waitForUpload" after="uploadFile"/> + <waitForElementVisible selector="{{AdminCreateProductConfigurationsPanel.imageFile(image.fileName)}}" stepKey="waitForThumbnail" after="waitForUpload"/> + + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickOnSaveButton2" after="clickOnNextButton4"/> + <click selector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" stepKey="clickOnConfirmInPopup" after="clickOnSaveButton2"/> + </actionGroup> + <actionGroup name="createConfigurationsForTwoAttribute" extends="generateConfigurationsByAttributeCode"> <arguments> <argument name="secondAttributeCode" type="string"/> @@ -276,4 +292,14 @@ <seeInField userInput="{{ApiConfigurableProduct.sku}}" selector="{{AdminProductFormSection.productSku}}" stepKey="seeSkuRequired"/> <dontSeeInField userInput="{{ApiConfigurableProduct.price}}" selector="{{AdminProductFormSection.productPrice}}" stepKey="dontSeePriceRequired"/> </actionGroup> + + <!--Click in Next Step and see Title--> + <actionGroup name="AdminConfigurableWizardMoveToNextStepActionGroup"> + <arguments> + <argument name="title" type="string"/> + </arguments> + <click selector="{{ConfigurableProductSection.nextButton}}" stepKey="clickNextButton"/> + <waitForPageLoad stepKey="waitForNextStepLoaded"/> + <see userInput="{{title}}" selector="{{AdminProductFormConfigurationsSection.stepsWizardTitle}}" stepKey="seeStepTitle"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml index c0a9f03906030..5feaab40a7695 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml @@ -10,7 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminCreateApiConfigurableProductActionGroup"> <arguments> - <argument name="productName" defaultValue="ApiConfigurableProductWithOutCategory" type="string"/> + <argument name="productName" defaultValue="{{ApiConfigurableProductWithOutCategory.name}}" type="string"/> </arguments> <!-- Create the configurable product based on the data in the /data folder --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml index 51bb041b66089..2c3e5716d6add 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml @@ -34,6 +34,19 @@ <seeElement selector="{{StorefrontProductInfoMainSection.attributeOptionByAttributeID(attributeCode, optionName)}}" stepKey="verifyOptionExists"/> </actionGroup> + <!-- Adds Single Option Configurable Product to cart--> + <actionGroup name="SelectSingleAttributeAndAddToCart"> + <arguments> + <argument name="productName" type="string"/> + <argument name="attributeCode" type="string"/> + <argument name="optionName" type="string"/> + </arguments> + <selectOption selector="{{StorefrontProductInfoMainSection.attributeSelectByAttributeID(attributeCode)}}" userInput="{{optionName}}" stepKey="selectAttribute"/> + <click stepKey="addProduct" selector="{{StorefrontProductActionSection.addToCart}}"/> + <waitForElementVisible selector="{{StorefrontQuickSearchResultsSection.messageSection}}" time="30" stepKey="waitForProductAdded"/> + <see selector="{{StorefrontQuickSearchResultsSection.messageSection}}" userInput="You added {{productName}} to your shopping cart." stepKey="seeAddedToCartMessage"/> + </actionGroup> + <!-- Verify configurable product options in storefront product view --> <actionGroup name="storefrontCheckConfigurableProductOptions"> <arguments> @@ -66,6 +79,18 @@ <seeElement selector="{{StorefrontProductMediaSection.imageFile(image.filename)}}" stepKey="seeFirstImage"/> </actionGroup> + <!-- Assert option image and price in storefront product page --> + <actionGroup name="AssertOptionImageAndPriceInStorefrontProductActionGroup"> + <arguments> + <argument name="label" type="string"/> + <argument name="image" type="string"/> + <argument name="price" type="string"/> + </arguments> + <selectOption userInput="{{label}}" selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="selectOption"/> + <seeElement selector="{{StorefrontProductMediaSection.imageFile(image)}}" stepKey="seeImage"/> + <see userInput="{{price}}" selector="{{StorefrontProductInfoMainSection.price}}" stepKey="seeProductPrice"/> + </actionGroup> + <!-- Assert configurable product with special price in storefront product page --> <actionGroup name="assertConfigurableProductWithSpecialPriceOnStorefrontProductPage"> <arguments> @@ -78,4 +103,4 @@ <see userInput="Regular Price" selector="{{StorefrontProductInfoMainSection.specialProductText}}" stepKey="seeText"/> <see userInput="{{price}}" selector="{{StorefrontProductInfoMainSection.oldProductPrice}}" stepKey="seeOldProductPrice"/> </actionGroup> -</actionGroups> \ No newline at end of file +</actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductOptionData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductOptionData.xml index f231d74b70dad..a1a499f33eda0 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductOptionData.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductOptionData.xml @@ -8,6 +8,11 @@ <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="ConfigurableProductOneOption" type="ConfigurableProductOption"> + <var key="attribute_id" entityKey="attribute_id" entityType="ProductAttribute" /> + <data key="label">option</data> + <requiredEntity type="ValueIndex">ValueIndex1</requiredEntity> + </entity> <entity name="ConfigurableProductTwoOptions" type="ConfigurableProductOption"> <var key="attribute_id" entityKey="attribute_id" entityType="ProductAttribute" /> <data key="label">option</data> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml index eccff2830d2a5..a5e74145c9fec 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml @@ -26,6 +26,10 @@ <element name="saveAttribute" type="button" selector="li[data-attribute-option-title=''] .action-save" timeout="30"/> <element name="attributeCheckboxByIndex" type="input" selector="li.attribute-option:nth-of-type({{var1}}) input" parameterized="true"/> + <element name="applySingleSetOfImages" type="radio" selector=".admin__field-label[for='apply-single-set-radio']" timeout="30"/> + <element name="imageFileUpload" type="input" selector=".steps-wizard-section input[type='file'][name='image']"/> + <element name="imageUploadButton" type="button" selector=".steps-wizard-section div.gallery"/> + <element name="applyUniquePricesByAttributeToEachSku" type="radio" selector=".admin__field-label[for='apply-unique-prices-radio']"/> <element name="applySinglePriceToAllSkus" type="radio" selector=".admin__field-label[for='apply-single-price-radio']"/> <element name="singlePrice" type="input" selector="#apply-single-price-input"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml index d1b16cb8f5ce3..f6828a3b86312 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml @@ -36,6 +36,8 @@ <element name="variationsSkuInputByRow" selector="[data-index='configurable-matrix'] table > tbody > tr:nth-of-type({{row}}) input[name*='sku']" type="input" parameterized="true"/> <element name="variationsSkuInputErrorByRow" selector="[data-index='configurable-matrix'] table > tbody > tr:nth-of-type({{row}}) .admin__field-error" type="text" parameterized="true"/> <element name="variationLabel" type="text" selector="//div[@data-index='configurable-matrix']/label"/> + <element name="stepsWizardTitle" type="text" selector="div.content:not([style='display: none;']) .steps-wizard-title"/> + <element name="attributeEntityByName" type="text" selector="//div[@class='attribute-entity']//div[normalize-space(.)='{{attributeLabel}}']" parameterized="true"/> </section> <section name="AdminConfigurableProductFormSection"> <element name="productWeight" type="input" selector=".admin__control-text[name='product[weight]']"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml index 09de3cc302903..24cd9262b6742 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -18,6 +18,7 @@ <!-- Parameter is the order number of the attribute on the page (1 is the newest) --> <element name="nthAttributeOnPage" type="block" selector="tr:nth-of-type({{numElement}}) .data" parameterized="true"/> <element name="stockIndication" type="block" selector=".stock" /> + <element name="attributeSelectByAttributeID" type="select" selector="//div[@class='fieldset']//div[//span[text()='{{attribute_code}}']]//select" parameterized="true"/> <element name="attributeOptionByAttributeID" type="select" selector="//div[@class='fieldset']//div[//span[text()='{{attribute_code}}']]//option[text()='{{optionName}}']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddingNewOptionsWithImagesAndPricesToConfigurableProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddingNewOptionsWithImagesAndPricesToConfigurableProductTest.xml new file mode 100644 index 0000000000000..52443a17dfe64 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddingNewOptionsWithImagesAndPricesToConfigurableProductTest.xml @@ -0,0 +1,124 @@ +<?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="AdminAddingNewOptionsWithImagesAndPricesToConfigurableProductTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="Update product"/> + <title value="Adding new options with images and prices to Configurable Product"/> + <description value="Test case verifies possibility to add new options for configurable attribute for existing configurable product."/> + <severity value="CRITICAL"/> + <testCaseId value="MC-13339"/> + <group value="configurableProduct"/> + </annotations> + + <before> + <actionGroup ref="AdminCreateApiConfigurableProductActionGroup" stepKey="createConfigurableProduct"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + + <after> + <deleteData createDataKey="createConfigProductCreateConfigurableProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigProductAttributeCreateConfigurableProduct" stepKey="deleteConfigProductAttribute"/> + <deleteData createDataKey="createConfigChildProduct1CreateConfigurableProduct" stepKey="deleteConfigChildProduct1"/> + <deleteData createDataKey="createConfigChildProduct2CreateConfigurableProduct" stepKey="deleteConfigChildProduct2"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open edit product page--> + <amOnPage url="{{AdminProductEditPage.url($$createConfigProductCreateConfigurableProduct.id$$)}}" stepKey="goToProductEditPage"/> + + <!--Open edit configuration wizard--> + <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="clickEditConfigurations"/> + <see userInput="Select Attributes" selector="{{AdminProductFormConfigurationsSection.stepsWizardTitle}}" stepKey="seeStepTitle"/> + + <!--Click Next button--> + <actionGroup ref="AdminConfigurableWizardMoveToNextStepActionGroup" stepKey="navigateToAttributeValuesStep"> + <argument name="title" value="Attribute Values"/> + </actionGroup> + <seeElement selector="{{AdminProductFormConfigurationsSection.attributeEntityByName($$createConfigProductAttributeCreateConfigurableProduct.default_frontend_label$$)}}" stepKey="seeAttribute"/> + + <!--Create one color option via "Create New Value" link--> + <click selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="clickOnCreateNewValue"/> + <fillField userInput="{{colorDefaultProductAttribute1.name}}" selector="{{AdminCreateProductConfigurationsPanel.attributeName}}" stepKey="fillFieldForNewAttribute"/> + <click selector="{{AdminCreateProductConfigurationsPanel.saveAttribute}}" stepKey="clickOnSaveNewAttribute"/> + + <!--Click Next button--> + <actionGroup ref="AdminConfigurableWizardMoveToNextStepActionGroup" stepKey="navigateToBulkStep"> + <argument name="title" value="Bulk Images, Price and Quantity"/> + </actionGroup> + + <!--Select Apply unique images by attribute to each SKU and color attribute in dropdown in Images--> + <click selector="{{AdminCreateProductConfigurationsPanel.applyUniqueImagesToEachSkus}}" stepKey="clickOnApplyUniqueImagesToEachSku"/> + <selectOption userInput="$$createConfigProductAttributeCreateConfigurableProduct.default_frontend_label$$" + selector="{{AdminCreateProductConfigurationsPanel.selectImagesButton}}" stepKey="selectAttributeOption"/> + + <!-- Add images to configurable product attribute options --> + <actionGroup ref="addUniqueImageToConfigurableProductOption" stepKey="addImageToConfigurableProductOptionOne"> + <argument name="image" value="ImageUpload"/> + <argument name="frontend_label" value="$$createConfigProductAttributeCreateConfigurableProduct.default_frontend_label$$"/> + <argument name="label" value="$$getConfigAttributeOption1CreateConfigurableProduct.label$$"/> + </actionGroup> + <actionGroup ref="addUniqueImageToConfigurableProductOption" stepKey="addImageToConfigurableProductOptionTwo"> + <argument name="image" value="ImageUpload_1"/> + <argument name="frontend_label" value="$$createConfigProductAttributeCreateConfigurableProduct.default_frontend_label$$"/> + <argument name="label" value="$$getConfigAttributeOption2CreateConfigurableProduct.label$$"/> + </actionGroup> + <actionGroup ref="addUniqueImageToConfigurableProductOption" stepKey="addImageToConfigurableProductOptionThree"> + <argument name="image" value="ImageUpload3"/> + <argument name="frontend_label" value="$$createConfigProductAttributeCreateConfigurableProduct.default_frontend_label$$"/> + <argument name="label" value="{{colorDefaultProductAttribute1.name}}"/> + </actionGroup> + + <!--Add prices to configurable product attribute options--> + <click selector="{{AdminCreateProductConfigurationsPanel.applyUniquePricesToEachSkus}}" stepKey="clickOnApplyUniquePricesByAttributeToEachSku"/> + <selectOption userInput="$$createConfigProductAttributeCreateConfigurableProduct.default_frontend_label$$" + selector="{{AdminCreateProductConfigurationsPanel.selectAttribute}}" stepKey="selectAttributes"/> + <fillField userInput="10" selector="{{AdminCreateProductConfigurationsPanel.price($$getConfigAttributeOption1CreateConfigurableProduct.label$$)}}" stepKey="fillAttributePrice"/> + <fillField userInput="20" selector="{{AdminCreateProductConfigurationsPanel.price($$getConfigAttributeOption2CreateConfigurableProduct.label$$)}}" stepKey="fillAttributePrice1"/> + <fillField userInput="30" selector="{{AdminCreateProductConfigurationsPanel.price(colorDefaultProductAttribute1.name)}}" stepKey="fillAttributePrice2"/> + + <!-- Add quantity to product attribute options --> + <click selector="{{AdminCreateProductConfigurationsPanel.applySingleQuantityToEachSkus}}" stepKey="clickOnApplySingleQuantityToEachSku"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.quantity}}" userInput="100" stepKey="enterAttributeQuantity"/> + + <!--Click Next button--> + <actionGroup ref="AdminConfigurableWizardMoveToNextStepActionGroup" stepKey="navigateToSummaryStep"> + <argument name="title" value="Summary"/> + </actionGroup> + + <!--Click Generate Configure button--> + <click selector="{{ConfigurableProductSection.generateConfigure}}" stepKey="clickGenerateConfigure"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!--Go to frontend and check image and price--> + <amOnPage url="{{StorefrontProductPage.url($$createConfigProductCreateConfigurableProduct.custom_attributes[url_key]$$)}}" stepKey="goToProductPage"/> + + <actionGroup ref="AssertOptionImageAndPriceInStorefrontProductActionGroup" stepKey="assertFirstOptionImageAndPriceInStorefrontProductPage"> + <argument name="label" value="$$getConfigAttributeOption1CreateConfigurableProduct.label$$"/> + <argument name="image" value="{{ImageUpload.filename}}"/> + <argument name="price" value="10"/> + </actionGroup> + + <actionGroup ref="AssertOptionImageAndPriceInStorefrontProductActionGroup" stepKey="assertSecondOptionImageAndPriceInStorefrontProductPage"> + <argument name="label" value="$$getConfigAttributeOption2CreateConfigurableProduct.label$$"/> + <argument name="image" value="{{ImageUpload_1.filename}}"/> + <argument name="price" value="20"/> + </actionGroup> + + <actionGroup ref="AssertOptionImageAndPriceInStorefrontProductActionGroup" stepKey="assertThirdOptionImageAndPriceInStorefrontProductPage"> + <argument name="label" value="{{colorDefaultProductAttribute1.name}}"/> + <argument name="image" value="{{ImageUpload3.filename}}"/> + <argument name="price" value="30"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml index 93df31a7d89e5..2cc71964042a4 100755 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml @@ -53,9 +53,6 @@ <testCaseId value="MC-10930"/> <group value="catalog"/> <group value="mtf_migrated"/> - <skip> - <issueId value="MSI-2110"/> - </skip> </annotations> <before> <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> @@ -93,10 +90,6 @@ <useCaseId value="MAGETWO-44165"/> <testCaseId value="MAGETWO-29398"/> <group value="catalog"/> - <group value="mtf_migrated"/> - <skip> - <issueId value="MSI-2110"/> - </skip> </annotations> <before> <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> @@ -139,9 +132,6 @@ <testCaseId value="MAGETWO-29398"/> <group value="catalog"/> <group value="mtf_migrated"/> - <skip> - <issueId value="MSI-2110"/> - </skip> </annotations> <before> <createData entity="_defaultCategory" stepKey="createPreReqCategory"/> diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js index e732960421541..ef40dcb9a7323 100644 --- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js @@ -373,7 +373,7 @@ define([ allowedProducts, i, j, - basePrice = parseFloat(this.options.spConfig.prices.basePrice.amount), + finalPrice = parseFloat(this.options.spConfig.prices.finalPrice.amount), optionFinalPrice, optionPriceDiff, optionPrices = this.options.spConfig.optionPrices, @@ -410,7 +410,7 @@ define([ typeof optionPrices[allowedProducts[0]] !== 'undefined') { allowedProductMinPrice = this._getAllowedProductWithMinPrice(allowedProducts); optionFinalPrice = parseFloat(optionPrices[allowedProductMinPrice].finalPrice.amount); - optionPriceDiff = optionFinalPrice - basePrice; + optionPriceDiff = optionFinalPrice - finalPrice; if (optionPriceDiff !== 0) { options[i].label = options[i].label + ' ' + priceUtils.formatPrice( diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..9166c8745c9e1 --- /dev/null +++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,21 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuStoresCurrencyCurrencyRates"> + <data key="pageTitle">Currency Rates</data> + <data key="title">Currency Rates</data> + <data key="dataUiId">magento-currencysymbol-system-currency-rates</data> + </entity> + <entity name="AdminMenuStoresCurrencyCurrencySymbols"> + <data key="pageTitle">Currency Symbols</data> + <data key="title">Currency Symbols</data> + <data key="dataUiId">magento-currencysymbol-system-currency-symbols</data> + </entity> +</entities> diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminStoresCurrencyRatesNavigateMenuTest.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminStoresCurrencyRatesNavigateMenuTest.xml new file mode 100644 index 0000000000000..4a33d40d2a35f --- /dev/null +++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminStoresCurrencyRatesNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminStoresCurrencyRatesNavigateMenuTest"> + <annotations> + <features value="CurrencySymbol"/> + <stories value="Menu Navigation"/> + <title value="Admin stores currency rates navigate menu test"/> + <description value="Admin should be able to navigate to Stores > Currency Rates"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14150"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToStoresCurrencyRatesPage"> + <argument name="menuUiId" value="{{AdminMenuStores.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuStoresCurrencyCurrencyRates.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuStoresCurrencyCurrencyRates.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminStoresCurrencySymbolsNavigateMenuTest.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminStoresCurrencySymbolsNavigateMenuTest.xml new file mode 100644 index 0000000000000..978917772f2dd --- /dev/null +++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminStoresCurrencySymbolsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminStoresCurrencySymbolsNavigateMenuTest"> + <annotations> + <features value="CurrencySymbol"/> + <stories value="Menu Navigation"/> + <title value="Admin stores currency symbols navigate menu test"/> + <description value="Admin should be able to navigate to Stores > Currency Symbols"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14151"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToStoresCurrencySymbolsPage"> + <argument name="menuUiId" value="{{AdminMenuStores.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuStoresCurrencyCurrencySymbols.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuStoresCurrencyCurrencySymbols.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php index 673300369fe06..250d190f8fae7 100644 --- a/app/code/Magento/Customer/Model/AccountManagement.php +++ b/app/code/Magento/Customer/Model/AccountManagement.php @@ -624,7 +624,6 @@ public function initiatePasswordReset($email, $template, $websiteId = null) * @param string $rpToken * @throws ExpiredException * @throws NoSuchEntityException - * * @return CustomerInterface * @throws LocalizedException */ @@ -703,7 +702,12 @@ public function resetPassword($email, $resetToken, $newPassword) $customerSecure->setRpTokenCreatedAt(null); $customerSecure->setPasswordHash($this->createPasswordHash($newPassword)); $this->destroyCustomerSessions($customer->getId()); - $this->sessionManager->destroy(); + if ($this->sessionManager->isSessionExists()) { + //delete old session and move data to the new session + //use this instead of $this->sessionManager->regenerateId because last one doesn't delete old session + // phpcs:ignore Magento2.Functions.DiscouragedFunction + session_regenerate_id(true); + } $this->customerRepository->save($customer); return true; @@ -1564,6 +1568,7 @@ private function getEmailNotification() /** * Destroy all active customer sessions by customer id (current session will not be destroyed). + * * Customer sessions which should be deleted are collecting from the "customer_visitor" table considering * configured session lifetime. * diff --git a/app/code/Magento/Customer/Model/Address/CustomAttributesProcessor.php b/app/code/Magento/Customer/Model/Address/CustomAttributesProcessor.php new file mode 100644 index 0000000000000..d6e63e11ee453 --- /dev/null +++ b/app/code/Magento/Customer/Model/Address/CustomAttributesProcessor.php @@ -0,0 +1,112 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Model\Address; + +use Magento\Customer\Api\AddressMetadataInterface; +use Magento\Eav\Api\AttributeOptionManagementInterface; + +/** + * Provides customer address data. + */ +class CustomAttributesProcessor +{ + /** + * @var AddressMetadataInterface + */ + private $addressMetadata; + + /** + * @var AttributeOptionManagementInterface + */ + private $attributeOptionManager; + + /** + * @param AddressMetadataInterface $addressMetadata + * @param AttributeOptionManagementInterface $attributeOptionManager + */ + public function __construct( + AddressMetadataInterface $addressMetadata, + AttributeOptionManagementInterface $attributeOptionManager + ) { + $this->addressMetadata = $addressMetadata; + $this->attributeOptionManager = $attributeOptionManager; + } + + /** + * Set Labels to custom Attributes + * + * @param \Magento\Framework\Api\AttributeValue[] $customAttributes + * @return array $customAttributes + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Exception\StateException + */ + private function setLabelsForAttributes(array $customAttributes): array + { + if (!empty($customAttributes)) { + foreach ($customAttributes as $customAttributeCode => $customAttribute) { + $attributeOptionLabels = $this->getAttributeLabels($customAttribute, $customAttributeCode); + if (!empty($attributeOptionLabels)) { + $customAttributes[$customAttributeCode]['label'] = implode(', ', $attributeOptionLabels); + } + } + } + + return $customAttributes; + } + /** + * Get Labels by CustomAttribute and CustomAttributeCode + * + * @param array $customAttribute + * @param string $customAttributeCode + * @return array $attributeOptionLabels + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Exception\StateException + */ + private function getAttributeLabels(array $customAttribute, string $customAttributeCode) : array + { + $attributeOptionLabels = []; + + if (!empty($customAttribute['value'])) { + $customAttributeValues = explode(',', $customAttribute['value']); + $attributeOptions = $this->attributeOptionManager->getItems( + \Magento\Customer\Model\Indexer\Address\AttributeProvider::ENTITY, + $customAttributeCode + ); + + if (!empty($attributeOptions)) { + foreach ($attributeOptions as $attributeOption) { + $attributeOptionValue = $attributeOption->getValue(); + if (\in_array($attributeOptionValue, $customAttributeValues, false)) { + $attributeOptionLabels[] = $attributeOption->getLabel() ?? $attributeOptionValue; + } + } + } + } + + return $attributeOptionLabels; + } + + /** + * Filter not visible on storefront custom attributes. + * + * @param array $attributes + * @return array + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function filterNotVisibleAttributes(array $attributes): array + { + $attributesMetadata = $this->addressMetadata->getAllAttributesMetadata(); + foreach ($attributesMetadata as $attributeMetadata) { + if (!$attributeMetadata->isVisible()) { + unset($attributes[$attributeMetadata->getAttributeCode()]); + } + } + + return $this->setLabelsForAttributes($attributes); + } +} diff --git a/app/code/Magento/Customer/Model/Address/CustomerAddressDataFormatter.php b/app/code/Magento/Customer/Model/Address/CustomerAddressDataFormatter.php new file mode 100644 index 0000000000000..9202d7492040c --- /dev/null +++ b/app/code/Magento/Customer/Model/Address/CustomerAddressDataFormatter.php @@ -0,0 +1,110 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Model\Address; + +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Customer\Model\Address\Mapper as AddressMapper; +use Magento\Customer\Model\Address\Config as AddressConfig; + +/** + * Provides method to format customer address data. + */ +class CustomerAddressDataFormatter +{ + /** + * @var AddressMapper + */ + private $addressMapper; + + /** + * @var AddressConfig + */ + private $addressConfig; + + /** + * @var CustomAttributesProcessor + */ + private $customAttributesProcessor; + + /** + * @param Mapper $addressMapper + * @param Config $addressConfig + * @param CustomAttributesProcessor $customAttributesProcessor + */ + public function __construct( + AddressMapper $addressMapper, + AddressConfig $addressConfig, + CustomAttributesProcessor $customAttributesProcessor + ) { + $this->addressMapper = $addressMapper; + $this->addressConfig = $addressConfig; + $this->customAttributesProcessor = $customAttributesProcessor; + } + + /** + * Prepare customer address data. + * + * @param AddressInterface $customerAddress + * @return array + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function prepareAddress(AddressInterface $customerAddress) + { + $resultAddress = [ + 'id' => $customerAddress->getId(), + 'customer_id' => $customerAddress->getCustomerId(), + 'company' => $customerAddress->getCompany(), + 'prefix' => $customerAddress->getPrefix(), + 'firstname' => $customerAddress->getFirstname(), + 'lastname' => $customerAddress->getLastname(), + 'middlename' => $customerAddress->getMiddlename(), + 'suffix' => $customerAddress->getSuffix(), + 'street' => $customerAddress->getStreet(), + 'city' => $customerAddress->getCity(), + 'region' => [ + 'region' => $customerAddress->getRegion()->getRegion(), + 'region_code' => $customerAddress->getRegion()->getRegionCode(), + 'region_id' => $customerAddress->getRegion()->getRegionId(), + ], + 'region_id' => $customerAddress->getRegionId(), + 'postcode' => $customerAddress->getPostcode(), + 'country_id' => $customerAddress->getCountryId(), + 'telephone' => $customerAddress->getTelephone(), + 'fax' => $customerAddress->getFax(), + 'default_billing' => $customerAddress->isDefaultBilling(), + 'default_shipping' => $customerAddress->isDefaultShipping(), + 'inline' => $this->getCustomerAddressInline($customerAddress), + 'custom_attributes' => [], + 'extension_attributes' => $customerAddress->getExtensionAttributes(), + ]; + + if ($customerAddress->getCustomAttributes()) { + $customerAddress = $customerAddress->__toArray(); + $resultAddress['custom_attributes'] = $this->customAttributesProcessor->filterNotVisibleAttributes( + $customerAddress['custom_attributes'] + ); + } + + return $resultAddress; + } + + /** + * Set additional customer address data + * + * @param AddressInterface $address + * @return string + */ + private function getCustomerAddressInline(AddressInterface $address): string + { + $builtOutputAddressData = $this->addressMapper->toFlatArray($address); + return $this->addressConfig + ->getFormatByCode(AddressConfig::DEFAULT_ADDRESS_FORMAT) + ->getRenderer() + ->renderArray($builtOutputAddressData); + } +} diff --git a/app/code/Magento/Customer/Model/Address/CustomerAddressDataProvider.php b/app/code/Magento/Customer/Model/Address/CustomerAddressDataProvider.php new file mode 100644 index 0000000000000..04fdd2a7f7266 --- /dev/null +++ b/app/code/Magento/Customer/Model/Address/CustomerAddressDataProvider.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Model\Address; + +/** + * Provides customer address data. + */ +class CustomerAddressDataProvider +{ + /** + * Customer addresses. + * + * @var array + */ + private $customerAddresses = []; + + /** + * @var CustomerAddressDataFormatter + */ + private $customerAddressDataFormatter; + + /** + * @param CustomerAddressDataFormatter $customerAddressDataFormatter + */ + public function __construct( + CustomerAddressDataFormatter $customerAddressDataFormatter + ) { + $this->customerAddressDataFormatter = $customerAddressDataFormatter; + } + + /** + * Get addresses for customer. + * + * @param \Magento\Customer\Api\Data\CustomerInterface $customer + * @return array + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function getAddressDataByCustomer( + \Magento\Customer\Api\Data\CustomerInterface $customer + ): array { + if (!empty($this->customerAddresses)) { + return $this->customerAddresses; + } + + $customerOriginAddresses = $customer->getAddresses(); + if (!$customerOriginAddresses) { + return []; + } + + $customerAddresses = []; + foreach ($customerOriginAddresses as $address) { + $customerAddresses[$address->getId()] = $this->customerAddressDataFormatter->prepareAddress($address); + } + + $this->customerAddresses = $customerAddresses; + + return $this->customerAddresses; + } +} diff --git a/app/code/Magento/Customer/Model/Attribute.php b/app/code/Magento/Customer/Model/Attribute.php index 98a97872f15f4..ae714f993082e 100644 --- a/app/code/Magento/Customer/Model/Attribute.php +++ b/app/code/Magento/Customer/Model/Attribute.php @@ -202,9 +202,14 @@ public function canBeFilterableInGrid() /** * @inheritdoc + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $this->unsetData('entity_type'); return array_diff( parent::__sleep(), @@ -214,9 +219,14 @@ public function __sleep() /** * @inheritdoc + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + parent::__wakeup(); $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); $this->indexerRegistry = $objectManager->get(\Magento\Framework\Indexer\IndexerRegistry::class); diff --git a/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php b/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php index 4d1bb2e6b9e99..07b8681df91ac 100644 --- a/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php +++ b/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php @@ -39,7 +39,6 @@ class DataProviderWithDefaultAddresses extends \Magento\Ui\DataProvider\Abstract private static $forbiddenCustomerFields = [ 'password_hash', 'rp_token', - 'confirmation', ]; /** diff --git a/app/code/Magento/Customer/Model/ResourceModel/Customer/Collection.php b/app/code/Magento/Customer/Model/ResourceModel/Customer/Collection.php index 394a0d3ed556d..af8980a129d3e 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/Customer/Collection.php +++ b/app/code/Magento/Customer/Model/ResourceModel/Customer/Collection.php @@ -5,8 +5,6 @@ */ namespace Magento\Customer\Model\ResourceModel\Customer; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; - /** * Customers collection * @@ -45,7 +43,6 @@ class Collection extends \Magento\Eav\Model\Entity\Collection\VersionControl\Abs * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection * @param string $modelName * - * @param ResourceModelPoolInterface|null $resourceModelPool * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -61,8 +58,7 @@ public function __construct( \Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot $entitySnapshot, \Magento\Framework\DataObject\Copy\Config $fieldsetConfig, \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, - $modelName = self::CUSTOMER_MODEL_NAME, - ResourceModelPoolInterface $resourceModelPool = null + $modelName = self::CUSTOMER_MODEL_NAME ) { $this->_fieldsetConfig = $fieldsetConfig; $this->_modelName = $modelName; @@ -77,8 +73,7 @@ public function __construct( $resourceHelper, $universalFactory, $entitySnapshot, - $connection, - $resourceModelPool + $connection ); } diff --git a/app/code/Magento/Customer/Setup/Patch/Data/MigrateStoresAllowedCountriesToWebsite.php b/app/code/Magento/Customer/Setup/Patch/Data/MigrateStoresAllowedCountriesToWebsite.php index 7488f3fd4a920..e4978070f53ad 100644 --- a/app/code/Magento/Customer/Setup/Patch/Data/MigrateStoresAllowedCountriesToWebsite.php +++ b/app/code/Magento/Customer/Setup/Patch/Data/MigrateStoresAllowedCountriesToWebsite.php @@ -8,12 +8,14 @@ use Magento\Directory\Model\AllowedCountries; use Magento\Framework\Setup\ModuleDataSetupInterface; -use Magento\Directory\Model\AllowedCountriesFactory; use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; +/** + * Migrate store allowed countries to website. + */ class MigrateStoresAllowedCountriesToWebsite implements DataPatchInterface, PatchVersionInterface { /** @@ -27,7 +29,7 @@ class MigrateStoresAllowedCountriesToWebsite implements DataPatchInterface, Patc private $storeManager; /** - * @var AllowedCountriesFactory + * @var AllowedCountries */ private $allowedCountries; @@ -48,10 +50,11 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { + $this->moduleDataSetup->getConnection()->beginTransaction(); try { @@ -149,7 +152,7 @@ private function mergeAllowedCountries(array $countries, array $newCountries, $i } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -159,7 +162,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -167,7 +170,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressesFromActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressesFromActionGroup.xml index 594337c1a6922..9c38f23739b10 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressesFromActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressesFromActionGroup.xml @@ -39,4 +39,13 @@ <click selector="{{AdminEditCustomerAddressesSection.defaultBillingAddressButton}}" stepKey="setDefaultBilling" before="setDefaultShipping"/> <click selector="{{AdminEditCustomerAddressesSection.defaultShippingAddressButton}}" stepKey="setDefaultShipping" before="fillPrefixName"/> </actionGroup> + <actionGroup name="SelectDropdownCustomerAddressAttributeValueActionGroup"> + <arguments> + <argument name="customerAddressAttribute"/> + <argument name="optionValue" type="string"/> + </arguments> + <selectOption selector="{{AdminEditCustomerAddressesSection.dropDownAttribute(customerAddressAttribute.code)}}" userInput="{{optionValue}}" stepKey="selectOptionValue"/> + <click selector="{{AdminEditCustomerAddressesSection.save}}" stepKey="saveAddress"/> + <waitForPageLoad stepKey="waitForAddressSaved"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminOpenCustomerEditPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminOpenCustomerEditPageActionGroup.xml new file mode 100644 index 0000000000000..8e6b56b19d674 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminOpenCustomerEditPageActionGroup.xml @@ -0,0 +1,17 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenCustomerEditPageActionGroup"> + <arguments> + <argument name="customerId" type="string" /> + </arguments> + <amOnPage url="{{AdminEditCustomerPage.url(customerId)}}" stepKey="openCustomerEditPage" /> + <waitForPageLoad stepKey="waitForPageLoad" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupNotInGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupNotInGridActionGroup.xml new file mode 100644 index 0000000000000..26c4f23fc9a77 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupNotInGridActionGroup.xml @@ -0,0 +1,20 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCustomerGroupNotInGridActionGroup"> + <arguments> + <argument name="customerGroup" type="entity" /> + </arguments> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="cleanFilters"/> + <click selector="{{AdminDataGridHeaderSection.filters}}" stepKey="openFiltersSectionOnCustomerGroupIndexPage"/> + <fillField userInput="{{customerGroup.code}}" selector="{{AdminDataGridHeaderSection.filterFieldInput('customer_group_code')}}" stepKey="fillNameFieldOnFiltersSection"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupNotOnProductFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupNotOnProductFormActionGroup.xml new file mode 100644 index 0000000000000..94e01db5c1ff8 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupNotOnProductFormActionGroup.xml @@ -0,0 +1,23 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCustomerGroupNotOnProductFormActionGroup"> + <arguments> + <argument name="customerGroup" type="entity" /> + </arguments> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickOnAdvancedPricingButton"/> + <waitForElementVisible selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="waitForCustomerGroupPriceAddButton"/> + <click selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="addCustomerGroupAllGroupsQty1PriceDiscountAnd10percent"/> + <grabMultiple selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelectOptions('0')}}" stepKey="customerGroups" /> + <assertNotContains stepKey="assertCustomerGroupNotInOptions"> + <actualResult type="variable">customerGroups</actualResult> + <expectedResult type="string">{{customerGroup.code}}</expectedResult> + </assertNotContains> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupOnCustomerFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupOnCustomerFormActionGroup.xml new file mode 100644 index 0000000000000..2c8e0081e5e90 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerGroupOnCustomerFormActionGroup.xml @@ -0,0 +1,18 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCustomerGroupOnCustomerFormActionGroup"> + <arguments> + <argument name="customerGroup" type="entity" /> + </arguments> + <click selector="{{AdminCustomerAccountInformationSection.accountInformationTab}}" stepKey="clickOnAccountInfoTab" /> + <waitForPageLoad stepKey="waitForPageLoad" /> + <seeOptionIsSelected userInput="{{customerGroup.code}}" selector="{{AdminCustomerAccountInformationSection.group}}" stepKey="verifyNeededCustomerGroupSelected" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml index fc5c1b881752e..c3b92b1af7f82 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml @@ -9,8 +9,8 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="CustomerLogoutStorefrontByMenuItemsActionGroup"> - <conditionalClick selector="{{StorefrontPanelHeaderSection.customerWelcome}}" - dependentSelector="{{StorefrontPanelHeaderSection.customerWelcomeMenu}}" + <conditionalClick selector="{{StorefrontPanelHeaderSection.customerWelcomeMenu}}" + dependentSelector="{{StorefrontPanelHeaderSection.customerLogoutLink}}" visible="false" stepKey="clickHeaderCustomerMenuButton" /> <click selector="{{StorefrontPanelHeaderSection.customerLogoutLink}}" stepKey="clickSignOutButton" /> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Customer/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..4e433c76b3ddb --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,31 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuCustomers"> + <data key="pageTitle">Customers</data> + <data key="title">Customers</data> + <data key="dataUiId">magento-customer-customer</data> + </entity> + <entity name="AdminMenuCustomersAllCustomers"> + <data key="pageTitle">Customers</data> + <data key="title">All Customers</data> + <data key="dataUiId">magento-customer-customer-manage</data> + </entity> + <entity name="AdminMenuCustomersCustomerGroups"> + <data key="pageTitle">Customer Groups</data> + <data key="title">Customer Groups</data> + <data key="dataUiId">magento-customer-customer-group</data> + </entity> + <entity name="AdminMenuCustomersNowOnline"> + <data key="pageTitle">Customers Now Online</data> + <data key="title">Now Online</data> + <data key="dataUiId">magento-customer-customer-online</data> + </entity> +</entities> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml index 3cbd70d342824..11a47459ab7b3 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml @@ -21,4 +21,11 @@ <entity name="GlobalCustomerAccountSharing" type="account_share_scope_value"> <data key="value">0</data> </entity> + + <entity name="CustomerAccountSharingSystemValue" type="customer_account_sharing_config_inherit"> + <requiredEntity type="account_share_scope_inherit">CustomerAccountSharingInherit</requiredEntity> + </entity> + <entity name="CustomerAccountSharingInherit" type="account_share_scope_inherit"> + <data key="inherit">true</data> + </entity> </entities> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml index 06c23a2864984..f8943dcfe8369 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml @@ -46,6 +46,19 @@ <data key="website_id">0</data> <requiredEntity type="address">US_Address_TX</requiredEntity> </entity> + <entity name="UsCustomerAssignedToNewCustomerGroup" type="customer"> + <var key="group_id" entityKey="id" entityType="customerGroup" /> + <data key="default_billing">true</data> + <data key="default_shipping">true</data> + <data key="email" unique="prefix">John.Doe@example.com</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">US_Address_TX</requiredEntity> + </entity> <entity name="Simple_US_Customer_Incorrect_Name" type="customer"> <data key="group_id">1</data> <data key="default_billing">true</data> @@ -203,4 +216,18 @@ <data key="store_id">0</data> <data key="website_id">0</data> </entity> + <entity name="Simple_US_Customer_Two_Addresses" type="customer"> + <data key="group_id">0</data> + <data key="default_billing">true</data> + <data key="default_shipping">true</data> + <data key="email" unique="prefix">John.Doe@example.com</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">US_Address_TX</requiredEntity> + <requiredEntity type="address">US_Address_NY_Not_Default_Address</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/NewCustomerData.xml b/app/code/Magento/Customer/Test/Mftf/Data/NewCustomerData.xml index cdd117c2a0b12..347a04251f9cd 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/NewCustomerData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/NewCustomerData.xml @@ -20,4 +20,30 @@ <data key="PhoneNumber">9999</data> <data key="Country">Armenia</data> </entity> + <entity name="Simple_UK_Customer_For_Shipment" type="customer"> + <data key="firstName">John</data> + <data key="lastName">Doe</data> + <data key="email">johndoe@example.com</data> + <data key="company">Test Company</data> + <data key="streetFirstLine">39 St Maurices Road</data> + <data key="streetSecondLine">ap. 654</data> + <data key="city">PULDAGON</data> + <data key="telephone">077 5866 0667</data> + <data key="country">United Kingdom</data> + <data key="region"> </data> + <data key="postcode">KW1 7NQ</data> + </entity> + <entity name="Simple_US_CA_Customer_For_Shipment" type="customer"> + <data key="firstName">John</data> + <data key="lastName">Doe</data> + <data key="email">johndoeusca@example.com</data> + <data key="company">Magento</data> + <data key="streetFirstLine">123 Alphabet Drive</data> + <data key="streetSecondLine">ap. 350</data> + <data key="city">Los Angeles</data> + <data key="telephone">555-55-555-55</data> + <data key="country">United States</data> + <data key="region">California</data> + <data key="postcode">90240</data> + </entity> </entities> diff --git a/app/code/Magento/Customer/Test/Mftf/Metadata/customer_config_account_sharing-meta.xml b/app/code/Magento/Customer/Test/Mftf/Metadata/customer_config_account_sharing-meta.xml index 41701bfac11ad..c3132b5b6a44f 100644 --- a/app/code/Magento/Customer/Test/Mftf/Metadata/customer_config_account_sharing-meta.xml +++ b/app/code/Magento/Customer/Test/Mftf/Metadata/customer_config_account_sharing-meta.xml @@ -18,4 +18,16 @@ </object> </object> </operation> + <operation name="CustomerAccountShareConfigInherit" dataType="customer_account_sharing_config_inherit" type="create" auth="adminFormKey" url="/admin/system_config/save/section/customer/" + method="POST"> + <object key="groups" dataType="customer_account_sharing_config_inherit"> + <object key="account_share" dataType="customer_account_sharing_config_inherit"> + <object key="fields" dataType="customer_account_sharing_config_inherit"> + <object key="scope" dataType="account_share_scope_inherit"> + <field key="inherit">boolean</field> + </object> + </object> + </object> + </object> + </operation> </operations> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml index 9e104eb52cf90..a934d71397b8c 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml @@ -8,5 +8,8 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCustomerConfigSection"> <element name="customerDataLifetime" type="input" selector="#customer_online_customers_section_data_lifetime"/> + <element name="accountSharingOptionsTab" type="button" selector="#customer_account_share-head"/> + <element name="shareCustomerAccountInherit" type="checkbox" selector="#customer_account_share_scope_inherit"/> + <element name="shareCustomerAccount" type="select" selector="#customer_account_share_scope"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml index 25617ca05dd44..17a4a283c2648 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml @@ -18,5 +18,6 @@ <element name="clearAll" type="button" selector=".admin__data-grid-header .action-tertiary.action-clear" timeout="30"/> <element name="viewDropdown" type="button" selector=".admin__data-grid-action-bookmarks button.admin__action-dropdown"/> <element name="viewBookmark" type="button" selector="//div[contains(@class, 'admin__data-grid-action-bookmarks')]/ul/li/div/a[text() = '{{label}}']" parameterized="true" timeout="30"/> + <element name="countryOptions" type="button" selector=".admin__data-grid-filters select[name=billing_country_id] option"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerAddressesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerAddressesSection.xml index ffddc6292ef5d..4d66b40ee660b 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerAddressesSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerAddressesSection.xml @@ -25,6 +25,7 @@ <element name="city" type="text" selector="//*[@class='modal-component']//input[@name='city']" /> <element name="country" type="select" selector="//*[@class='modal-component']//select[@name='country_id']" /> <element name="state" type="select" selector="//*[@class='modal-component']//select[@name='region_id']" /> + <element name="dropDownAttribute" type="select" selector="//select[@name='{{var1}}']" parameterized="true"/> <element name="zipCode" type="text" selector="//*[@class='modal-component']//input[@name='postcode']" /> <element name="phone" type="text" selector="//*[@class='modal-component']//input[@name='telephone']" /> <element name="vat" type="text" selector="input[name='vat_id']" /> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml index dab298f6b416b..3610532c5356b 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml @@ -15,8 +15,8 @@ <element name="welcomeMessage" type="text" selector="header>.panel .greet.welcome" /> <element name="createAnAccountLink" type="select" selector="//div[@class='panel wrapper']//li/a[contains(.,'Create an Account')]" timeout="30"/> <element name="notYouLink" type="button" selector=".greet.welcome span a"/> - <element name="customerWelcome" type="text" selector=".panel.header .customer-welcome"/> - <element name="customerWelcomeMenu" type="text" selector=".panel.header .customer-welcome .customer-menu"/> + <element name="customerWelcome" type="text" selector=".panel.header .greet.welcome"/> + <element name="customerWelcomeMenu" type="text" selector=".panel.header .customer-welcome .customer-name"/> <element name="customerLoginLink" type="button" selector=".panel.header .header.links .authorization-link a" timeout="30"/> <element name="customerLogoutLink" type="text" selector=".panel.header .customer-welcome .customer-menu .authorization-link a" timeout="30"/> </section> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCustomersAllCustomersNavigateMenuTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCustomersAllCustomersNavigateMenuTest.xml new file mode 100644 index 0000000000000..76e4407675e4c --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCustomersAllCustomersNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminCustomersAllCustomersNavigateMenuTest"> + <annotations> + <features value="Customer"/> + <stories value="Menu Navigation"/> + <title value="Admin customers all customers navigate menu test"/> + <description value="Admin should be able to navigate to Customers > All Customers"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14113"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToAllCustomerPage"> + <argument name="menuUiId" value="{{AdminMenuCustomers.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuCustomersAllCustomers.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuCustomersAllCustomers.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCustomersCustomerGroupsNavigateMenuTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCustomersCustomerGroupsNavigateMenuTest.xml new file mode 100644 index 0000000000000..13a4b1c714337 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCustomersCustomerGroupsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminCustomersCustomerGroupsNavigateMenuTest"> + <annotations> + <features value="Customer"/> + <stories value="Menu Navigation"/> + <title value="Admin customers customer groups navigate menu test"/> + <description value="Admin should be able to navigate to Customers > Customer Groups"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14115"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToCustomerGroupsPage"> + <argument name="menuUiId" value="{{AdminMenuCustomers.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuCustomersCustomerGroups.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuCustomersCustomerGroups.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCustomersNowOnlineNavigateMenuTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCustomersNowOnlineNavigateMenuTest.xml new file mode 100644 index 0000000000000..e9eb7803e01ea --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCustomersNowOnlineNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminCustomersNowOnlineNavigateMenuTest"> + <annotations> + <features value="Customer"/> + <stories value="Menu Navigation"/> + <title value="Admin customers now online navigate menu test"/> + <description value="Admin should be able to navigate to Customers > Now Online"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14114"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToNowOnlinePage"> + <argument name="menuUiId" value="{{AdminMenuCustomers.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuCustomersNowOnline.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuCustomersNowOnline.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml new file mode 100644 index 0000000000000..f39394ef312e4 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml @@ -0,0 +1,118 @@ +<?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="AllowedCountriesRestrictionApplyOnBackendTest"> + <annotations> + <title value="Country filter on Customers page when allowed countries restriction for a default website is applied"/> + <description value="Country filter on Customers page when allowed countries restriction for a default website is applied"/> + <features value="Customer"/> + <severity value="MAJOR"/> + <testCaseId value="MC-6441"/> + <useCaseId value="MAGETWO-91523"/> + <group value="customer"/> + </annotations> + <before> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <actionGroup ref="LoginActionGroup" stepKey="login"/> + <!--Create new website,store and store view--> + <comment userInput="Create new website,store and store view" stepKey="createWebsite"/> + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="goToAdminSystemStorePage"/> + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="adminCreateNewWebsite"> + <argument name="newWebsiteName" value="{{NewWebSiteData.name}}"/> + <argument name="websiteCode" value="{{NewWebSiteData.code}}"/> + </actionGroup> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="adminCreateNewStore"> + <argument name="website" value="{{NewWebSiteData.name}}"/> + <argument name="storeGroupName" value="{{NewStoreData.name}}"/> + <argument name="storeGroupCode" value="{{NewStoreData.code}}"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="adminCreateNewStoreView"> + <argument name="StoreGroup" value="NewStoreData"/> + </actionGroup> + <!--Set account sharing option - Default value is 'Per Website'--> + <comment userInput="Set account sharing option - Default value is 'Per Website'" stepKey="setAccountSharingOption"/> + <createData entity="CustomerAccountSharingDefault" stepKey="setToAccountSharingToDefault"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + </before> + <after> + <!--delete all created data and set main website country options to default--> + <comment userInput="Delete all created data and set main website country options to default" stepKey="resetConfigToDefault"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteTestWebsite"> + <argument name="websiteName" value="{{NewWebSiteData.name}}"/> + </actionGroup> + <actionGroup ref="NavigateToConfigurationGeneralPage" stepKey="navigateToConfigGeneralPage2"/> + <actionGroup ref="AdminSwitchWebsiteActionGroup" stepKey="adminSwitchWebsiteActionGroup"> + <argument name="website" value="_defaultWebsite"/> + </actionGroup> + <actionGroup ref="SetWebsiteCountryOptionsToDefaultActionGroup" stepKey="setCountryOptionsToDefault"/> + <createData entity="CustomerAccountSharingSystemValue" stepKey="setAccountSharingToSystemValue"/> + <actionGroup ref="logout" stepKey="logout"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </after> + <!--Check that all countries are allowed initially and get amount--> + <comment userInput="Check that all countries are allowed initially and get amount" stepKey="checkAllCountriesAreAllowed"/> + <actionGroup ref="NavigateToConfigurationGeneralPage" stepKey="navigateToConfigGeneralPage"/> + <createData entity="DisableAdminAccountAllowCountry" stepKey="setDefaultValueForAllowCountries"/> + <executeJS function="return document.querySelectorAll('{{CountryOptionsSection.allowedCountries}} option').length" stepKey="countriesAmount"/> + <!-- Create customer for US --> + <comment userInput="Create customer for US" stepKey="createUSCustomer"/> + <createData entity="Simple_US_CA_Customer" stepKey="createCustomer"/> + <!-- Switch to first website, allow only Canada and set Canada as default country --> + <comment userInput="Switch to first website, allow only Canada and set Canada as default country" stepKey="setCanadaAsDefaultCountry"/> + <actionGroup ref="AdminSwitchWebsiteActionGroup" stepKey="adminSwitchWebsiteActionGroup"> + <argument name="website" value="_defaultWebsite"/> + </actionGroup> + <conditionalClick selector="{{CountryOptionsSection.countryOptions}}" dependentSelector="{{CountryOptionsSection.countryOptionsOpen}}" visible="false" stepKey="clickOnStoreInformation2"/> + <waitForElementVisible selector="{{CountryOptionsSection.allowedCountries}}" stepKey="waitAllowedCountriesToBeVisible"/> + <uncheckOption selector="{{CountryOptionsSection.generalCountryDefaultInherit}}" stepKey="uncheckCheckbox1"/> + <selectOption selector="{{CountryOptionsSection.generalCountryDefault}}" userInput="Canada" stepKey="chooseCanada1"/> + <uncheckOption selector="{{CountryOptionsSection.generalCountryAllowInherit}}" stepKey="uncheckCheckbox2"/> + <selectOption selector="{{CountryOptionsSection.allowedCountries}}" userInput="Canada" stepKey="chooseCanada2"/> + <click selector="{{ContentManagementSection.Save}}" stepKey="saveConfig2"/> + <waitForPageLoad stepKey="waitForSavingSystemConfiguration2"/> + <!--Switch to second website and allow all countries except Canada--> + <comment userInput="Switch to second website and allow all countries except Canada" stepKey="switchToWebsiteAndAllowOnlyCanada"/> + <actionGroup ref="AdminSwitchWebsiteActionGroup" stepKey="adminSwitchWebsiteActionGroup2"> + <argument name="website" value="NewWebSiteData"/> + </actionGroup> + <conditionalClick selector="{{CountryOptionsSection.countryOptions}}" dependentSelector="{{CountryOptionsSection.countryOptionsOpen}}" visible="false" stepKey="clickOnStoreInformation3"/> + <waitForElementVisible selector="{{CountryOptionsSection.allowedCountries}}" stepKey="waitAllowedCountriesToBeVisible2"/> + <uncheckOption selector="{{CountryOptionsSection.generalCountryAllowInherit}}" stepKey="uncheckCheckbox3"/> + <unselectOption selector="{{CountryOptionsSection.allowedCountries}}" userInput="Canada" stepKey="unselectCanada"/> + <click selector="{{ContentManagementSection.Save}}" stepKey="saveConfig3"/> + <waitForPageLoad stepKey="waitForSavingSystemConfiguration3"/> + <amOnPage url="{{AdminEditCustomerPage.url($$createCustomer.id$$)}}" stepKey="goToCustomerEditPage"/> + <waitForPageLoad stepKey="waitPageToLoad"/> + <!--Open created customer details page and change US address to Canada address--> + <comment userInput="Open created customer details page and change US address to Canada address" stepKey="changeCustomerAddressToCanada"/> + <actionGroup ref="OpenEditCustomerAddressFromAdminActionGroup" stepKey="editCustomerAddress"> + <argument name="address" value="US_Address_CA"/> + </actionGroup> + <selectOption selector="{{AdminEditCustomerAddressesSection.country}}" userInput="Canada" stepKey="selectCountry"/> + <selectOption selector="{{AdminEditCustomerAddressesSection.state}}" userInput="Quebec" stepKey="selectState"/> + <click selector="{{AdminEditCustomerAddressesSection.save}}" stepKey="saveAddress"/> + <waitForPageLoad stepKey="waitForAddressSaved"/> + <click stepKey="saveCustomer" selector="{{AdminCustomerAccountInformationSection.saveCustomerAndContinueEdit}}"/> + <waitForPageLoad stepKey="waitForCustomersPage"/> + <!--Go to Customers grid and check that filter countries amount is the same as initial allowed countries amount--> + <comment userInput="Go to Customers grid and check that filter countries amount is the same as initial allowed countries amount" stepKey="compareCountriesAmount"/> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="goToCustomersGrid"/> + <waitForPageLoad stepKey="waitForCustomersGrid"/> + <click selector="{{AdminDataGridHeaderSection.filters}}" stepKey="openFiltersSectionOnCustomersGrid"/> + <executeJS function="var len = document.querySelectorAll('{{AdminCustomerFiltersSection.countryOptions}}').length; return len-1;" stepKey="countriesAmount2"/> + <assertEquals expected='($countriesAmount)' expectedType="integer" actual="($countriesAmount2)" stepKey="assertCountryAmounts"/> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/DeleteCustomerGroupTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/DeleteCustomerGroupTest.xml new file mode 100644 index 0000000000000..a085a67167f60 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/DeleteCustomerGroupTest.xml @@ -0,0 +1,55 @@ +<?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="DeleteCustomerGroupTest"> + <annotations> + <title value="Delete customer group entity test"/> + <description value="Delete a customer group"/> + <stories value="Delete Customer Group"/> + <testCaseId value="MC-14590" /> + <group value="customers"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="CustomCustomerGroup" stepKey="customerGroup" /> + <createData entity="UsCustomerAssignedToNewCustomerGroup" stepKey="customer"> + <requiredEntity createDataKey="customerGroup" /> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Customer Group success delete message--> + <actionGroup ref="AdminDeleteCustomerGroupActionGroup" stepKey="deleteCustomerGroup"> + <argument name="customerGroupName" value="$$customerGroup.code$$"/> + </actionGroup> + <actionGroup ref="AssertCustomerGroupNotInGridActionGroup" stepKey="assertCustomerGroupNotInGrid"> + <argument name="customerGroup" value="$$customerGroup$$" /> + </actionGroup> + + <actionGroup ref="AdminOpenCustomerEditPageActionGroup" stepKey="openCustomerEditPage"> + <argument name="customerId" value="$$customer.id$$" /> + </actionGroup> + + <actionGroup ref="AssertCustomerGroupOnCustomerFormActionGroup" stepKey="assertCustomerGroupOnCustomerForm"> + <argument name="customerGroup" value="GeneralCustomerGroup" /> + </actionGroup> + + <actionGroup ref="AdminOpenNewProductFormPageActionGroup" stepKey="openNewProductForm" /> + + <actionGroup ref="AssertCustomerGroupNotOnProductFormActionGroup" stepKey="assertCustomerGroupNotOnProductForm"> + <argument name="customerGroup" value="$$customerGroup$$" /> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php index 22c9d90c086dc..209a9b077a307 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php @@ -1238,8 +1238,7 @@ public function testInitiatePasswordResetEmailReminder() $storeId = 1; - mt_srand(mt_rand() + (100000000 * (float)microtime()) % PHP_INT_MAX); - $hash = md5(uniqid(microtime() . mt_rand(0, mt_getrandmax()), true)); + $hash = hash('sha256', microtime() . random_int(PHP_INT_MIN, PHP_INT_MAX)); $this->emailNotificationMock->expects($this->once()) ->method('passwordReminder') @@ -1263,8 +1262,7 @@ public function testInitiatePasswordResetEmailReset() $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)); + $hash = hash('sha256', microtime() . random_int(PHP_INT_MIN, PHP_INT_MAX)); $this->emailNotificationMock->expects($this->once()) ->method('passwordResetConfirmation') @@ -1288,8 +1286,7 @@ public function testInitiatePasswordResetNoTemplate() $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)); + $hash = hash('sha256', microtime() . random_int(PHP_INT_MIN, PHP_INT_MAX)); $this->prepareInitiatePasswordReset($email, $templateIdentifier, $sender, $storeId, $customerId, $hash); @@ -1610,7 +1607,7 @@ function ($string) { $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->method('isSessionExists')->willReturn(false); $this->sessionManager->expects($this->atLeastOnce())->method('getSessionId'); $visitor = $this->getMockBuilder(\Magento\Customer\Model\Visitor::class) ->disableOriginalConstructor() diff --git a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderWithDefaultAddressesTest.php b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderWithDefaultAddressesTest.php index 2fc3cdb927723..d5eaecb3ef35f 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderWithDefaultAddressesTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderWithDefaultAddressesTest.php @@ -339,7 +339,6 @@ public function testGetData(): void 'default_shipping' => 2, 'password_hash' => 'password_hash', 'rp_token' => 'rp_token', - 'confirmation' => 'confirmation', ]; $address = $this->getMockBuilder(\Magento\Customer\Model\Address::class) diff --git a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml index 6b479ad1cb290..f845d407d401a 100644 --- a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml +++ b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml @@ -174,6 +174,7 @@ </column> <column name="billing_country_id" component="Magento_Ui/js/grid/columns/select" sortOrder="80"> <settings> + <options class="Magento\Customer\Model\ResourceModel\Address\Attribute\Source\CountryWithWebsites"/> <filter>select</filter> <dataType>select</dataType> <label translate="true">Country</label> diff --git a/app/code/Magento/Customer/view/base/ui_component/customer_form.xml b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml index 7e6b7bbe9cd09..e87997dbdb5e9 100644 --- a/app/code/Magento/Customer/view/base/ui_component/customer_form.xml +++ b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml @@ -74,6 +74,17 @@ <visible>false</visible> </settings> </field> + <field name="confirmation" formElement="input"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="source" xsi:type="string">customer</item> + </item> + </argument> + <settings> + <dataType>text</dataType> + <visible>false</visible> + </settings> + </field> <field name="created_in" formElement="input"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> diff --git a/app/code/Magento/Customer/view/frontend/templates/account/customer.phtml b/app/code/Magento/Customer/view/frontend/templates/account/customer.phtml index 2bdb6ac044a92..b7b8796142de8 100644 --- a/app/code/Magento/Customer/view/frontend/templates/account/customer.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/account/customer.phtml @@ -16,7 +16,6 @@ data-toggle="dropdown" data-trigger-keypress-button="true" data-bind="scope: 'customer'"> - <span data-bind="text: customer().fullname"></span> <button type="button" class="action switch" tabindex="-1" diff --git a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls index 4e4fd1d0fa8ad..1238184075057 100644 --- a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls @@ -2,7 +2,7 @@ # See COPYING.txt for license details. type Query { - customer: Customer @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\Customer") @doc(description: "The customer query returns information about a customer account") + customer: Customer @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\Customer") @doc(description: "The customer query returns information about a customer account") @cache(cacheable: false) isEmailAvailable ( email: String! @doc(description: "The new customer email") ): IsEmailAvailableOutput @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\IsEmailAvailable") diff --git a/app/code/Magento/Dhl/etc/adminhtml/system.xml b/app/code/Magento/Dhl/etc/adminhtml/system.xml index 37b653225c7b9..7ab37de2f3658 100644 --- a/app/code/Magento/Dhl/etc/adminhtml/system.xml +++ b/app/code/Magento/Dhl/etc/adminhtml/system.xml @@ -31,7 +31,7 @@ <field id="account" translate="label" type="text" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> <label>Account Number</label> </field> - <field id="content_type" translate="label" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="content_type" translate="label comment" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> <label>Content Type (Non Domestic)</label> <comment>Whether to use Documents or NonDocuments service for non domestic shipments. (Shipments within the EU are classed as domestic)</comment> <source_model>Magento\Dhl\Model\Source\Contenttype</source_model> diff --git a/app/code/Magento/Dhl/i18n/en_US.csv b/app/code/Magento/Dhl/i18n/en_US.csv index a5532c2cea963..0e4c7a8385b93 100644 --- a/app/code/Magento/Dhl/i18n/en_US.csv +++ b/app/code/Magento/Dhl/i18n/en_US.csv @@ -61,6 +61,7 @@ Title,Title Password,Password "Account Number","Account Number" "Content Type","Content Type" +"Whether to use Documents or NonDocuments service for non domestic shipments. (Shipments within the EU are classed as domestic)","Whether to use Documents or NonDocuments service for non domestic shipments. (Shipments within the EU are classed as domestic)" "Calculate Handling Fee","Calculate Handling Fee" "Handling Applied","Handling Applied" """Per Order"" allows a single handling fee for the entire order. ""Per Package"" allows an individual handling fee for each package.","""Per Order"" allows a single handling fee for the entire order. ""Per Package"" allows an individual handling fee for each package." diff --git a/app/code/Magento/DirectoryGraphQl/Controller/HttpHeaderProcessor/CurrencyProcessor.php b/app/code/Magento/DirectoryGraphQl/Controller/HttpHeaderProcessor/CurrencyProcessor.php new file mode 100644 index 0000000000000..b076b6fe9e768 --- /dev/null +++ b/app/code/Magento/DirectoryGraphQl/Controller/HttpHeaderProcessor/CurrencyProcessor.php @@ -0,0 +1,105 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\DirectoryGraphQl\Controller\HttpHeaderProcessor; + +use Magento\GraphQl\Controller\HttpHeaderProcessorInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Framework\App\Http\Context as HttpContext; +use Magento\Framework\Session\SessionManagerInterface; +use Psr\Log\LoggerInterface; + +/** + * Process the "Currency" header entry + */ +class CurrencyProcessor implements HttpHeaderProcessorInterface +{ + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var HttpContext + */ + private $httpContext; + + /** + * @var SessionManagerInterface + */ + private $session; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param StoreManagerInterface $storeManager + * @param HttpContext $httpContext + * @param SessionManagerInterface $session + * @param LoggerInterface $logger + */ + public function __construct( + StoreManagerInterface $storeManager, + HttpContext $httpContext, + SessionManagerInterface $session, + LoggerInterface $logger + ) { + $this->storeManager = $storeManager; + $this->httpContext = $httpContext; + $this->session = $session; + $this->logger = $logger; + } + + /** + * Handle the header 'Content-Currency' value. + * + * @param string $headerValue + * @return void + */ + public function processHeaderValue(string $headerValue) : void + { + try { + if (!empty($headerValue)) { + $headerCurrency = strtoupper(ltrim(rtrim($headerValue))); + /** @var \Magento\Store\Model\Store $currentStore */ + $currentStore = $this->storeManager->getStore(); + if (in_array($headerCurrency, $currentStore->getAvailableCurrencyCodes(true))) { + $currentStore->setCurrentCurrencyCode($headerCurrency); + } else { + /** @var \Magento\Store\Model\Store $store */ + $store = $this->storeManager->getStore() ?? $this->storeManager->getDefaultStoreView(); + //skip store not found exception as it will be handled in graphql validation + $this->logger->warning(__('Currency not allowed for store %1', [$store->getCode()])); + $this->httpContext->setValue( + HttpContext::CONTEXT_CURRENCY, + $headerCurrency, + $store->getDefaultCurrency()->getCode() + ); + } + } else { + if ($this->session->getCurrencyCode()) { + /** @var \Magento\Store\Model\Store $currentStore */ + $currentStore = $this->storeManager->getStore() ?? $this->storeManager->getDefaultStoreView(); + $currentStore->setCurrentCurrencyCode($this->session->getCurrencyCode()); + } else { + /** @var \Magento\Store\Model\Store $store */ + $store = $this->storeManager->getStore() ?? $this->storeManager->getDefaultStoreView(); + $this->httpContext->setValue( + HttpContext::CONTEXT_CURRENCY, + $store->getCurrentCurrency()->getCode(), + $store->getDefaultCurrency()->getCode() + ); + } + } + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + //skip store not found exception as it will be handled in graphql validation + $this->logger->warning($e->getMessage()); + } + } +} diff --git a/app/code/Magento/DirectoryGraphQl/Controller/HttpRequestValidator/CurrencyValidator.php b/app/code/Magento/DirectoryGraphQl/Controller/HttpRequestValidator/CurrencyValidator.php new file mode 100644 index 0000000000000..7dab90802c205 --- /dev/null +++ b/app/code/Magento/DirectoryGraphQl/Controller/HttpRequestValidator/CurrencyValidator.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\DirectoryGraphQl\Controller\HttpRequestValidator; + +use Magento\Framework\App\HttpRequestInterface; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\GraphQl\Controller\HttpRequestValidatorInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Validate the "Currency" header entry + */ +class CurrencyValidator implements HttpRequestValidatorInterface +{ + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param StoreManagerInterface $storeManager + */ + public function __construct( + StoreManagerInterface $storeManager + ) { + $this->storeManager = $storeManager; + } + + /** + * Validate the header 'Content-Currency' value. + * + * @param HttpRequestInterface $request + * @return void + * @throws GraphQlInputException + */ + public function validate(HttpRequestInterface $request): void + { + try { + $headerValue = $request->getHeader('Content-Currency'); + if (!empty($headerValue)) { + $headerCurrency = strtoupper(ltrim(rtrim($headerValue))); + /** @var \Magento\Store\Model\Store $currentStore */ + $currentStore = $this->storeManager->getStore(); + if (!in_array($headerCurrency, $currentStore->getAvailableCurrencyCodes(true))) { + throw new GraphQlInputException( + __('Please correct the target currency') + ); + } + } + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + $this->storeManager->setCurrentStore(null); + throw new GraphQlInputException( + __("Requested store is not found") + ); + } + } +} diff --git a/app/code/Magento/DirectoryGraphQl/composer.json b/app/code/Magento/DirectoryGraphQl/composer.json index 0a81102a92767..d3c783e6c7bfe 100644 --- a/app/code/Magento/DirectoryGraphQl/composer.json +++ b/app/code/Magento/DirectoryGraphQl/composer.json @@ -5,11 +5,10 @@ "require": { "php": "~7.1.3||~7.2.0", "magento/module-directory": "*", + "magento/module-store": "*", + "magento/module-graph-ql": "*", "magento/framework": "*" }, - "suggest": { - "magento/module-graph-ql": "*" - }, "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/DirectoryGraphQl/etc/graphql/di.xml b/app/code/Magento/DirectoryGraphQl/etc/graphql/di.xml new file mode 100644 index 0000000000000..63f501551f535 --- /dev/null +++ b/app/code/Magento/DirectoryGraphQl/etc/graphql/di.xml @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\GraphQl\Controller\HttpRequestProcessor"> + <arguments> + <argument name="graphQlHeaders" xsi:type="array"> + <item name="Content-Currency" xsi:type="object">Magento\DirectoryGraphQl\Controller\HttpHeaderProcessor\CurrencyProcessor</item> + </argument> + <argument name="requestValidators" xsi:type="array"> + <item name="currencyValidator" xsi:type="object">Magento\DirectoryGraphQl\Controller\HttpRequestValidator\CurrencyValidator</item> + </argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls b/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls index 8da1920f9a444..6daf13f567d4b 100644 --- a/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls +++ b/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls @@ -2,9 +2,9 @@ # See COPYING.txt for license details. type Query { - currency: Currency @resolver(class: "Magento\\DirectoryGraphQl\\Model\\Resolver\\Currency") @doc(description: "The currency query returns information about store currency.") - countries: [Country] @resolver(class: "Magento\\DirectoryGraphQl\\Model\\Resolver\\Countries") @doc(description: "The countries query provides information for all countries.") - country (id: String): Country @resolver(class: "Magento\\DirectoryGraphQl\\Model\\Resolver\\Country") @doc(description: "The countries query provides information for a single country.") + currency: Currency @resolver(class: "Magento\\DirectoryGraphQl\\Model\\Resolver\\Currency") @doc(description: "The currency query returns information about store currency.") @cache(cacheable: false) + countries: [Country] @resolver(class: "Magento\\DirectoryGraphQl\\Model\\Resolver\\Countries") @doc(description: "The countries query provides information for all countries.") @cache(cacheable: false) + country (id: String): Country @resolver(class: "Magento\\DirectoryGraphQl\\Model\\Resolver\\Country") @doc(description: "The countries query provides information for a single country.") @cache(cacheable: false) } type Currency { diff --git a/app/code/Magento/Downloadable/Model/SampleRepository.php b/app/code/Magento/Downloadable/Model/SampleRepository.php index 0c7d2b96f1b53..07c7631fade13 100644 --- a/app/code/Magento/Downloadable/Model/SampleRepository.php +++ b/app/code/Magento/Downloadable/Model/SampleRepository.php @@ -23,6 +23,7 @@ /** * Class SampleRepository + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class SampleRepository implements \Magento\Downloadable\Api\SampleRepositoryInterface @@ -100,7 +101,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getList($sku) { @@ -209,6 +210,8 @@ public function save( } /** + * Save sample. + * * @param \Magento\Catalog\Api\Data\ProductInterface $product * @param SampleInterface $sample * @param bool $isGlobalScopeContent @@ -257,6 +260,8 @@ protected function saveSample( } /** + * Update sample. + * * @param \Magento\Catalog\Api\Data\ProductInterface $product * @param SampleInterface $sample * @param bool $isGlobalScopeContent @@ -308,15 +313,18 @@ protected function updateSample( $existingSample->setTitle($sample->getTitle()); } - if ($sample->getSampleType() === 'file' && $sample->getSampleFileContent() === null) { - $sample->setSampleFile($existingSample->getSampleFile()); + if ($sample->getSampleType() === 'file' + && $sample->getSampleFileContent() === null + && $sample->getSampleFile() !== null + ) { + $existingSample->setSampleFile($sample->getSampleFile()); } $this->saveSample($product, $sample, $isGlobalScopeContent); return $existingSample->getId(); } /** - * {@inheritdoc} + * @inheritdoc */ public function delete($id) { diff --git a/app/code/Magento/Downloadable/Observer/SaveDownloadableOrderItemObserver.php b/app/code/Magento/Downloadable/Observer/SaveDownloadableOrderItemObserver.php index 64305cfce9b08..7c1d2748a3e9c 100644 --- a/app/code/Magento/Downloadable/Observer/SaveDownloadableOrderItemObserver.php +++ b/app/code/Magento/Downloadable/Observer/SaveDownloadableOrderItemObserver.php @@ -9,6 +9,8 @@ use Magento\Store\Model\ScopeInterface; /** + * Saves data from order to purchased links. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class SaveDownloadableOrderItemObserver implements ObserverInterface @@ -92,9 +94,15 @@ public function execute(\Magento\Framework\Event\Observer $observer) if ($purchasedLink->getId()) { return $this; } + $storeId = $orderItem->getOrder()->getStoreId(); + $orderStatusToEnableItem = $this->_scopeConfig->getValue( + \Magento\Downloadable\Model\Link\Purchased\Item::XML_PATH_ORDER_ITEM_STATUS, + ScopeInterface::SCOPE_STORE, + $storeId + ); if (!$product) { $product = $this->_createProductModel()->setStoreId( - $orderItem->getOrder()->getStoreId() + $storeId )->load( $orderItem->getProductId() ); @@ -150,6 +158,8 @@ public function execute(\Magento\Framework\Event\Observer $observer) )->setNumberOfDownloadsBought( $numberOfDownloads )->setStatus( + \Magento\Sales\Model\Order\Item::STATUS_PENDING == $orderStatusToEnableItem ? + \Magento\Downloadable\Model\Link\Purchased\Item::LINK_STATUS_AVAILABLE : \Magento\Downloadable\Model\Link\Purchased\Item::LINK_STATUS_PENDING )->setCreatedAt( $orderItem->getCreatedAt() @@ -165,6 +175,8 @@ public function execute(\Magento\Framework\Event\Observer $observer) } /** + * Create purchased model. + * * @return \Magento\Downloadable\Model\Link\Purchased */ protected function _createPurchasedModel() @@ -173,6 +185,8 @@ protected function _createPurchasedModel() } /** + * Create product model. + * * @return \Magento\Catalog\Model\Product */ protected function _createProductModel() @@ -181,6 +195,8 @@ protected function _createProductModel() } /** + * Create purchased item model. + * * @return \Magento\Downloadable\Model\Link\Purchased\Item */ protected function _createPurchasedItemModel() @@ -189,6 +205,8 @@ protected function _createPurchasedItemModel() } /** + * Create items collection. + * * @return \Magento\Downloadable\Model\ResourceModel\Link\Purchased\Item\Collection */ protected function _createItemsCollection() diff --git a/app/code/Magento/Downloadable/Observer/UpdateLinkPurchasedObserver.php b/app/code/Magento/Downloadable/Observer/UpdateLinkPurchasedObserver.php new file mode 100644 index 0000000000000..db391ccda6866 --- /dev/null +++ b/app/code/Magento/Downloadable/Observer/UpdateLinkPurchasedObserver.php @@ -0,0 +1,80 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Downloadable\Observer; + +use Magento\Framework\Event\ObserverInterface; + +/** + * Assign Downloadable links to customer created after issuing guest order. + */ +class UpdateLinkPurchasedObserver implements ObserverInterface +{ + /** + * Core store config + * @var \Magento\Framework\App\Config\ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var \Magento\Downloadable\Model\ResourceModel\Link\Purchased\CollectionFactory + */ + private $purchasedFactory; + + /** + * @var \Magento\Framework\DataObject\Copy + */ + private $objectCopyService; + + /** + * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param \Magento\Downloadable\Model\ResourceModel\Link\Purchased\CollectionFactory $purchasedFactory + * @param \Magento\Framework\DataObject\Copy $objectCopyService + */ + public function __construct( + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, + \Magento\Downloadable\Model\ResourceModel\Link\Purchased\CollectionFactory $purchasedFactory, + \Magento\Framework\DataObject\Copy $objectCopyService + ) { + $this->scopeConfig = $scopeConfig; + $this->purchasedFactory = $purchasedFactory; + $this->objectCopyService = $objectCopyService; + } + + /** + * Re-save order data after order update. + * + * @param \Magento\Framework\Event\Observer $observer + * @return $this + */ + public function execute(\Magento\Framework\Event\Observer $observer) + { + $order = $observer->getEvent()->getOrder(); + + if (!$order->getId()) { + //order not saved in the database + return $this; + } + + $purchasedLinks = $this->purchasedFactory->create()->addFieldToFilter( + 'order_id', + ['eq' => $order->getId()] + ); + + foreach ($purchasedLinks as $linkPurchased) { + $this->objectCopyService->copyFieldsetToTarget( + \downloadable_sales_copy_order::class, + 'to_downloadable', + $order, + $linkPurchased + ); + $linkPurchased->save(); + } + + return $this; + } +} diff --git a/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml index 4bed31d9f854e..c836b0d90d13d 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml @@ -19,6 +19,21 @@ <data key="status">1</data> <data key="urlKey" unique="suffix">downloadableproduct</data> </entity> + <entity name="DownloadableProductWithOneLink" type="product"> + <data key="sku" unique="suffix">downloadableproduct</data> + <data key="type_id">downloadable</data> + <data key="attribute_set_id">4</data> + <data key="name" unique="suffix">DownloadableProduct</data> + <data key="price">50.99</data> + <data key="quantity">100</data> + <data key="weight">0</data> + <data key="status">1</data> + <data key="urlKey" unique="suffix">downloadableproduct</data> + <data key="visibility">4</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> + <requiredEntity type="downloadable_link">downloadableLink1</requiredEntity> + </entity> <entity name="DownloadableProductWithTwoLink" type="product"> <data key="sku" unique="suffix">downloadableproduct</data> <data key="type_id">downloadable</data> diff --git a/app/code/Magento/Downloadable/etc/events.xml b/app/code/Magento/Downloadable/etc/events.xml index 5a985fc33802e..21cc50ddc9669 100644 --- a/app/code/Magento/Downloadable/etc/events.xml +++ b/app/code/Magento/Downloadable/etc/events.xml @@ -11,6 +11,7 @@ </event> <event name="sales_order_save_after"> <observer name="downloadable_observer" instance="Magento\Downloadable\Observer\SetLinkStatusObserver" /> + <observer name="downloadable_observer_assign_customer" instance="Magento\Downloadable\Observer\UpdateLinkPurchasedObserver" /> </event> <event name="sales_model_service_quote_submit_success"> <observer name="checkout_type_onepage_save_order_after" instance="Magento\Downloadable\Observer\SetHasDownloadableProductsObserver" /> diff --git a/app/code/Magento/Downloadable/view/adminhtml/templates/product/composite/fieldset/downloadable.phtml b/app/code/Magento/Downloadable/view/adminhtml/templates/product/composite/fieldset/downloadable.phtml index 6ac6ecdfa6557..c2338e30ecd3b 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/templates/product/composite/fieldset/downloadable.phtml +++ b/app/code/Magento/Downloadable/view/adminhtml/templates/product/composite/fieldset/downloadable.phtml @@ -30,7 +30,7 @@ value="<?= /* @escapeNotVerified */ $_link->getId() ?>" <?= /* @escapeNotVerified */ $block->getLinkCheckedValue($_link) ?> price="<?= /* @escapeNotVerified */ $block->getCurrencyPrice($_link->getPrice()) ?>"/> <?php endif; ?> - <label for="links_<?= /* @escapeNotVerified */ $_link->getId() ?>" class="label"> + <label for="links_<?= /* @escapeNotVerified */ $_link->getId() ?>" class="label admin__field-label"> <?= $block->escapeHtml($_link->getTitle()) ?> <?php if ($_link->getSampleFile() || $_link->getSampleUrl()): ?>  (<a href="<?= /* @escapeNotVerified */ $block->getLinkSampleUrl($_link) ?>" <?= $block->getIsOpenInNewWindow()?'onclick="this.target=\'_blank\'"':'' ?>><?= /* @escapeNotVerified */ __('sample') ?></a>) diff --git a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls index e2cacdf7608d6..788a5fc601ee1 100644 --- a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls +++ b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls @@ -2,7 +2,7 @@ # See COPYING.txt for license details. type Query { - customerDownloadableProducts: CustomerDownloadableProducts @resolver(class: "Magento\\DownloadableGraphQl\\Model\\Resolver\\CustomerDownloadableProducts") @doc(description: "The query returns the contents of a customer's downloadable products") + customerDownloadableProducts: CustomerDownloadableProducts @resolver(class: "Magento\\DownloadableGraphQl\\Model\\Resolver\\CustomerDownloadableProducts") @doc(description: "The query returns the contents of a customer's downloadable products") @cache(cacheable: false) } type CustomerDownloadableProducts { diff --git a/app/code/Magento/Eav/Model/Entity/Attribute.php b/app/code/Magento/Eav/Model/Entity/Attribute.php index 23054ad613c21..e23f81607a0c0 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute.php @@ -310,9 +310,9 @@ public function beforeSave() } /** - * Save additional data + * @inheritdoc * - * @return $this + * Save additional data. */ public function afterSave() { @@ -496,9 +496,14 @@ public function getIdentities() /** * @inheritdoc * @since 100.0.7 + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $this->unsetData('attribute_set_info'); return array_diff( parent::__sleep(), @@ -509,9 +514,14 @@ public function __sleep() /** * @inheritdoc * @since 100.0.7 + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + parent::__wakeup(); $objectManager = ObjectManager::getInstance(); $this->_localeDate = $objectManager->get(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class); diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php index 7ed455eccf4e0..9ed4ac5293681 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php @@ -1404,9 +1404,14 @@ public function setExtensionAttributes(\Magento\Eav\Api\Data\AttributeExtensionI /** * @inheritdoc * @since 100.0.7 + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + return array_diff( parent::__sleep(), [ @@ -1429,9 +1434,14 @@ public function __sleep() /** * @inheritdoc * @since 100.0.7 + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + parent::__wakeup(); $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); $this->_eavConfig = $objectManager->get(\Magento\Eav\Model\Config::class); diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Source/AbstractSource.php b/app/code/Magento/Eav/Model/Entity/Attribute/Source/AbstractSource.php index 36ad026029056..dd4cd4217a127 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/Source/AbstractSource.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/Source/AbstractSource.php @@ -3,11 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Eav\Model\Entity\Attribute\Source; /** * Entity/Attribute/Model - attribute selection source abstract - * + * phpcs:disable Magento2.Classes.AbstractApi * @api * @author Magento Core Team <core@magentocommerce.com> * @SuppressWarnings(PHPMD.NumberOfChildren) @@ -65,7 +66,7 @@ public function getOptionText($value) { $options = $this->getAllOptions(); // Fixed for tax_class_id and custom_design - if (sizeof($options) > 0) { + if (count($options) > 0) { foreach ($options as $option) { if (isset($option['value']) && $option['value'] == $value) { return isset($option['label']) ? $option['label'] : $option['value']; @@ -88,7 +89,7 @@ public function getOptionText($value) public function getOptionId($value) { foreach ($this->getAllOptions() as $option) { - if (strcasecmp($option['label'], $value) == 0 || $option['value'] == $value) { + if ($this->mbStrcasecmp($option['label'], $value) == 0 || $option['value'] == $value) { return $option['value']; } } @@ -166,4 +167,20 @@ public function toOptionArray() { return $this->getAllOptions(); } + + /** + * Multibyte support strcasecmp function version. + * + * @param string $str1 + * @param string $str2 + * @return int + */ + private function mbStrcasecmp($str1, $str2) + { + $encoding = mb_internal_encoding(); + return strcmp( + mb_strtoupper($str1, $encoding), + mb_strtoupper($str2, $encoding) + ); + } } diff --git a/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php b/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php index dad420ea0b375..e50abbc11e54a 100644 --- a/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php +++ b/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php @@ -6,16 +6,15 @@ namespace Magento\Eav\Model\Entity\Collection; -use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ResourceConnection\SourceProviderInterface; use Magento\Framework\Data\Collection\AbstractDb; use Magento\Framework\DB\Select; use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; /** * Entity/Attribute/Model - collection abstract * + * phpcs:disable Magento2.Classes.AbstractApi * @api * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) @@ -127,15 +126,10 @@ abstract class AbstractCollection extends AbstractDb implements SourceProviderIn protected $_resourceHelper; /** - * @deprecated To instantiate resource models, use $resourceModelPool instead * * @var \Magento\Framework\Validator\UniversalFactory */ protected $_universalFactory; - /** - * @var ResourceModelPoolInterface - */ - private $resourceModelPool; /** * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory @@ -148,7 +142,6 @@ abstract class AbstractCollection extends AbstractDb implements SourceProviderIn * @param \Magento\Eav\Model\ResourceModel\Helper $resourceHelper * @param \Magento\Framework\Validator\UniversalFactory $universalFactory * @param mixed $connection - * @param ResourceModelPoolInterface|null $resourceModelPool * @codeCoverageIgnore * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -161,9 +154,8 @@ public function __construct( \Magento\Framework\App\ResourceConnection $resource, \Magento\Eav\Model\EntityFactory $eavEntityFactory, \Magento\Eav\Model\ResourceModel\Helper $resourceHelper, - \Magento\Framework\Validator\UniversalFactory $universalFactory = null, - \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, - ResourceModelPoolInterface $resourceModelPool = null + \Magento\Framework\Validator\UniversalFactory $universalFactory, + \Magento\Framework\DB\Adapter\AdapterInterface $connection = null ) { $this->_eventManager = $eventManager; $this->_eavConfig = $eavConfig; @@ -171,12 +163,6 @@ public function __construct( $this->_eavEntityFactory = $eavEntityFactory; $this->_resourceHelper = $resourceHelper; $this->_universalFactory = $universalFactory; - if ($resourceModelPool === null) { - $resourceModelPool = ObjectManager::getInstance()->get( - ResourceModelPoolInterface::class - ); - } - $this->resourceModelPool = $resourceModelPool; parent::__construct($entityFactory, $logger, $fetchStrategy, $connection); $this->_construct(); $this->setConnection($this->getEntity()->getConnection()); @@ -188,6 +174,7 @@ public function __construct( * Initialize collection * * @return void + * phpcs:disable Magento2.CodeAnalysis.EmptyBlock */ protected function _construct() { @@ -243,7 +230,7 @@ protected function _initSelect() protected function _init($model, $entityModel) { $this->setItemObjectClass($model); - $entity = $this->resourceModelPool->get($entityModel); + $entity = $this->_universalFactory->create($entityModel); $this->setEntity($entity); return $this; @@ -264,7 +251,7 @@ public function setEntity($entity) $this->_entity = $this->_eavEntityFactory->create()->setType($entity); } else { throw new LocalizedException( - __('The "%1" entity supplied is invalid. Verify the entity and try again.', print_r($entity, 1)) + __('The entity supplied to collection is invalid. Verify the entity and try again.') ); } return $this; @@ -298,7 +285,7 @@ public function getResource() /** * Set template object for the collection * - * @param \Magento\Framework\DataObject $object + * @param \Magento\Framework\DataObject $object * @return $this */ public function setObject($object = null) @@ -395,7 +382,7 @@ public function addAttributeToFilter($attribute, $condition = null, $joinType = if (!empty($conditionSql)) { $this->getSelect()->where($conditionSql, null, \Magento\Framework\DB\Select::TYPE_CONDITION); - $this->invalidateSize(); + $this->_totalRecords = null; } else { throw new \Magento\Framework\Exception\LocalizedException( __('Invalid attribute identifier for filter (%1)', get_class($attribute)) @@ -1061,6 +1048,7 @@ public function importFromArray($arr) $this->_items[$entityId]->addData($row); } } + $this->_setIsLoaded(); return $this; } @@ -1164,7 +1152,6 @@ public function _loadEntities($printQuery = false, $logQuery = false) * @param bool $printQuery * @param bool $logQuery * @return $this - * @throws LocalizedException * @throws \Exception * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -1371,8 +1358,8 @@ protected function _getAttributeFieldName($attributeCode) /** * Add attribute value table to the join if it wasn't added previously * - * @param string $attributeCode - * @param string $joinType inner|left + * @param string $attributeCode + * @param string $joinType inner|left * @return $this * @throws LocalizedException * @SuppressWarnings(PHPMD.NPathComplexity) @@ -1466,12 +1453,12 @@ protected function getEntityPkName(\Magento\Eav\Model\Entity\AbstractEntity $ent /** * Adding join statement to collection select instance * - * @param string $method - * @param object $attribute - * @param string $tableAlias - * @param array $condition - * @param string $fieldCode - * @param string $fieldAlias + * @param string $method + * @param object $attribute + * @param string $tableAlias + * @param array $condition + * @param string $fieldCode + * @param string $fieldAlias * @return $this */ protected function _joinAttributeToSelect($method, $attribute, $tableAlias, $condition, $fieldCode, $fieldAlias) @@ -1719,16 +1706,4 @@ public function removeAllFieldsFromSelect() { return $this->removeAttributeToSelect(); } - - /** - * Invalidates "Total Records Count". - * Invalidates saved "Total Records Count" attribute with last counting, - * so a next calling of method getSize() will query new total records count. - * - * @return void - */ - private function invalidateSize(): void - { - $this->_totalRecords = null; - } } diff --git a/app/code/Magento/Eav/Model/Entity/Collection/VersionControl/AbstractCollection.php b/app/code/Magento/Eav/Model/Entity/Collection/VersionControl/AbstractCollection.php index 2181c6bc1be05..631bfa3c2d2b5 100644 --- a/app/code/Magento/Eav/Model/Entity/Collection/VersionControl/AbstractCollection.php +++ b/app/code/Magento/Eav/Model/Entity/Collection/VersionControl/AbstractCollection.php @@ -5,10 +5,10 @@ */ namespace Magento\Eav\Model\Entity\Collection\VersionControl; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; - /** * Class Abstract Collection + * + * phpcs:disable Magento2.Classes.AbstractApi * @api * @since 100.0.2 * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -21,6 +21,7 @@ abstract class AbstractCollection extends \Magento\Eav\Model\Entity\Collection\A protected $entitySnapshot; /** + * AbstractCollection constructor. * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory * @param \Psr\Log\LoggerInterface $logger * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy @@ -30,9 +31,9 @@ abstract class AbstractCollection extends \Magento\Eav\Model\Entity\Collection\A * @param \Magento\Eav\Model\EntityFactory $eavEntityFactory * @param \Magento\Eav\Model\ResourceModel\Helper $resourceHelper * @param \Magento\Framework\Validator\UniversalFactory $universalFactory - * @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot $entitySnapshot , - * @param mixed $connection - * @param ResourceModelPoolInterface|null $resourceModelPool + * @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot $entitySnapshot + * @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection + * * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @codeCoverageIgnore */ @@ -47,8 +48,7 @@ public function __construct( \Magento\Eav\Model\ResourceModel\Helper $resourceHelper, \Magento\Framework\Validator\UniversalFactory $universalFactory, \Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot $entitySnapshot, - \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, - ResourceModelPoolInterface $resourceModelPool = null + \Magento\Framework\DB\Adapter\AdapterInterface $connection = null ) { $this->entitySnapshot = $entitySnapshot; @@ -62,8 +62,7 @@ public function __construct( $eavEntityFactory, $resourceHelper, $universalFactory, - $connection, - $resourceModelPool + $connection ); } diff --git a/app/code/Magento/Eav/Model/Entity/Type.php b/app/code/Magento/Eav/Model/Entity/Type.php index b24f86c73e8df..444d58bf546d4 100644 --- a/app/code/Magento/Eav/Model/Entity/Type.php +++ b/app/code/Magento/Eav/Model/Entity/Type.php @@ -5,9 +5,6 @@ */ namespace Magento\Eav\Model\Entity; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; - /** * Entity type model * @@ -78,16 +75,10 @@ class Type extends \Magento\Framework\Model\AbstractModel protected $_storeFactory; /** - * @deprecated To instantiate resource models, use $resourceModelPool instead * @var \Magento\Framework\Validator\UniversalFactory */ protected $_universalFactory; - /** - * @var ResourceModelPoolInterface - */ - private $resourceModelPool; - /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -98,9 +89,7 @@ class Type extends \Magento\Framework\Model\AbstractModel * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data - * @param ResourceModelPoolInterface|null $resourceModelPool * @codeCoverageIgnore - * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Framework\Model\Context $context, @@ -111,20 +100,13 @@ public function __construct( \Magento\Framework\Validator\UniversalFactory $universalFactory, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, - array $data = [], - ResourceModelPoolInterface $resourceModelPool = null + array $data = [] ) { parent::__construct($context, $registry, $resource, $resourceCollection, $data); $this->_attributeFactory = $attributeFactory; $this->_attSetFactory = $attSetFactory; $this->_storeFactory = $storeFactory; $this->_universalFactory = $universalFactory; - if ($resourceModelPool === null) { - $resourceModelPool = ObjectManager::getInstance()->get( - ResourceModelPoolInterface::class - ); - } - $this->resourceModelPool = $resourceModelPool; } /** @@ -381,7 +363,7 @@ public function getAttributeModel() */ public function getEntity() { - return $this->resourceModelPool->get($this->_data['entity_model']); + return $this->_universalFactory->create($this->_data['entity_model']); } /** diff --git a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php index 0e7a46125d872..5e7226e7a36dd 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php +++ b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php @@ -725,9 +725,14 @@ public function getValidAttributeIds($attributeIds) * * @return array * @since 100.0.7 + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $properties = parent::__sleep(); $properties = array_diff($properties, ['_storeManager']); return $properties; @@ -738,9 +743,14 @@ public function __sleep() * * @return void * @since 100.0.7 + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + parent::__wakeup(); $this->_storeManager = \Magento\Framework\App\ObjectManager::getInstance() ->get(\Magento\Store\Model\StoreManagerInterface::class); diff --git a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute/Collection.php b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute/Collection.php index cec415e513677..6fce6bd2dc44e 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute/Collection.php +++ b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute/Collection.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Eav\Model\ResourceModel\Entity\Attribute; use Magento\Eav\Model\Entity\Type; @@ -128,7 +129,7 @@ public function setEntityTypeFilter($type) /** * Specify attribute set filter * - * @param int $setId + * @param int|int[] $setId * @return $this */ public function setAttributeSetFilter($setId) @@ -183,6 +184,7 @@ public function setAttributeSetFilterBySetName($attributeSetName, $entityTypeCod /** * Specify multiple attribute sets filter + * * Result will be ordered by sort_order * * @param array $setIds @@ -225,7 +227,6 @@ public function setInAllAttributeSetsFilter(array $setIds) ->having(new \Zend_Db_Expr('COUNT(*)') . ' = ' . count($setIds)); } - //$this->getSelect()->distinct(true); $this->setOrder('is_user_defined', self::SORT_ORDER_ASC); return $this; @@ -475,7 +476,7 @@ public function addStoreLabel($storeId) } /** - * {@inheritdoc} + * @inheritdoc */ public function getSelectCountSql() { diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Collection/AbstractCollectionTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Collection/AbstractCollectionTest.php index c7af666604b39..ab5b40c56685c 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Collection/AbstractCollectionTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Collection/AbstractCollectionTest.php @@ -63,7 +63,7 @@ class AbstractCollectionTest extends \PHPUnit\Framework\TestCase /** * @var ResourceModelPoolInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $resourceModelPoolMock; + protected $validatorFactoryMock; /** * @var \Magento\Framework\DB\Statement\Pdo\Mysql|\PHPUnit_Framework_MockObject_MockObject @@ -74,11 +74,17 @@ protected function setUp() { $this->coreEntityFactoryMock = $this->createMock(\Magento\Framework\Data\Collection\EntityFactory::class); $this->loggerMock = $this->createMock(\Psr\Log\LoggerInterface::class); - $this->fetchStrategyMock = $this->createMock(FetchStrategyInterface::class); + $this->fetchStrategyMock = $this->createMock( + \Magento\Framework\Data\Collection\Db\FetchStrategyInterface::class + ); $this->eventManagerMock = $this->createMock(\Magento\Framework\Event\ManagerInterface::class); $this->configMock = $this->createMock(\Magento\Eav\Model\Config::class); + $this->coreResourceMock = $this->createMock(\Magento\Framework\App\ResourceConnection::class); $this->resourceHelperMock = $this->createMock(\Magento\Eav\Model\ResourceModel\Helper::class); + $this->validatorFactoryMock = $this->createMock(\Magento\Framework\Validator\UniversalFactory::class); $this->entityFactoryMock = $this->createMock(\Magento\Eav\Model\EntityFactory::class); + /** @var \Magento\Framework\DB\Adapter\AdapterInterface|\PHPUnit_Framework_MockObject_MockObject */ + $connectionMock = $this->createMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class); $this->statementMock = $this->createPartialMock(\Magento\Framework\DB\Statement\Pdo\Mysql::class, ['fetch']); /** @var $selectMock \Magento\Framework\DB\Select|\PHPUnit_Framework_MockObject_MockObject */ $selectMock = $this->createMock(\Magento\Framework\DB\Select::class); @@ -89,12 +95,9 @@ protected function setUp() )->will( $this->returnCallback([$this, 'getMagentoObject']) ); - /** @var \Magento\Framework\DB\Adapter\AdapterInterface|\PHPUnit_Framework_MockObject_MockObject */ - $connectionMock = $this->createMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class); $connectionMock->expects($this->any())->method('select')->will($this->returnValue($selectMock)); $connectionMock->expects($this->any())->method('query')->willReturn($this->statementMock); - $this->coreResourceMock = $this->createMock(\Magento\Framework\App\ResourceConnection::class); $this->coreResourceMock->expects( $this->any() )->method( @@ -106,11 +109,10 @@ protected function setUp() $entityMock->expects($this->any())->method('getConnection')->will($this->returnValue($connectionMock)); $entityMock->expects($this->any())->method('getDefaultAttributes')->will($this->returnValue([])); - $this->resourceModelPoolMock = $this->createMock(ResourceModelPoolInterface::class); - $this->resourceModelPoolMock->expects( + $this->validatorFactoryMock->expects( $this->any() )->method( - 'get' + 'create' )->with( 'test_entity_model' // see \Magento\Eav\Test\Unit\Model\Entity\Collection\AbstractCollectionStub )->will( @@ -126,9 +128,8 @@ protected function setUp() $this->coreResourceMock, $this->entityFactoryMock, $this->resourceHelperMock, - null, - null, - $this->resourceModelPoolMock + $this->validatorFactoryMock, + null ); } diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Collection/VersionControl/AbstractCollectionTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Collection/VersionControl/AbstractCollectionTest.php index 5b41b9b71f4b5..cce7b43786a76 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Collection/VersionControl/AbstractCollectionTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Collection/VersionControl/AbstractCollectionTest.php @@ -39,7 +39,7 @@ protected function setUp() \Magento\Eav\Test\Unit\Model\Entity\Collection\VersionControl\AbstractCollectionStub::class, [ 'entityFactory' => $this->coreEntityFactoryMock, - 'resourceModelPool' => $this->resourceModelPoolMock, + 'universalFactory' => $this->validatorFactoryMock, 'entitySnapshot' => $this->entitySnapshot ] ); diff --git a/app/code/Magento/EavGraphQl/etc/schema.graphqls b/app/code/Magento/EavGraphQl/etc/schema.graphqls index adada3030f501..0299067bd0523 100644 --- a/app/code/Magento/EavGraphQl/etc/schema.graphqls +++ b/app/code/Magento/EavGraphQl/etc/schema.graphqls @@ -2,7 +2,7 @@ # See COPYING.txt for license details. type Query { - customAttributeMetadata(attributes: [AttributeInput!]!): CustomAttributeMetadata @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\CustomAttributeMetadata") @doc(description: "The customAttributeMetadata query returns the attribute type, given an attribute code and entity type") + customAttributeMetadata(attributes: [AttributeInput!]!): CustomAttributeMetadata @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\CustomAttributeMetadata") @doc(description: "The customAttributeMetadata query returns the attribute type, given an attribute code and entity type") @cache(cacheable: false) } type CustomAttributeMetadata @doc(description: "CustomAttributeMetadata defines an array of attribute_codes and entity_types") { diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php index 255c7885e84b9..32cb85ff750c4 100644 --- a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php +++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php @@ -79,7 +79,7 @@ public function resolve(): SearchCriteria $this->builder->setPageSize($this->size); $searchCriteria = $this->builder->create(); $searchCriteria->setRequestName($this->searchRequestName); - $searchCriteria->setSortOrders($this->orders); + $searchCriteria->setSortOrders(array_merge(['relevance' => 'DESC'], $this->orders)); $searchCriteria->setCurrentPage($this->currentPage - 1); return $searchCriteria; diff --git a/app/code/Magento/Email/Model/Template.php b/app/code/Magento/Email/Model/Template.php index 8724b725ae489..ef2c4341dafa4 100644 --- a/app/code/Magento/Email/Model/Template.php +++ b/app/code/Magento/Email/Model/Template.php @@ -326,7 +326,7 @@ public function getVariablesOptionArray($withGroup = false) $optionArray[] = ['value' => '{{' . $value . '}}', 'label' => __('%1', $label)]; } if ($withGroup) { - $optionArray = ['label' => __('Template Variables'), 'value' => $optionArray]; + $optionArray = [['label' => __('Template Variables'), 'value' => $optionArray]]; } } return $optionArray; @@ -418,6 +418,8 @@ public function setOptions(array $options) } /** + * Return filter factory. + * * @return \Magento\Email\Model\Template\FilterFactory */ protected function getFilterFactory() diff --git a/app/code/Magento/Email/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Email/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..5086d74efa606 --- /dev/null +++ b/app/code/Magento/Email/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,16 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuMarketingCommunicationsEmailTemplates"> + <data key="pageTitle">Email Templates</data> + <data key="title">Email Templates</data> + <data key="dataUiId">magento-email-template</data> + </entity> +</entities> diff --git a/app/code/Magento/Email/Test/Mftf/Test/AdminMarketingEmailTemplatesNavigateMenuTest.xml b/app/code/Magento/Email/Test/Mftf/Test/AdminMarketingEmailTemplatesNavigateMenuTest.xml new file mode 100644 index 0000000000000..d512fc263ef2c --- /dev/null +++ b/app/code/Magento/Email/Test/Mftf/Test/AdminMarketingEmailTemplatesNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminMarketingEmailTemplatesNavigateMenuTest"> + <annotations> + <features value="Email"/> + <stories value="Menu Navigation"/> + <title value="Admin marketing email templates navigate menu test"/> + <description value="Admin should be able to navigate to Marketing > Email Templates"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14173"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToMarketingEmailTemplatesPage"> + <argument name="menuUiId" value="{{AdminMenuMarketing.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuMarketingCommunicationsEmailTemplates.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuMarketingCommunicationsEmailTemplates.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Email/Test/Unit/Model/TemplateTest.php b/app/code/Magento/Email/Test/Unit/Model/TemplateTest.php index 5464ca51cbe35..9599cd1fef448 100644 --- a/app/code/Magento/Email/Test/Unit/Model/TemplateTest.php +++ b/app/code/Magento/Email/Test/Unit/Model/TemplateTest.php @@ -5,21 +5,37 @@ */ namespace Magento\Email\Test\Unit\Model; +use Magento\Email\Model\Template; +use Magento\Email\Model\Template\Config; +use Magento\Email\Model\Template\FilterFactory; +use Magento\Email\Model\TemplateFactory; use Magento\Framework\App\Area; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\TemplateTypesInterface; +use Magento\Framework\Filesystem; +use Magento\Framework\Filter\FilterManager; +use Magento\Framework\Model\Context; +use Magento\Framework\Registry; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\Url; +use Magento\Framework\View\Asset\Repository; use Magento\Setup\Module\I18n\Locale; +use Magento\Store\Model\App\Emulation; use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManager; +use Magento\Theme\Model\View\Design; +use PHPUnit\Framework\TestCase; /** * Covers \Magento\Email\Model\Template * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class TemplateTest extends \PHPUnit\Framework\TestCase +class TemplateTest extends TestCase { /** - * @var \Magento\Framework\Model\Context|\PHPUnit_Framework_MockObject_MockObject + * @var Context|\PHPUnit_Framework_MockObject_MockObject */ private $context; @@ -29,13 +45,13 @@ class TemplateTest extends \PHPUnit\Framework\TestCase private $design; /** - * @var \Magento\Framework\Registry|\PHPUnit_Framework_MockObject_MockObject + * @var Registry|\PHPUnit_Framework_MockObject_MockObject * @deprecated since 2.3.0 in favor of stateful global objects elimination. */ private $registry; /** - * @var \Magento\Store\Model\App\Emulation|\PHPUnit_Framework_MockObject_MockObject + * @var Emulation|\PHPUnit_Framework_MockObject_MockObject */ private $appEmulation; @@ -45,53 +61,53 @@ class TemplateTest extends \PHPUnit\Framework\TestCase private $storeManager; /** - * @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject + * @var Filesystem|\PHPUnit_Framework_MockObject_MockObject */ private $filesystem; /** - * @var \Magento\Framework\View\Asset\Repository|\PHPUnit_Framework_MockObject_MockObject + * @var Repository|\PHPUnit_Framework_MockObject_MockObject */ private $assetRepo; /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ private $scopeConfig; /** - * @var \Magento\Email\Model\Template\FilterFactory|\PHPUnit_Framework_MockObject_MockObject + * @var FilterFactory|\PHPUnit_Framework_MockObject_MockObject */ private $filterFactory; /** - * @var \Magento\Framework\Filter\FilterManager|\PHPUnit_Framework_MockObject_MockObject + * @var FilterManager|\PHPUnit_Framework_MockObject_MockObject */ private $filterManager; /** - * @var \Magento\Framework\Url|\PHPUnit_Framework_MockObject_MockObject + * @var Url|\PHPUnit_Framework_MockObject_MockObject */ private $urlModel; /** - * @var \Magento\Email\Model\Template\Config|\PHPUnit_Framework_MockObject_MockObject + * @var Config|\PHPUnit_Framework_MockObject_MockObject */ private $emailConfig; /** - * @var \Magento\Email\Model\TemplateFactory|\PHPUnit_Framework_MockObject_MockObject + * @var TemplateFactory|\PHPUnit_Framework_MockObject_MockObject */ private $templateFactory; /** - * @var \Magento\Framework\Serialize\Serializer\Json|\PHPUnit_Framework_MockObject_MockObject + * @var Json|\PHPUnit_Framework_MockObject_MockObject */ private $serializerMock; protected function setUp() { - $this->context = $this->getMockBuilder(\Magento\Framework\Model\Context::class) + $this->context = $this->getMockBuilder(Context::class) ->disableOriginalConstructor() ->getMock(); @@ -99,11 +115,11 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); - $this->registry = $this->getMockBuilder(\Magento\Framework\Registry::class) + $this->registry = $this->getMockBuilder(Registry::class) ->disableOriginalConstructor() ->getMock(); - $this->appEmulation = $this->getMockBuilder(\Magento\Store\Model\App\Emulation::class) + $this->appEmulation = $this->getMockBuilder(Emulation::class) ->disableOriginalConstructor() ->getMock(); @@ -111,51 +127,51 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); - $this->assetRepo = $this->getMockBuilder(\Magento\Framework\View\Asset\Repository::class) + $this->assetRepo = $this->getMockBuilder(Repository::class) ->disableOriginalConstructor() ->getMock(); - $this->filesystem = $this->getMockBuilder(\Magento\Framework\Filesystem::class) + $this->filesystem = $this->getMockBuilder(Filesystem::class) ->disableOriginalConstructor() ->getMock(); - $this->scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) + $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->emailConfig = $this->getMockBuilder(\Magento\Email\Model\Template\Config::class) + $this->emailConfig = $this->getMockBuilder(Config::class) ->disableOriginalConstructor() ->getMock(); - $this->templateFactory = $this->getMockBuilder(\Magento\Email\Model\TemplateFactory::class) + $this->templateFactory = $this->getMockBuilder(TemplateFactory::class) ->disableOriginalConstructor() ->getMock(); - $this->filterManager = $this->getMockBuilder(\Magento\Framework\Filter\FilterManager::class) + $this->filterManager = $this->getMockBuilder(FilterManager::class) ->disableOriginalConstructor() ->getMock(); - $this->urlModel = $this->getMockBuilder(\Magento\Framework\Url::class) + $this->urlModel = $this->getMockBuilder(Url::class) ->disableOriginalConstructor() ->getMock(); - $this->filterFactory = $this->getMockBuilder(\Magento\Email\Model\Template\FilterFactory::class) + $this->filterFactory = $this->getMockBuilder(FilterFactory::class) ->setMethods(['create']) ->disableOriginalConstructor() ->getMock(); - $this->serializerMock = $this->getMockBuilder(\Magento\Framework\Serialize\Serializer\Json::class)->getMock(); + $this->serializerMock = $this->getMockBuilder(Json::class)->getMock(); } /** * Return the model under test with additional methods mocked. * * @param array $mockedMethods - * @return \Magento\Email\Model\Template|\PHPUnit_Framework_MockObject_MockObject + * @return Template|\PHPUnit_Framework_MockObject_MockObject */ protected function getModelMock(array $mockedMethods = []) { - return $this->getMockBuilder(\Magento\Email\Model\Template::class) + return $this->getMockBuilder(Template::class) ->setMethods(array_merge($mockedMethods, ['__wakeup', '__sleep', '_init'])) ->setConstructorArgs([ $this->context, @@ -210,7 +226,7 @@ public function testGetTemplateFilterWithEmptyValue() ->method('setStoreId') ->will($this->returnSelf()); $this->filterFactory->method('create') - ->will($this->returnValue($filterTemplate)); + ->willReturn($filterTemplate); $designConfig = $this->getMockBuilder(\Magento\Framework\DataObject::class) ->setMethods(['getStore']) ->disableOriginalConstructor() @@ -219,7 +235,7 @@ public function testGetTemplateFilterWithEmptyValue() $model = $this->getModelMock(['getUseAbsoluteLinks', 'getDesignConfig']); $model->expects($this->once()) ->method('getDesignConfig') - ->will($this->returnValue($designConfig)); + ->willReturn($designConfig); $this->assertSame($filterTemplate, $model->getTemplateFilter()); } @@ -253,7 +269,7 @@ public function testLoadDefault( $model->expects($this->once()) ->method('getDesignParams') - ->will($this->returnValue($designParams)); + ->willReturn($designParams); $templateId = 'templateId'; @@ -261,11 +277,11 @@ public function testLoadDefault( $this->emailConfig->expects($this->once()) ->method('getTemplateFilename') ->with($templateId) - ->will($this->returnValue($templateFile)); + ->willReturn($templateFile); $this->emailConfig->expects($this->once()) ->method('getTemplateType') ->with($templateId) - ->will($this->returnValue($templateType)); + ->willReturn($templateType); $modulesDir = $this->getMockBuilder(\Magento\Framework\Filesystem\Directory\ReadInterface::class) ->setMethods(['readFile', 'getRelativePath']) @@ -275,15 +291,15 @@ public function testLoadDefault( $modulesDir->expects($this->once()) ->method('getRelativePath') ->with($templateFile) - ->will($this->returnValue($relativePath)); + ->willReturn($relativePath); $modulesDir->expects($this->once()) ->method('readFile') - ->will($this->returnValue($templateText)); + ->willReturn($templateText); $this->filesystem->expects($this->once()) ->method('getDirectoryRead') ->with(DirectoryList::ROOT) - ->will($this->returnValue($modulesDir)); + ->willReturn($modulesDir); $model->loadDefault($templateId); @@ -382,10 +398,10 @@ public function testLoadByConfigPath($loadFromDatabase) $storeId = 'storeId'; $designConfig->expects($this->once()) ->method('getStore') - ->will($this->returnValue($storeId)); + ->willReturn($storeId); $model->expects($this->once()) ->method('getDesignConfig') - ->will($this->returnValue($designConfig)); + ->willReturn($designConfig); if ($loadFromDatabase) { $templateId = '1'; @@ -404,7 +420,7 @@ public function testLoadByConfigPath($loadFromDatabase) $this->scopeConfig->expects($this->once()) ->method('getValue') ->with($configPath, ScopeInterface::SCOPE_STORE, $storeId) - ->will($this->returnValue($templateId)); + ->willReturn($templateId); $model->loadByConfigPath($configPath); } @@ -447,13 +463,13 @@ public function testIsValidForSend($senderName, $senderEmail, $templateSubject, $model = $this->getModelMock(['getSenderName', 'getSenderEmail', 'getTemplateSubject']); $model->expects($this->any()) ->method('getSenderName') - ->will($this->returnValue($senderName)); + ->willReturn($senderName); $model->expects($this->any()) ->method('getSenderEmail') - ->will($this->returnValue($senderEmail)); + ->willReturn($senderEmail); $model->expects($this->any()) ->method('getTemplateSubject') - ->will($this->returnValue($templateSubject)); + ->willReturn($templateSubject); $this->assertEquals($expectedValue, $model->isValidForSend()); } @@ -503,7 +519,7 @@ public function testGetProcessedTemplateSubject() ->getMock(); $model->expects($this->once()) ->method('getTemplateFilter') - ->will($this->returnValue($filterTemplate)); + ->willReturn($filterTemplate); $model->expects($this->once()) ->method('applyDesignConfig'); @@ -515,10 +531,10 @@ public function testGetProcessedTemplateSubject() $storeId = 'storeId'; $designConfig->expects($this->once()) ->method('getStore') - ->will($this->returnValue($storeId)); + ->willReturn($storeId); $model->expects($this->once()) ->method('getDesignConfig') - ->will($this->returnValue($designConfig)); + ->willReturn($designConfig); $filterTemplate->expects($this->once()) ->method('setStoreId') @@ -528,7 +544,7 @@ public function testGetProcessedTemplateSubject() $filterTemplate->expects($this->once()) ->method('filter') ->with($templateSubject) - ->will($this->returnValue($expectedResult)); + ->willReturn($expectedResult); $variables = [ 'key' => 'value' ]; $filterTemplate->expects($this->once()) @@ -595,19 +611,21 @@ public function getVariablesOptionArrayDataProvider() 'templateVariables' => '{"store url=\"\"":"Store Url","var logo_url":"Email Logo Image Url",' . '"var customer.name":"Customer Name"}', 'expectedResult' => [ - 'label' => __('Template Variables'), - 'value' => [ - [ - 'value' => '{{store url=""}}', - 'label' => __('%1', 'Store Url'), - ], - [ - 'value' => '{{var logo_url}}', - 'label' => __('%1', 'Email Logo Image Url'), - ], - [ - 'value' => '{{var customer.name}}', - 'label' => __('%1', 'Customer Name'), + [ + 'label' => __('Template Variables'), + 'value' => [ + [ + 'value' => '{{store url=""}}', + 'label' => __('%1', 'Store Url'), + ], + [ + 'value' => '{{var logo_url}}', + 'label' => __('%1', 'Email Logo Image Url'), + ], + [ + 'value' => '{{var customer.name}}', + 'label' => __('%1', 'Customer Name'), + ], ], ], ], @@ -642,17 +660,17 @@ public function testProcessTemplate($templateId, $expectedResult) $model->expects($this->once()) ->method('applyDesignConfig') - ->will($this->returnValue(true)); + ->willReturn(true); $model->expects($this->once()) ->method('cancelDesignConfig') - ->will($this->returnValue(true)); + ->willReturn(true); $vars = [ 'key' => 'value' ]; $model->setVars($vars); $model->expects($this->once()) ->method('getProcessedTemplate') ->with($vars) - ->will($this->returnValue($expectedResult)); + ->willReturn($expectedResult); $this->assertEquals($expectedResult, $model->processTemplate()); $this->assertTrue($model->getUseAbsoluteLinks()); @@ -686,11 +704,11 @@ public function testProcessTemplateThrowsExceptionNonExistentTemplate() ]); $model->expects($this->once()) ->method('loadDefault') - ->will($this->returnValue(true)); + ->willReturn(true); $model->expects($this->once()) ->method('applyDesignConfig') - ->will($this->returnValue(true)); + ->willReturn(true); $model->processTemplate(); } @@ -704,7 +722,7 @@ public function testGetSubject() $model->expects($this->once()) ->method('getProcessedTemplateSubject') ->with($variables) - ->will($this->returnValue($expectedResult)); + ->willReturn($expectedResult); $this->assertEquals($expectedResult, $model->getSubject()); } @@ -725,30 +743,32 @@ public function testSetOptions() */ public function testGetType($templateType, $expectedResult) { - $emailConfig = $this->getMockBuilder(\Magento\Email\Model\Template\Config::class) + $emailConfig = $this->getMockBuilder(Config::class) ->setMethods(['getTemplateType']) ->disableOriginalConstructor() ->getMock(); $emailConfig->expects($this->once())->method('getTemplateType')->will($this->returnValue($templateType)); - /** @var \Magento\Email\Model\Template $model */ - $model = $this->getMockBuilder(\Magento\Email\Model\Template::class) + /** @var Template $model */ + $model = $this->getMockBuilder(Template::class) ->setMethods(['_init']) ->setConstructorArgs([ - $this->createMock(\Magento\Framework\Model\Context::class), - $this->createMock(\Magento\Theme\Model\View\Design::class), - $this->createMock(\Magento\Framework\Registry::class), - $this->createMock(\Magento\Store\Model\App\Emulation::class), - $this->createMock(\Magento\Store\Model\StoreManager::class), - $this->createMock(\Magento\Framework\View\Asset\Repository::class), - $this->createMock(\Magento\Framework\Filesystem::class), - $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class), + $this->createMock(Context::class), + $this->createMock(Design::class), + $this->createMock(Registry::class), + $this->createMock(Emulation::class), + $this->createMock(StoreManager::class), + $this->createMock(Repository::class), + $this->createMock(Filesystem::class), + $this->createMock(ScopeConfigInterface::class), $emailConfig, - $this->createMock(\Magento\Email\Model\TemplateFactory::class), - $this->createMock(\Magento\Framework\Filter\FilterManager::class), - $this->createMock(\Magento\Framework\Url::class), - $this->createMock(\Magento\Email\Model\Template\FilterFactory::class), + $this->createMock(TemplateFactory::class), + $this->createMock(FilterManager::class), + $this->createMock(Url::class), + $this->createMock(FilterFactory::class), + [], + $this->createMock(Json::class) ]) ->getMock(); diff --git a/app/code/Magento/Email/view/adminhtml/layout/adminhtml_email_template_grid_block.xml b/app/code/Magento/Email/view/adminhtml/layout/adminhtml_email_template_grid_block.xml index fa15560817dd9..87da146d8ce56 100644 --- a/app/code/Magento/Email/view/adminhtml/layout/adminhtml_email_template_grid_block.xml +++ b/app/code/Magento/Email/view/adminhtml/layout/adminhtml_email_template_grid_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.system.email.template.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">systemEmailTemplateGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\Email\Model\ResourceModel\Template\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Email\Model\ResourceModel\Template\Collection</argument> <argument name="use_ajax" xsi:type="string">1</argument> <argument name="save_parameters_in_session" xsi:type="string">1</argument> <argument name="grid_url" xsi:type="url" path="*/*/grid"> diff --git a/app/code/Magento/GraphQl/Controller/GraphQl.php b/app/code/Magento/GraphQl/Controller/GraphQl.php index c04bb7f5775a0..75b3ad277c603 100644 --- a/app/code/Magento/GraphQl/Controller/GraphQl.php +++ b/app/code/Magento/GraphQl/Controller/GraphQl.php @@ -17,17 +17,22 @@ use Magento\Framework\GraphQl\Schema\SchemaGeneratorInterface; use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\Webapi\Response; +use Magento\Framework\App\Response\Http as HttpResponse; use Magento\Framework\GraphQl\Query\Fields as QueryFields; +use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Framework\App\ObjectManager; /** * Front controller for web API GraphQL area. * * @api + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class GraphQl implements FrontControllerInterface { /** - * @var Response + * @var \Magento\Framework\Webapi\Response + * @deprecated */ private $response; @@ -47,12 +52,12 @@ class GraphQl implements FrontControllerInterface private $queryProcessor; /** - * @var \Magento\Framework\GraphQl\Exception\ExceptionFormatter + * @var ExceptionFormatter */ private $graphQlError; /** - * @var \Magento\Framework\GraphQl\Query\Resolver\ContextInterface + * @var ContextInterface */ private $resolverContext; @@ -66,15 +71,28 @@ class GraphQl implements FrontControllerInterface */ private $queryFields; + /** + * @var JsonFactory + */ + private $jsonFactory; + + /** + * @var HttpResponse + */ + private $httpResponse; + /** * @param Response $response * @param SchemaGeneratorInterface $schemaGenerator * @param SerializerInterface $jsonSerializer * @param QueryProcessor $queryProcessor - * @param \Magento\Framework\GraphQl\Exception\ExceptionFormatter $graphQlError - * @param \Magento\Framework\GraphQl\Query\Resolver\ContextInterface $resolverContext + * @param ExceptionFormatter $graphQlError + * @param ContextInterface $resolverContext * @param HttpRequestProcessor $requestProcessor * @param QueryFields $queryFields + * @param JsonFactory|null $jsonFactory + * @param HttpResponse|null $httpResponse + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( Response $response, @@ -84,7 +102,9 @@ public function __construct( ExceptionFormatter $graphQlError, ContextInterface $resolverContext, HttpRequestProcessor $requestProcessor, - QueryFields $queryFields + QueryFields $queryFields, + JsonFactory $jsonFactory = null, + HttpResponse $httpResponse = null ) { $this->response = $response; $this->schemaGenerator = $schemaGenerator; @@ -94,6 +114,8 @@ public function __construct( $this->resolverContext = $resolverContext; $this->requestProcessor = $requestProcessor; $this->queryFields = $queryFields; + $this->jsonFactory = $jsonFactory ?: ObjectManager::getInstance()->get(JsonFactory::class); + $this->httpResponse = $httpResponse ?: ObjectManager::getInstance()->get(HttpResponse::class); } /** @@ -105,14 +127,16 @@ public function __construct( public function dispatch(RequestInterface $request) : ResponseInterface { $statusCode = 200; + $jsonResult = $this->jsonFactory->create(); try { /** @var Http $request */ - $this->requestProcessor->processHeaders($request); - $data = $this->jsonSerializer->unserialize($request->getContent()); + $this->requestProcessor->validateRequest($request); + + $data = $this->getDataFromRequest($request); + $query = $data['query'] ?? ''; + $variables = $data['variables'] ?? null; - $query = isset($data['query']) ? $data['query'] : ''; - $variables = isset($data['variables']) ? $data['variables'] : null; - // We have to extract queried field names to avoid instantiation of non necessary fields in webonyx schema + // We must extract queried field names to avoid instantiation of unnecessary fields in webonyx schema // Temporal coupling is required for performance optimization $this->queryFields->setQuery($query, $variables); $schema = $this->schemaGenerator->generate(); @@ -121,17 +145,41 @@ public function dispatch(RequestInterface $request) : ResponseInterface $schema, $query, $this->resolverContext, - isset($data['variables']) ? $data['variables'] : [] + $data['variables'] ?? [] ); } catch (\Exception $error) { $result['errors'] = isset($result) && isset($result['errors']) ? $result['errors'] : []; $result['errors'][] = $this->graphQlError->create($error); $statusCode = ExceptionFormatter::HTTP_GRAPH_QL_SCHEMA_ERROR_STATUS; } - $this->response->setBody($this->jsonSerializer->serialize($result))->setHeader( - 'Content-Type', - 'application/json' - )->setHttpResponseCode($statusCode); - return $this->response; + + $jsonResult->setHttpResponseCode($statusCode); + $jsonResult->setData($result); + $jsonResult->renderResult($this->httpResponse); + return $this->httpResponse; + } + + /** + * Get data from request body or query string + * + * @param RequestInterface $request + * @return array + */ + private function getDataFromRequest(RequestInterface $request) : array + { + /** @var Http $request */ + if ($request->isPost()) { + $data = $this->jsonSerializer->unserialize($request->getContent()); + } elseif ($request->isGet()) { + $data = $request->getParams(); + $data['variables'] = isset($data['variables']) ? + $this->jsonSerializer->unserialize($data['variables']) : null; + $data['variables'] = is_array($data['variables']) ? + $data['variables'] : null; + } else { + return []; + } + + return $data; } } diff --git a/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/ContentTypeProcessor.php b/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/ContentTypeProcessor.php deleted file mode 100644 index 2270f2616e67b..0000000000000 --- a/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/ContentTypeProcessor.php +++ /dev/null @@ -1,32 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\GraphQl\Controller\HttpHeaderProcessor; - -use Magento\Framework\Exception\LocalizedException; -use Magento\GraphQl\Controller\HttpHeaderProcessorInterface; - -/** - * Processes the "Content-Type" header entry - */ -class ContentTypeProcessor implements HttpHeaderProcessorInterface -{ - /** - * Handle the mandatory application/json header - * - * {@inheritDoc} - * @throws LocalizedException - */ - public function processHeaderValue(string $headerValue) : void - { - if (!$headerValue || strpos($headerValue, 'application/json') === false) { - throw new LocalizedException( - new \Magento\Framework\Phrase('Request content type must be application/json') - ); - } - } -} diff --git a/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/StoreProcessor.php b/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/StoreProcessor.php deleted file mode 100644 index be359eafdf246..0000000000000 --- a/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/StoreProcessor.php +++ /dev/null @@ -1,55 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\GraphQl\Controller\HttpHeaderProcessor; - -use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Framework\GraphQl\Exception\GraphQlInputException; -use Magento\GraphQl\Controller\HttpHeaderProcessorInterface; -use Magento\Store\Model\StoreManagerInterface; - -/** - * Process the "Store" header entry - */ -class StoreProcessor implements HttpHeaderProcessorInterface -{ - /** - * @var StoreManagerInterface - */ - private $storeManager; - - /** - * StoreProcessor constructor. - * - * @param StoreManagerInterface $storeManager - */ - public function __construct(StoreManagerInterface $storeManager) - { - $this->storeManager = $storeManager; - } - - /** - * Handle the value of the store and set the scope - * - * {@inheritDoc} - * @throws NoSuchEntityException - */ - public function processHeaderValue(string $headerValue) : void - { - if ($headerValue) { - $storeCode = ltrim(rtrim($headerValue)); - $stores = $this->storeManager->getStores(false, true); - if (isset($stores[$storeCode])) { - $this->storeManager->setCurrentStore($storeCode); - } elseif (strtolower($storeCode) !== 'default') { - throw new GraphQlInputException( - new \Magento\Framework\Phrase('Store code %1 does not exist', [$storeCode]) - ); - } - } - } -} diff --git a/app/code/Magento/GraphQl/Controller/HttpRequestProcessor.php b/app/code/Magento/GraphQl/Controller/HttpRequestProcessor.php index 5e42b81a61981..bb29f1fa68af9 100644 --- a/app/code/Magento/GraphQl/Controller/HttpRequestProcessor.php +++ b/app/code/Magento/GraphQl/Controller/HttpRequestProcessor.php @@ -19,12 +19,19 @@ class HttpRequestProcessor */ private $headerProcessors = []; + /** + * @var HttpRequestValidatorInterface[] array + */ + private $requestValidators = []; + /** * @param HttpHeaderProcessorInterface[] $graphQlHeaders + * @param HttpRequestValidatorInterface[] $requestValidators */ - public function __construct(array $graphQlHeaders = []) + public function __construct(array $graphQlHeaders = [], array $requestValidators = []) { $this->headerProcessors = $graphQlHeaders; + $this->requestValidators = $requestValidators; } /** @@ -39,4 +46,17 @@ public function processHeaders(Http $request) : void $headerClass->processHeaderValue((string)$request->getHeader($headerName)); } } + + /** + * Validate HTTP request + * + * @param Http $request + * @return void + */ + public function validateRequest(Http $request) : void + { + foreach ($this->requestValidators as $requestValidator) { + $requestValidator->validate($request); + } + } } diff --git a/app/code/Magento/GraphQl/Controller/HttpRequestValidator/ContentTypeValidator.php b/app/code/Magento/GraphQl/Controller/HttpRequestValidator/ContentTypeValidator.php new file mode 100644 index 0000000000000..555048aac6771 --- /dev/null +++ b/app/code/Magento/GraphQl/Controller/HttpRequestValidator/ContentTypeValidator.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Controller\HttpRequestValidator; + +use Magento\Framework\App\HttpRequestInterface; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\GraphQl\Controller\HttpRequestValidatorInterface; + +/** + * Processes the "Content-Type" header entry + */ +class ContentTypeValidator implements HttpRequestValidatorInterface +{ + /** + * Handle the mandatory application/json header + * + * @param HttpRequestInterface $request + * @return void + * @throws GraphQlInputException + */ + public function validate(HttpRequestInterface $request) : void + { + $headerName = 'Content-Type'; + $requiredHeaderValue = 'application/json'; + + $headerValue = (string)$request->getHeader($headerName); + if ($request->isPost() + && strpos($headerValue, $requiredHeaderValue) === false + ) { + throw new GraphQlInputException( + new \Magento\Framework\Phrase('Request content type must be application/json') + ); + } + } +} diff --git a/app/code/Magento/GraphQl/Controller/HttpRequestValidator/HttpVerbValidator.php b/app/code/Magento/GraphQl/Controller/HttpRequestValidator/HttpVerbValidator.php new file mode 100644 index 0000000000000..300b3d4f44dca --- /dev/null +++ b/app/code/Magento/GraphQl/Controller/HttpRequestValidator/HttpVerbValidator.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Controller\HttpRequestValidator; + +use Magento\Framework\App\HttpRequestInterface; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\App\Request\Http; +use Magento\GraphQl\Controller\HttpRequestValidatorInterface; + +/** + * Validator to check HTTP verb for Graphql requests + */ +class HttpVerbValidator implements HttpRequestValidatorInterface +{ + /** + * Check if request is using correct verb for query or mutation + * + * @param HttpRequestInterface $request + * @return void + * @throws GraphQlInputException + */ + public function validate(HttpRequestInterface $request) : void + { + /** @var Http $request */ + if (false === $request->isPost()) { + $query = $request->getParam('query', ''); + // The easiest way to determine mutations without additional parsing + if (strpos(trim($query), 'mutation') === 0) { + throw new GraphQlInputException( + new \Magento\Framework\Phrase('Mutation requests allowed only for POST requests') + ); + } + } + } +} diff --git a/app/code/Magento/GraphQl/Controller/HttpRequestValidatorInterface.php b/app/code/Magento/GraphQl/Controller/HttpRequestValidatorInterface.php new file mode 100644 index 0000000000000..2d9d50569e344 --- /dev/null +++ b/app/code/Magento/GraphQl/Controller/HttpRequestValidatorInterface.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Controller; + +use Magento\Framework\App\HttpRequestInterface; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; + +/** + * Use this interface to implement a validator for a Graphql HTTP requests + */ +interface HttpRequestValidatorInterface +{ + /** + * Perform validation of request + * + * @param HttpRequestInterface $request + * @return void + * @throws GraphQlInputException + */ + public function validate(HttpRequestInterface $request) : void; +} diff --git a/app/code/Magento/GraphQl/composer.json b/app/code/Magento/GraphQl/composer.json index 3e821b0909444..3a1e8d1bfd9f4 100644 --- a/app/code/Magento/GraphQl/composer.json +++ b/app/code/Magento/GraphQl/composer.json @@ -5,12 +5,12 @@ "require": { "php": "~7.1.3||~7.2.0", "magento/module-authorization": "*", - "magento/module-store": "*", "magento/module-eav": "*", "magento/framework": "*" }, "suggest": { - "magento/module-webapi": "*" + "magento/module-webapi": "*", + "magento/module-graph-ql-cache": "*" }, "license": [ "OSL-3.0", diff --git a/app/code/Magento/GraphQl/etc/graphql/di.xml b/app/code/Magento/GraphQl/etc/graphql/di.xml index f4e6ca59364b2..03bae5c80e12c 100644 --- a/app/code/Magento/GraphQl/etc/graphql/di.xml +++ b/app/code/Magento/GraphQl/etc/graphql/di.xml @@ -27,9 +27,9 @@ </type> <type name="Magento\GraphQl\Controller\HttpRequestProcessor"> <arguments> - <argument name="graphQlHeaders" xsi:type="array"> - <item name="Content-Type" xsi:type="object">Magento\GraphQl\Controller\HttpHeaderProcessor\ContentTypeProcessor</item> - <item name="Store" xsi:type="object">Magento\GraphQl\Controller\HttpHeaderProcessor\StoreProcessor</item> + <argument name="requestValidators" xsi:type="array"> + <item name="ContentTypeValidator" xsi:type="object">Magento\GraphQl\Controller\HttpRequestValidator\ContentTypeValidator</item> + <item name="VerbValidator" xsi:type="object">Magento\GraphQl\Controller\HttpRequestValidator\HttpVerbValidator</item> </argument> </arguments> </type> diff --git a/app/code/Magento/GraphQl/etc/module.xml b/app/code/Magento/GraphQl/etc/module.xml index 4d8b2090a8514..af0f5d06d3bae 100644 --- a/app/code/Magento/GraphQl/etc/module.xml +++ b/app/code/Magento/GraphQl/etc/module.xml @@ -9,7 +9,6 @@ <module name="Magento_GraphQl" > <sequence> <module name="Magento_Authorization"/> - <module name="Magento_Store"/> <module name="Magento_Eav"/> </sequence> </module> diff --git a/app/code/Magento/GraphQl/etc/schema.graphqls b/app/code/Magento/GraphQl/etc/schema.graphqls index 7ea715097cdf3..ab8472aa56cf0 100644 --- a/app/code/Magento/GraphQl/etc/schema.graphqls +++ b/app/code/Magento/GraphQl/etc/schema.graphqls @@ -39,3 +39,181 @@ enum SortEnum @doc(description: "This enumeration indicates whether to return re type ComplexTextValue { html: String! @doc(description: "HTML format") } + +type Money @doc(description: "A Money object defines a monetary value, including a numeric value and a currency code.") { + value: Float @doc(description: "A number expressing a monetary value") + currency: CurrencyEnum @doc(description: "A three-letter currency code, such as USD or EUR") +} + +enum CurrencyEnum @doc(description: "The list of available currency codes") { + AFN + ALL + AZN + DZD + AOA + ARS + AMD + AWG + AUD + BSD + BHD + BDT + BBD + BYR + BZD + BMD + BTN + BOB + BAM + BWP + BRL + GBP + BND + BGN + BUK + BIF + KHR + CAD + CVE + CZK + KYD + GQE + CLP + CNY + COP + KMF + CDF + CRC + HRK + CUP + DKK + DJF + DOP + XCD + EGP + SVC + ERN + EEK + ETB + EUR + FKP + FJD + GMD + GEK + GEL + GHS + GIP + GTQ + GNF + GYD + HTG + HNL + HKD + HUF + ISK + INR + IDR + IRR + IQD + ILS + JMD + JPY + JOD + KZT + KES + KWD + KGS + LAK + LVL + LBP + LSL + LRD + LYD + LTL + MOP + MKD + MGA + MWK + MYR + MVR + LSM + MRO + MUR + MXN + MDL + MNT + MAD + MZN + MMK + NAD + NPR + ANG + YTL + NZD + NIC + NGN + KPW + NOK + OMR + PKR + PAB + PGK + PYG + PEN + PHP + PLN + QAR + RHD + RON + RUB + RWF + SHP + STD + SAR + RSD + SCR + SLL + SGD + SKK + SBD + SOS + ZAR + KRW + LKR + SDG + SRD + SZL + SEK + CHF + SYP + TWD + TJS + TZS + THB + TOP + TTD + TND + TMM + USD + UGX + UAH + AED + UYU + UZS + VUV + VEB + VEF + VND + CHE + CHW + XOF + WST + YER + ZMK + ZWD + TRY + AZM + ROL + TRL + XPF +} diff --git a/app/code/Magento/GraphQlCache/Controller/Plugin/GraphQl.php b/app/code/Magento/GraphQlCache/Controller/Plugin/GraphQl.php new file mode 100644 index 0000000000000..7c026e7d4136e --- /dev/null +++ b/app/code/Magento/GraphQlCache/Controller/Plugin/GraphQl.php @@ -0,0 +1,108 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQlCache\Controller\Plugin; + +use Magento\Framework\App\FrontControllerInterface; +use Magento\Framework\App\RequestInterface; +use Magento\GraphQlCache\Model\CacheableQuery; +use Magento\Framework\App\Response\Http as HttpResponse; +use Magento\Framework\Controller\ResultInterface; +use Magento\PageCache\Model\Config; +use Magento\GraphQl\Controller\HttpRequestProcessor; +use Magento\Framework\App\Response\Http as ResponseHttp; + +/** + * Plugin for handling controller after controller tags and pre-controller validation. + */ +class GraphQl +{ + /** + * @var CacheableQuery + */ + private $cacheableQuery; + + /** + * @var Config + */ + private $config; + + /** + * @var HttpResponse + */ + private $response; + + /** + * @var HttpRequestProcessor + */ + private $requestProcessor; + + /** + * @param CacheableQuery $cacheableQuery + * @param Config $config + * @param HttpResponse $response + * @param HttpRequestProcessor $requestProcessor + */ + public function __construct( + CacheableQuery $cacheableQuery, + Config $config, + HttpResponse $response, + HttpRequestProcessor $requestProcessor + ) { + $this->cacheableQuery = $cacheableQuery; + $this->config = $config; + $this->response = $response; + $this->requestProcessor = $requestProcessor; + } + + /** + * Process graphql headers + * + * @param FrontControllerInterface $subject + * @param RequestInterface $request + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeDispatch( + FrontControllerInterface $subject, + RequestInterface $request + ) { + /** @var \Magento\Framework\App\Request\Http $request */ + $this->requestProcessor->processHeaders($request); + } + + /** + * Plugin for GraphQL after render from dispatch to set tag and cache headers + * + * @param ResultInterface $subject + * @param ResultInterface $result + * @param ResponseHttp $response + * @return ResultInterface + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterRenderResult(ResultInterface $subject, ResultInterface $result, ResponseHttp $response) + { + $sendNoCacheHeaders = false; + if ($this->config->isEnabled()) { + if ($this->cacheableQuery->shouldPopulateCacheHeadersWithTags()) { + $this->response->setPublicHeaders($this->config->getTtl()); + $this->response->setHeader('X-Magento-Tags', implode(',', $this->cacheableQuery->getCacheTags()), true); + } else { + $sendNoCacheHeaders = true; + } + } else { + $sendNoCacheHeaders = true; + } + + if ($sendNoCacheHeaders) { + $this->response->setNoCacheHeaders(); + } + + return $result; + } +} diff --git a/app/code/Magento/GraphQlCache/Model/CacheableQuery.php b/app/code/Magento/GraphQlCache/Model/CacheableQuery.php new file mode 100644 index 0000000000000..451e1039eec57 --- /dev/null +++ b/app/code/Magento/GraphQlCache/Model/CacheableQuery.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQlCache\Model; + +/** + * CacheableQuery should be used as a singleton for collecting cache related info and tags of all entities. + */ +class CacheableQuery +{ + /** + * @var string[] + */ + private $cacheTags = []; + + /** + * @var bool + */ + private $cacheable = true; + + /** + * Return cache tags + * + * @return array + */ + public function getCacheTags(): array + { + return $this->cacheTags; + } + + /** + * Add Cache Tags + * + * @param array $cacheTags + * @return void + */ + public function addCacheTags(array $cacheTags): void + { + $this->cacheTags = array_merge($this->cacheTags, $cacheTags); + } + + /** + * Return if its valid to cache the response + * + * @return bool + */ + public function isCacheable(): bool + { + return $this->cacheable; + } + + /** + * Set cache validity + * + * @param bool $cacheable + */ + public function setCacheValidity(bool $cacheable): void + { + $this->cacheable = $cacheable; + } + + /** + * Check if query is cacheable and we have a list of tags to populate + * + * @return bool + */ + public function shouldPopulateCacheHeadersWithTags() : bool + { + $cacheTags = $this->getCacheTags(); + $isQueryCaheable = $this->isCacheable(); + return !empty($cacheTags) && $isQueryCaheable; + } +} diff --git a/app/code/Magento/GraphQlCache/Model/CacheableQueryHandler.php b/app/code/Magento/GraphQlCache/Model/CacheableQueryHandler.php new file mode 100644 index 0000000000000..7e624845f5682 --- /dev/null +++ b/app/code/Magento/GraphQlCache/Model/CacheableQueryHandler.php @@ -0,0 +1,94 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQlCache\Model; + +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\App\RequestInterface; +use Magento\GraphQlCache\Model\Resolver\IdentityPool; + +/** + * Handler of collecting tagging on cache. + * + * This class would be used to collect tags after each operation where we need to collect tags + * usually after data is fetched or resolved. + */ +class CacheableQueryHandler +{ + /** + * @var CacheableQuery + */ + private $cacheableQuery; + + /** + * @var RequestInterface + */ + private $request; + + /** + * @var IdentityPool + */ + private $identityPool; + + /** + * @param CacheableQuery $cacheableQuery + * @param RequestInterface $request + * @param IdentityPool $identityPool + */ + public function __construct( + CacheableQuery $cacheableQuery, + RequestInterface $request, + IdentityPool $identityPool + ) { + $this->cacheableQuery = $cacheableQuery; + $this->request = $request; + $this->identityPool = $identityPool; + } + + /** + * Set cache validity to the cacheableQuery after resolving any resolver or evaluating a promise in a query + * + * @param array $resolvedValue + * @param Field $field + * @return void + */ + public function handleCacheFromResolverResponse(array $resolvedValue, Field $field) : void + { + $cache = $field->getCache(); + $cacheIdentityClass = $cache['cacheIdentity'] ?? ''; + $cacheable = $cache['cacheable'] ?? true; + $cacheTag = $cache['cacheTag'] ?? null; + + $cacheTags = []; + if ($cacheTag && $this->request->isGet()) { + if (!empty($cacheIdentityClass)) { + $cacheIdentity = $this->identityPool->get($cacheIdentityClass); + $cacheTagIds = $cacheIdentity->getIdentities($resolvedValue); + if (!empty($cacheTagIds)) { + $cacheTags[] = $cacheTag; + foreach ($cacheTagIds as $cacheTagId) { + $cacheTags[] = $cacheTag . '_' . $cacheTagId; + } + } + } + $this->cacheableQuery->addCacheTags($cacheTags); + } + $this->setCacheValidity($cacheable); + } + + /** + * Set cache validity for the graphql request + * + * @param bool $isValid + * @return void + */ + private function setCacheValidity(bool $isValid): void + { + $cacheValidity = $this->cacheableQuery->isCacheable() && $isValid; + $this->cacheableQuery->setCacheValidity($cacheValidity); + } +} diff --git a/app/code/Magento/GraphQlCache/Model/Plugin/App/PageCache/Identifier.php b/app/code/Magento/GraphQlCache/Model/Plugin/App/PageCache/Identifier.php new file mode 100644 index 0000000000000..e02a51d2c1ca1 --- /dev/null +++ b/app/code/Magento/GraphQlCache/Model/Plugin/App/PageCache/Identifier.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQlCache\Model\Plugin\App\PageCache; + +/** + * Handles unique identifier for graphql query + */ +class Identifier +{ + /** + * @var \Magento\Framework\App\Request\Http + */ + private $request; + + /** + * @var \Magento\Framework\App\Http\Context + */ + private $context; + + /** + * @var \Magento\Framework\Serialize\Serializer\Json + */ + private $serializer; + + /** + * @var \Magento\PageCache\Model\Config + */ + private $config; + + /** + * @param \Magento\Framework\App\Request\Http $request + * @param \Magento\Framework\App\Http\Context $context + * @param \Magento\Framework\Serialize\Serializer\Json $serializer + * @param \Magento\PageCache\Model\Config $config + */ + public function __construct( + \Magento\Framework\App\Request\Http $request, + \Magento\Framework\App\Http\Context $context, + \Magento\Framework\Serialize\Serializer\Json $serializer, + \Magento\PageCache\Model\Config $config + ) { + $this->request = $request; + $this->context = $context; + $this->serializer = $serializer; + $this->config = $config; + } + + /** + * Add/Override a unique key identifier for graphql specific query and variables that skips X-Magento-Vary cookie + * + * @param \Magento\Framework\App\PageCache\Identifier $identifier + * @param string $result + * @return string + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGetValue(\Magento\Framework\App\PageCache\Identifier $identifier, string $result) : string + { + if ($this->config->isEnabled()) { + $data = [ + $this->request->isSecure(), + $this->request->getUriString(), + $this->context->getVaryString() + ]; + $result = sha1($this->serializer->serialize($data)); + } + return $result; + } +} diff --git a/app/code/Magento/GraphQlCache/Model/Plugin/Query/Resolver.php b/app/code/Magento/GraphQlCache/Model/Plugin/Query/Resolver.php new file mode 100644 index 0000000000000..54cb5559923af --- /dev/null +++ b/app/code/Magento/GraphQlCache/Model/Plugin/Query/Resolver.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQlCache\Model\Plugin\Query; + +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\GraphQl\Model\Query\Resolver\Context; +use Magento\Framework\GraphQl\Query\Resolver\Value; +use Magento\GraphQlCache\Model\CacheableQueryHandler; + +/** + * Plugin to handle cache validation that can be done after each resolver + */ +class Resolver +{ + /** + * @var CacheableQueryHandler + */ + private $cacheableQueryHandler; + + /** + * @param CacheableQueryHandler $cacheableQueryHandler + */ + public function __construct( + CacheableQueryHandler $cacheableQueryHandler + ) { + $this->cacheableQueryHandler = $cacheableQueryHandler; + } + + /** + * Set cache validity to the cacheableQuery after resolving any resolver in a query + * + * @param ResolverInterface $subject + * @param mixed|Value $resolvedValue + * @param Field $field + * @param Context $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * @return mixed + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterResolve( + ResolverInterface $subject, + $resolvedValue, + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + /** Only if array @see \Magento\Framework\GraphQl\Query\Resolver\Value */ + if (is_array($resolvedValue) && !empty($field->getCache())) { + $this->cacheableQueryHandler->handleCacheFromResolverResponse($resolvedValue, $field); + } elseif ($resolvedValue instanceof \Magento\Framework\GraphQl\Query\Resolver\Value) { + $resolvedValue->then(function () use ($resolvedValue, $field) { + if (is_array($resolvedValue->promise->result) && $field) { + $this->cacheableQueryHandler->handleCacheFromResolverResponse( + $resolvedValue->promise->result, + $field + ); + } + }); + } + return $resolvedValue; + } +} diff --git a/app/code/Magento/GraphQlCache/Model/Resolver/IdentityPool.php b/app/code/Magento/GraphQlCache/Model/Resolver/IdentityPool.php new file mode 100644 index 0000000000000..00ef8762c28ef --- /dev/null +++ b/app/code/Magento/GraphQlCache/Model/Resolver/IdentityPool.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQlCache\Model\Resolver; + +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\GraphQl\Query\Resolver\IdentityInterface; + +/** + * Pool of IdentityInterface objects + */ +class IdentityPool +{ + /** + * @var IdentityInterface[] + */ + private $identityInstances = []; + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @param ObjectManagerInterface $objectManager + */ + public function __construct(ObjectManagerInterface $objectManager) + { + $this->objectManager = $objectManager; + } + + /** + * Get an identity resolver by class name + * + * @param string $identityClass + * @return IdentityInterface + */ + public function get(string $identityClass): IdentityInterface + { + if (!isset($this->identityInstances[$identityClass])) { + $this->identityInstances[$identityClass] = $this->objectManager->create($identityClass); + } + return $this->identityInstances[$identityClass]; + } +} diff --git a/app/code/Magento/GraphQlCache/README.md b/app/code/Magento/GraphQlCache/README.md new file mode 100644 index 0000000000000..fd2f19f957c5e --- /dev/null +++ b/app/code/Magento/GraphQlCache/README.md @@ -0,0 +1,4 @@ +# GraphQl Cache + +**GraphQL Cache** provides the ability to cache GraphQL queries. +This module allows Magento's built-in cache or Varnish as the application for serving the Full Page Cache to the front end. diff --git a/app/code/Magento/GraphQlCache/Test/Unit/Model/CacheableQueryHandlerTest.php b/app/code/Magento/GraphQlCache/Test/Unit/Model/CacheableQueryHandlerTest.php new file mode 100644 index 0000000000000..9c1be89928215 --- /dev/null +++ b/app/code/Magento/GraphQlCache/Test/Unit/Model/CacheableQueryHandlerTest.php @@ -0,0 +1,99 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQlCache\Test\Unit\Model; + +use Magento\Framework\App\Request\Http; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\Resolver\IdentityInterface; +use Magento\GraphQlCache\Model\CacheableQueryHandler; +use Magento\GraphQlCache\Model\Resolver\IdentityPool; +use Magento\GraphQlCache\Model\CacheableQuery; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\TestCase; + +/** + * Test CacheableQueryHandler + */ +class CacheableQueryHandlerTest extends TestCase +{ + + private $cacheableQueryHandler; + + private $cacheableQueryMock; + + private $requestMock; + + private $identityPoolMock; + + protected function setup(): void + { + $objectManager = new ObjectManager($this); + $this->cacheableQueryMock = $this->createMock(CacheableQuery::class); + $this->requestMock = $this->createMock(Http::class); + $this->identityPoolMock = $this->createMock(IdentityPool::class); + $this->cacheableQueryHandler = $objectManager->getObject( + CacheableQueryHandler::class, + [ + 'cacheableQuery' => $this->cacheableQueryMock, + 'request' => $this->requestMock, + 'identityPool' => $this->identityPoolMock + ] + ); + } + + /** + * @param array $resolvedData + * @param array $identities + * @dataProvider resolvedDataProvider + */ + public function testhandleCacheFromResolverResponse( + array $resolvedData, + array $identities, + array $expectedCacheTags + ): void { + $cacheData = [ + 'cacheIdentity' => IdentityInterface::class, + 'cacheTag' => 'cat_p' + ]; + $fieldMock = $this->createMock(Field::class); + $mockIdentity = $this->getMockBuilder($cacheData['cacheIdentity']) + ->setMethods(['getIdentities']) + ->getMockForAbstractClass(); + + $this->requestMock->expects($this->once())->method('isGet')->willReturn(true); + $this->identityPoolMock->expects($this->once())->method('get')->willReturn($mockIdentity); + $fieldMock->expects($this->once())->method('getCache')->willReturn($cacheData); + $mockIdentity->expects($this->once()) + ->method('getIdentities') + ->with($resolvedData) + ->willReturn($identities); + $this->cacheableQueryMock->expects($this->once())->method('addCacheTags')->with($expectedCacheTags); + $this->cacheableQueryMock->expects($this->once())->method('isCacheable')->willReturn(true); + $this->cacheableQueryMock->expects($this->once())->method('setCacheValidity')->with(true); + + $this->cacheableQueryHandler->handleCacheFromResolverResponse($resolvedData, $fieldMock); + } + + /** + * @return array + */ + public function resolvedDataProvider(): array + { + return [ + [ + "resolvedData" => [ + "id" => 10, + "name" => "TesName", + "sku" => "TestSku" + ], + "identities" => [10], + "expectedCacheTags" => ["cat_p", "cat_p_10"] + ] + ]; + } +} diff --git a/app/code/Magento/GraphQlCache/composer.json b/app/code/Magento/GraphQlCache/composer.json new file mode 100644 index 0000000000000..436ae95da40f5 --- /dev/null +++ b/app/code/Magento/GraphQlCache/composer.json @@ -0,0 +1,23 @@ +{ + "name": "magento/module-graph-ql-cache", + "description": "N/A", + "type": "magento2-module", + "require": { + "php": "~7.1.3||~7.2.0", + "magento/framework": "*", + "magento/module-page-cache": "*", + "magento/module-graph-ql": "*" + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\GraphQlCache\\": "" + } + } +} diff --git a/app/code/Magento/GraphQlCache/etc/graphql/di.xml b/app/code/Magento/GraphQlCache/etc/graphql/di.xml new file mode 100644 index 0000000000000..5dd8c816ce929 --- /dev/null +++ b/app/code/Magento/GraphQlCache/etc/graphql/di.xml @@ -0,0 +1,26 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Framework\App\FrontControllerInterface"> + <plugin name="graphql-dispatch-plugin" type="Magento\GraphQlCache\Controller\Plugin\GraphQl"/> + <plugin name="front-controller-builtin-cache" type="Magento\PageCache\Model\App\FrontController\BuiltinPlugin"/> + <plugin name="front-controller-varnish-cache" type="Magento\PageCache\Model\App\FrontController\VarnishPlugin"/> + </type> + <type name="Magento\Framework\GraphQl\Query\ResolverInterface"> + <plugin name="cache" type="Magento\GraphQlCache\Model\Plugin\Query\Resolver"/> + </type> + <type name="Magento\Framework\App\PageCache\Identifier"> + <plugin name="core-app-area-design-exception-plugin" + type="Magento\GraphQlCache\Model\Plugin\App\PageCache\Identifier" sortOrder="1"/> + </type> + <type name="Magento\Framework\Controller\ResultInterface"> + <plugin name="graphql-result-plugin" type="Magento\GraphQlCache\Controller\Plugin\GraphQl"/> + <plugin name="result-builtin-cache" type="Magento\PageCache\Model\Controller\Result\BuiltinPlugin"/> + <plugin name="result-varnish-cache" type="Magento\PageCache\Model\Controller\Result\VarnishPlugin"/> + </type> +</config> diff --git a/app/code/Magento/GraphQlCache/etc/module.xml b/app/code/Magento/GraphQlCache/etc/module.xml new file mode 100644 index 0000000000000..3cbd4d8f0cb48 --- /dev/null +++ b/app/code/Magento/GraphQlCache/etc/module.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_GraphQlCache"> + <sequence> + <module name="Magento_PageCache"/> + <module name="Magento_GraphQl"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/GraphQlCache/registration.php b/app/code/Magento/GraphQlCache/registration.php new file mode 100644 index 0000000000000..2dfe717003a03 --- /dev/null +++ b/app/code/Magento/GraphQlCache/registration.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_GraphQlCache', __DIR__); diff --git a/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php b/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php index f67c9c57ee034..187fd27fa0554 100644 --- a/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php +++ b/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php @@ -210,7 +210,7 @@ public function getAssociatedProducts($product) $collection = $this->getAssociatedProductCollection( $product )->addAttributeToSelect( - ['name', 'price', 'special_price', 'special_from_date', 'special_to_date', 'tax_class_id'] + ['name', 'price', 'special_price', 'special_from_date', 'special_to_date', 'tax_class_id', 'image'] )->addFilterByRequiredOptions()->setPositionOrder()->addStoreFilter( $this->getStoreFilter($product) )->addAttributeToFilter( @@ -475,10 +475,12 @@ public function hasWeight() * @param \Magento\Catalog\Model\Product $product * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * phpcs:disable Magento2.CodeAnalysis.EmptyBlock */ public function deleteTypeSpecificData(\Magento\Catalog\Model\Product $product) { } + //phpcs:enable /** * @inheritdoc @@ -488,6 +490,7 @@ public function beforeSave($product) //clear cached associated links $product->unsetData($this->_keyAssociatedProducts); if ($product->hasData('product_options') && !empty($product->getData('product_options'))) { + //phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception('Custom options for grouped product type are not supported'); } return parent::beforeSave($product); diff --git a/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Type/Grouped/AssociatedProductsCollection.php b/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Type/Grouped/AssociatedProductsCollection.php index 251dca8ef1615..cbe1ef26c54b0 100644 --- a/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Type/Grouped/AssociatedProductsCollection.php +++ b/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Type/Grouped/AssociatedProductsCollection.php @@ -7,17 +7,11 @@ */ namespace Magento\GroupedProduct\Model\ResourceModel\Product\Type\Grouped; -use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; -use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; -use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; -use Magento\Framework\EntityManager\MetadataPool; -use Magento\Framework\Indexer\DimensionFactory; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; - /** * Associated products collection. * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class AssociatedProductsCollection extends \Magento\Catalog\Model\ResourceModel\Product\Link\Product\Collection { @@ -61,12 +55,6 @@ class AssociatedProductsCollection extends \Magento\Catalog\Model\ResourceModel\ * @param \Magento\Catalog\Model\ProductTypes\ConfigInterface $config * @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection * - * @param ProductLimitationFactory|null $productLimitationFactory - * @param MetadataPool|null $metadataPool - * @param TableMaintainer|null $tableMaintainer - * @param PriceTableResolver|null $priceTableResolver - * @param DimensionFactory|null $dimensionFactory - * @param ResourceModelPoolInterface|null $resourceModelPool * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -91,13 +79,7 @@ public function __construct( \Magento\Customer\Api\GroupManagementInterface $groupManagement, \Magento\Framework\Registry $coreRegistry, \Magento\Catalog\Model\ProductTypes\ConfigInterface $config, - \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, - ProductLimitationFactory $productLimitationFactory = null, - MetadataPool $metadataPool = null, - TableMaintainer $tableMaintainer = null, - PriceTableResolver $priceTableResolver = null, - DimensionFactory $dimensionFactory = null, - ResourceModelPoolInterface $resourceModelPool = null + \Magento\Framework\DB\Adapter\AdapterInterface $connection = null ) { $this->_coreRegistry = $coreRegistry; $this->_config = $config; @@ -121,13 +103,7 @@ public function __construct( $customerSession, $dateTime, $groupManagement, - $connection, - $productLimitationFactory, - $metadataPool, - $tableMaintainer, - $priceTableResolver, - $dimensionFactory, - $resourceModelPool + $connection ); } diff --git a/app/code/Magento/GroupedProduct/Test/Unit/Ui/DataProvider/Product/Form/Modifier/GroupedTest.php b/app/code/Magento/GroupedProduct/Test/Unit/Ui/DataProvider/Product/Form/Modifier/GroupedTest.php index 327b47d4a75d8..ad4b86351a66c 100644 --- a/app/code/Magento/GroupedProduct/Test/Unit/Ui/DataProvider/Product/Form/Modifier/GroupedTest.php +++ b/app/code/Magento/GroupedProduct/Test/Unit/Ui/DataProvider/Product/Form/Modifier/GroupedTest.php @@ -21,6 +21,9 @@ use Magento\GroupedProduct\Model\Product\Type\Grouped as GroupedProductType; use Magento\GroupedProduct\Ui\DataProvider\Product\Form\Modifier\Grouped; use Magento\Store\Api\Data\StoreInterface; +use Magento\Catalog\Model\Product; +use Magento\GroupedProduct\Model\Product\Link\CollectionProvider\Grouped as GroupedProducts; +use Magento\Catalog\Api\Data\ProductLinkInterfaceFactory; /** * Class GroupedTest @@ -82,23 +85,38 @@ class GroupedTest extends AbstractModifierTest */ protected $storeMock; + /** + * @var GroupedProducts|\PHPUnit_Framework_MockObject_MockObject + */ + private $groupedProductsMock; + + /** + * @var ProductLinkInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $productLinkFactoryMock; + + /** + * @inheritdoc + */ protected function setUp() { $this->objectManager = new ObjectManager($this); $this->locatorMock = $this->getMockBuilder(LocatorInterface::class) ->getMockForAbstractClass(); - $this->productMock = $this->getMockBuilder(ProductInterface::class) + $this->productMock = $this->getMockBuilder(Product::class) ->setMethods(['getId', 'getTypeId']) - ->getMockForAbstractClass(); + ->disableOriginalConstructor() + ->getMock(); $this->productMock->expects($this->any()) ->method('getId') ->willReturn(self::PRODUCT_ID); $this->productMock->expects($this->any()) ->method('getTypeId') ->willReturn(GroupedProductType::TYPE_CODE); - $this->linkedProductMock = $this->getMockBuilder(ProductInterface::class) + $this->linkedProductMock = $this->getMockBuilder(Product::class) ->setMethods(['getId', 'getName', 'getPrice']) - ->getMockForAbstractClass(); + ->disableOriginalConstructor() + ->getMock(); $this->linkedProductMock->expects($this->any()) ->method('getId') ->willReturn(self::LINKED_PRODUCT_ID); @@ -135,7 +153,7 @@ protected function setUp() $this->linkRepositoryMock->expects($this->any()) ->method('getList') ->with($this->productMock) - ->willReturn([$this->linkMock]); + ->willReturn([$this->linkedProductMock]); $this->productRepositoryMock = $this->getMockBuilder(ProductRepositoryInterface::class) ->setMethods(['get']) ->getMockForAbstractClass(); @@ -155,7 +173,7 @@ protected function setUp() } /** - * {@inheritdoc} + * @inheritdoc */ protected function createModel() { @@ -169,6 +187,16 @@ protected function createModel() ->setMethods(['init', 'getUrl']) ->disableOriginalConstructor() ->getMock(); + + $this->groupedProductsMock = $this->getMockBuilder(GroupedProducts::class) + ->setMethods(['getLinkedProducts']) + ->disableOriginalConstructor() + ->getMock(); + $this->productLinkFactoryMock = $this->getMockBuilder(ProductLinkInterfaceFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->imageHelperMock->expects($this->any()) ->method('init') ->willReturn($this->imageHelperMock); @@ -189,16 +217,23 @@ protected function createModel() 'localeCurrency' => $this->currencyMock, 'imageHelper' => $this->imageHelperMock, 'attributeSetRepository' => $this->attributeSetRepositoryMock, + 'groupedProducts' => $this->groupedProductsMock, + 'productLinkFactory' => $this->productLinkFactoryMock, ]); } + /** + * Assert array has key + * + * @return void + */ public function testModifyMeta() { $this->assertArrayHasKey(Grouped::GROUP_GROUPED, $this->getModel()->modifyMeta([])); } /** - * {@inheritdoc} + * @inheritdoc */ public function testModifyData() { @@ -226,6 +261,42 @@ public function testModifyData() ], ], ]; - $this->assertSame($expectedData, $this->getModel()->modifyData([])); + $model = $this->getModel(); + $linkedProductMock = $this->getMockBuilder(Product::class) + ->setMethods(['getId', 'getName', 'getPrice', 'getSku', 'getImage', 'getPosition', 'getQty']) + ->disableOriginalConstructor() + ->getMock(); + $linkedProductMock->expects($this->once()) + ->method('getId') + ->willReturn(self::LINKED_PRODUCT_ID); + $linkedProductMock->expects($this->once()) + ->method('getName') + ->willReturn(self::LINKED_PRODUCT_NAME); + $linkedProductMock->expects($this->once()) + ->method('getPrice') + ->willReturn(self::LINKED_PRODUCT_PRICE); + $linkedProductMock->expects($this->once()) + ->method('getSku') + ->willReturn(self::LINKED_PRODUCT_SKU); + $linkedProductMock->expects($this->once()) + ->method('getImage') + ->willReturn(''); + $linkedProductMock->expects($this->exactly(2)) + ->method('getPosition') + ->willReturn(self::LINKED_PRODUCT_POSITION); + $linkedProductMock->expects($this->once()) + ->method('getQty') + ->willReturn(self::LINKED_PRODUCT_QTY); + $this->groupedProductsMock->expects($this->once()) + ->method('getLinkedProducts') + ->willReturn([$linkedProductMock]); + $linkMock = $this->getMockBuilder(ProductLinkInterface::class) + ->getMockForAbstractClass(); + + $this->productLinkFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($linkMock); + + $this->assertSame($expectedData, $model->modifyData([])); } } diff --git a/app/code/Magento/GroupedProduct/Ui/DataProvider/Product/Form/Modifier/Grouped.php b/app/code/Magento/GroupedProduct/Ui/DataProvider/Product/Form/Modifier/Grouped.php index fff84d9221c8a..2ea622c1c2b8f 100644 --- a/app/code/Magento/GroupedProduct/Ui/DataProvider/Product/Form/Modifier/Grouped.php +++ b/app/code/Magento/GroupedProduct/Ui/DataProvider/Product/Form/Modifier/Grouped.php @@ -21,6 +21,9 @@ use Magento\Eav\Api\AttributeSetRepositoryInterface; use Magento\Catalog\Model\Product\Attribute\Source\Status; use Magento\Framework\Locale\CurrencyInterface; +use Magento\GroupedProduct\Model\Product\Link\CollectionProvider\Grouped as GroupedProducts; +use Magento\Framework\App\ObjectManager; +use Magento\Catalog\Api\Data\ProductLinkInterfaceFactory; /** * Data provider for Grouped products @@ -99,6 +102,16 @@ class Grouped extends AbstractModifier */ private static $codeQty = 'qty'; + /** + * @var GroupedProducts + */ + private $groupedProducts; + + /** + * @var ProductLinkInterfaceFactory + */ + private $productLinkFactory; + /** * @param LocatorInterface $locator * @param UrlInterface $urlBuilder @@ -109,6 +122,9 @@ class Grouped extends AbstractModifier * @param AttributeSetRepositoryInterface $attributeSetRepository * @param CurrencyInterface $localeCurrency * @param array $uiComponentsConfig + * @param GroupedProducts $groupedProducts + * @param \Magento\Catalog\Api\Data\ProductLinkInterfaceFactory|null $productLinkFactory + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( LocatorInterface $locator, @@ -119,7 +135,9 @@ public function __construct( Status $status, AttributeSetRepositoryInterface $attributeSetRepository, CurrencyInterface $localeCurrency, - array $uiComponentsConfig = [] + array $uiComponentsConfig = [], + GroupedProducts $groupedProducts = null, + \Magento\Catalog\Api\Data\ProductLinkInterfaceFactory $productLinkFactory = null ) { $this->locator = $locator; $this->urlBuilder = $urlBuilder; @@ -130,6 +148,11 @@ public function __construct( $this->status = $status; $this->localeCurrency = $localeCurrency; $this->uiComponentsConfig = array_replace_recursive($this->uiComponentsConfig, $uiComponentsConfig); + $this->groupedProducts = $groupedProducts ?: ObjectManager::getInstance()->get( + \Magento\GroupedProduct\Model\Product\Link\CollectionProvider\Grouped::class + ); + $this->productLinkFactory = $productLinkFactory ?: ObjectManager::getInstance() + ->get(\Magento\Catalog\Api\Data\ProductLinkInterfaceFactory::class); } /** @@ -143,18 +166,15 @@ public function modifyData(array $data) if ($modelId) { $storeId = $this->locator->getStore()->getId(); $data[$product->getId()]['links'][self::LINK_TYPE] = []; - $linkedItems = $this->productLinkRepository->getList($product); + $linkedItems = $this->groupedProducts->getLinkedProducts($product); usort($linkedItems, function ($a, $b) { return $a->getPosition() <=> $b->getPosition(); }); + $productLink = $this->productLinkFactory->create(); foreach ($linkedItems as $index => $linkItem) { - if ($linkItem->getLinkType() !== self::LINK_TYPE) { - continue; - } /** @var \Magento\Catalog\Api\Data\ProductInterface $linkedProduct */ - $linkedProduct = $this->productRepository->get($linkItem->getLinkedProductSku(), false, $storeId); $linkItem->setPosition($index); - $data[$modelId]['links'][self::LINK_TYPE][] = $this->fillData($linkedProduct, $linkItem); + $data[$modelId]['links'][self::LINK_TYPE][] = $this->fillData($linkItem, $productLink); } $data[$modelId][self::DATA_SOURCE_DEFAULT]['current_store_id'] = $storeId; } @@ -167,6 +187,7 @@ public function modifyData(array $data) * @param ProductInterface $linkedProduct * @param ProductLinkInterface $linkItem * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function fillData(ProductInterface $linkedProduct, ProductLinkInterface $linkItem) { @@ -176,12 +197,15 @@ protected function fillData(ProductInterface $linkedProduct, ProductLinkInterfac return [ 'id' => $linkedProduct->getId(), 'name' => $linkedProduct->getName(), - 'sku' => $linkItem->getLinkedProductSku(), + 'sku' => $linkedProduct->getSku(), 'price' => $currency->toCurrency(sprintf("%f", $linkedProduct->getPrice())), - 'qty' => $linkItem->getExtensionAttributes()->getQty(), - 'position' => $linkItem->getPosition(), - 'positionCalculated' => $linkItem->getPosition(), - 'thumbnail' => $this->imageHelper->init($linkedProduct, 'product_listing_thumbnail')->getUrl(), + 'qty' => $linkedProduct->getQty(), + 'position' => $linkedProduct->getPosition(), + 'positionCalculated' => $linkedProduct->getPosition(), + 'thumbnail' => $this->imageHelper + ->init($linkedProduct, 'product_listing_thumbnail') + ->setImageFile($linkedProduct->getImage()) + ->getUrl(), 'type_id' => $linkedProduct->getTypeId(), 'status' => $this->status->getOptionText($linkedProduct->getStatus()), 'attribute_set' => $this->attributeSetRepository diff --git a/app/code/Magento/GroupedProduct/view/adminhtml/layout/groupedproduct_popup_grid.xml b/app/code/Magento/GroupedProduct/view/adminhtml/layout/groupedproduct_popup_grid.xml index 503404c6cb3cf..fab7851df5bc4 100644 --- a/app/code/Magento/GroupedProduct/view/adminhtml/layout/groupedproduct_popup_grid.xml +++ b/app/code/Magento/GroupedProduct/view/adminhtml/layout/groupedproduct_popup_grid.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="catalog.product.edit.tab.super.group.popup" as="grid"> <arguments> <argument name="id" xsi:type="string">grouped_grid_popup</argument> - <argument name="dataSource" xsi:type="object">Magento\GroupedProduct\Model\ResourceModel\Product\Type\Grouped\AssociatedProductsCollection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\GroupedProduct\Model\ResourceModel\Product\Type\Grouped\AssociatedProductsCollection</argument> <argument name="use_ajax" xsi:type="string">1</argument> <argument name="default_sort" xsi:type="string">id</argument> <argument name="default_dir" xsi:type="string">ASC</argument> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/ImportExport/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..c09cd192d05c7 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,21 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuSystemDataTransferExport"> + <data key="pageTitle">Export</data> + <data key="title">Export</data> + <data key="dataUiId">magento-importexport-system-convert-export</data> + </entity> + <entity name="AdminMenuSystemDataTransferImport"> + <data key="pageTitle">Import</data> + <data key="title">Import</data> + <data key="dataUiId">magento-importexport-system-convert-import</data> + </entity> +</entities> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminExportPageNavigateMenuTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminExportPageNavigateMenuTest.xml new file mode 100644 index 0000000000000..e8fb12ca521c2 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminExportPageNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminExportPageNavigateMenuTest"> + <annotations> + <features value="ImportExport"/> + <stories value="Menu Navigation"/> + <title value="Admin export page navigate menu test"/> + <description value="Admin should be able to navigate to System > Export"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14157"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToExportPage"> + <argument name="menuUiId" value="{{AdminMenuSystem.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSystemDataTransferExport.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSystemDataTransferExport.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminSystemImportNavigateMenuTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminSystemImportNavigateMenuTest.xml new file mode 100644 index 0000000000000..9913933d857a8 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminSystemImportNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminSystemImportNavigateMenuTest"> + <annotations> + <features value="ImportExport"/> + <stories value="Menu Navigation"/> + <title value="Admin system import navigate menu test"/> + <description value="Admin should be able to navigate to System > Import"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14156"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToImportPage"> + <argument name="menuUiId" value="{{AdminMenuSystem.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSystemDataTransferImport.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSystemDataTransferImport.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/ImportExport/view/adminhtml/layout/adminhtml_history_grid_block.xml b/app/code/Magento/ImportExport/view/adminhtml/layout/adminhtml_history_grid_block.xml index 02fc198cb0ada..51e5827c8ab98 100644 --- a/app/code/Magento/ImportExport/view/adminhtml/layout/adminhtml_history_grid_block.xml +++ b/app/code/Magento/ImportExport/view/adminhtml/layout/adminhtml_history_grid_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.import.history.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">importHistoryGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\ImportExport\Model\ResourceModel\History\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\ImportExport\Model\ResourceModel\History\Collection</argument> <argument name="default_sort" xsi:type="string">history_id</argument> <argument name="default_dir" xsi:type="string">desc</argument> </arguments> diff --git a/app/code/Magento/Indexer/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Indexer/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..b74d521f2cb36 --- /dev/null +++ b/app/code/Magento/Indexer/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,16 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuSystemToolsIndexManagement"> + <data key="pageTitle">Index Management</data> + <data key="title">Index Management</data> + <data key="dataUiId">magento-indexer-system-index</data> + </entity> +</entities> diff --git a/app/code/Magento/Indexer/Test/Mftf/Test/AdminSystemIndexManagementNavigateMenuTest.xml b/app/code/Magento/Indexer/Test/Mftf/Test/AdminSystemIndexManagementNavigateMenuTest.xml new file mode 100644 index 0000000000000..140c93f7f2b48 --- /dev/null +++ b/app/code/Magento/Indexer/Test/Mftf/Test/AdminSystemIndexManagementNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminSystemIndexManagementNavigateMenuTest"> + <annotations> + <features value="Indexer"/> + <stories value="Menu Navigation"/> + <title value="Admin system index management navigate menu test"/> + <description value="Admin should be able to navigate to System > Index Management"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14127"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToIndexManagementPage"> + <argument name="menuUiId" value="{{AdminMenuSystem.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSystemToolsIndexManagement.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSystemToolsIndexManagement.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Indexer/view/adminhtml/layout/indexer_indexer_list_grid.xml b/app/code/Magento/Indexer/view/adminhtml/layout/indexer_indexer_list_grid.xml index bf6b2351f75f1..f1099c4133bca 100644 --- a/app/code/Magento/Indexer/view/adminhtml/layout/indexer_indexer_list_grid.xml +++ b/app/code/Magento/Indexer/view/adminhtml/layout/indexer_indexer_list_grid.xml @@ -13,7 +13,7 @@ <argument name="use_ajax" xsi:type="string">0</argument> <argument name="pager_visibility" xsi:type="string">0</argument> <argument name="id" xsi:type="string">gridIndexer</argument> - <argument name="dataSource" xsi:type="object">Magento\Indexer\Ui\DataProvider\Indexer\DataCollection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Indexer\Ui\DataProvider\Indexer\DataCollection</argument> </arguments> <block class="Magento\Backend\Block\Widget\Grid\Massaction" name="adminhtml.indexer.grid.grid.massaction" as="grid.massaction"> <arguments> diff --git a/app/code/Magento/Integration/Model/AdminTokenService.php b/app/code/Magento/Integration/Model/AdminTokenService.php index 5a030325e9fbd..7726ff979c6d7 100644 --- a/app/code/Magento/Integration/Model/AdminTokenService.php +++ b/app/code/Magento/Integration/Model/AdminTokenService.php @@ -72,7 +72,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function createAdminAccessToken($username, $password) { @@ -110,7 +110,7 @@ public function revokeAdminAccessToken($adminId) { $tokenCollection = $this->tokenModelCollectionFactory->create()->addFilterByAdminId($adminId); if ($tokenCollection->getSize() == 0) { - throw new LocalizedException(__('This user has no tokens.')); + return true; } try { foreach ($tokenCollection as $token) { diff --git a/app/code/Magento/Integration/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Integration/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..be5b3f16ad3c5 --- /dev/null +++ b/app/code/Magento/Integration/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,16 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuSystemExtensionsIntegrations"> + <data key="pageTitle">Integrations</data> + <data key="title">Integrations</data> + <data key="dataUiId">magento-integration-system-integrations</data> + </entity> +</entities> diff --git a/app/code/Magento/Integration/Test/Mftf/Test/AdminSystemIntegrationsNavigateMenuTest.xml b/app/code/Magento/Integration/Test/Mftf/Test/AdminSystemIntegrationsNavigateMenuTest.xml new file mode 100644 index 0000000000000..3bd149d222c0e --- /dev/null +++ b/app/code/Magento/Integration/Test/Mftf/Test/AdminSystemIntegrationsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminSystemIntegrationsNavigateMenuTest"> + <annotations> + <features value="Integration"/> + <stories value="Menu Navigation"/> + <title value="Admin system integrations navigate menu test"/> + <description value="Admin should be able to navigate to System > Integrations"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14149"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToIntegrationsPage"> + <argument name="menuUiId" value="{{AdminMenuSystem.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSystemExtensionsIntegrations.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSystemExtensionsIntegrations.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Integration/Test/Unit/Model/AdminTokenServiceTest.php b/app/code/Magento/Integration/Test/Unit/Model/AdminTokenServiceTest.php index e0b1113e80d8d..83efe4074e15f 100644 --- a/app/code/Magento/Integration/Test/Unit/Model/AdminTokenServiceTest.php +++ b/app/code/Magento/Integration/Test/Unit/Model/AdminTokenServiceTest.php @@ -8,6 +8,9 @@ use Magento\Integration\Model\Oauth\Token; +/** + * Test for Magento\Integration\Model\AdminTokenService class. + */ class AdminTokenServiceTest extends \PHPUnit\Framework\TestCase { /** \Magento\Integration\Model\AdminTokenService */ @@ -99,10 +102,6 @@ public function testRevokeAdminAccessToken() $this->assertTrue($this->_tokenService->revokeAdminAccessToken($adminId)); } - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage This user has no tokens. - */ public function testRevokeAdminAccessTokenWithoutAdminId() { $this->_tokenModelCollectionMock->expects($this->once()) diff --git a/app/code/Magento/Integration/etc/db_schema.xml b/app/code/Magento/Integration/etc/db_schema.xml index f1824fadb97fd..cbf43d79b2cf6 100644 --- a/app/code/Magento/Integration/etc/db_schema.xml +++ b/app/code/Magento/Integration/etc/db_schema.xml @@ -107,7 +107,7 @@ comment="Oauth consumer"/> <column xsi:type="timestamp" name="created_at" on_update="false" nullable="false" default="CURRENT_TIMESTAMP" comment="Creation Time"/> - <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="0" + <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="CURRENT_TIMESTAMP" comment="Update Time"/> <column xsi:type="smallint" name="setup_type" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Integration type - manual or config file"/> diff --git a/app/code/Magento/Integration/view/adminhtml/layout/adminhtml_integration_grid_block.xml b/app/code/Magento/Integration/view/adminhtml/layout/adminhtml_integration_grid_block.xml index 506f836f99514..43b67d6904f1a 100644 --- a/app/code/Magento/Integration/view/adminhtml/layout/adminhtml_integration_grid_block.xml +++ b/app/code/Magento/Integration/view/adminhtml/layout/adminhtml_integration_grid_block.xml @@ -13,7 +13,7 @@ <block class="Magento\Integration\Block\Adminhtml\Integration\Grid" name="integration.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">integrationGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\Integration\Model\ResourceModel\Integration\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Integration\Model\ResourceModel\Integration\Collection</argument> <argument name="use_ajax" xsi:type="string">1</argument> <argument name="default_sort" xsi:type="string">integration_id</argument> <argument name="default_dir" xsi:type="string">asc</argument> diff --git a/app/code/Magento/MessageQueue/Model/CallbackInvoker.php b/app/code/Magento/MessageQueue/Model/CallbackInvoker.php deleted file mode 100644 index f6305363fc1a6..0000000000000 --- a/app/code/Magento/MessageQueue/Model/CallbackInvoker.php +++ /dev/null @@ -1,66 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\MessageQueue\Model; - -use Magento\Framework\MessageQueue\CallbackInvokerInterface; -use Magento\Framework\MessageQueue\QueueInterface; -use Magento\MessageQueue\Api\PoisonPillCompareInterface; -use Magento\MessageQueue\Api\PoisonPillReadInterface; - -/** - * Callback invoker - */ -class CallbackInvoker implements CallbackInvokerInterface -{ - /** - * @var PoisonPillReadInterface $poisonPillRead - */ - private $poisonPillRead; - - /** - * @var int $poisonPillVersion - */ - private $poisonPillVersion; - - /** - * @var PoisonPillCompareInterface - */ - private $poisonPillCompare; - - /** - * @param PoisonPillReadInterface $poisonPillRead - * @param PoisonPillCompareInterface $poisonPillCompare - */ - public function __construct( - PoisonPillReadInterface $poisonPillRead, - PoisonPillCompareInterface $poisonPillCompare - ) { - $this->poisonPillRead = $poisonPillRead; - $this->poisonPillCompare = $poisonPillCompare; - } - - /** - * @inheritdoc - */ - public function invoke(QueueInterface $queue, $maxNumberOfMessages, $callback) - { - $this->poisonPillVersion = $this->poisonPillRead->getLatestVersion(); - for ($i = $maxNumberOfMessages; $i > 0; $i--) { - do { - $message = $queue->dequeue(); - // phpcs:ignore Magento2.Functions.DiscouragedFunction - } while ($message === null && (sleep(1) === 0)); - if (false === $this->poisonPillCompare->isLatestVersion($this->poisonPillVersion)) { - $queue->reject($message); - // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage - exit(0); - } - $callback($message); - } - } -} diff --git a/app/code/Magento/MessageQueue/Model/PoisonPillCompare.php b/app/code/Magento/MessageQueue/Model/PoisonPillCompare.php index a8e40ea495002..ffa478aecf36c 100644 --- a/app/code/Magento/MessageQueue/Model/PoisonPillCompare.php +++ b/app/code/Magento/MessageQueue/Model/PoisonPillCompare.php @@ -7,8 +7,8 @@ namespace Magento\MessageQueue\Model; -use Magento\MessageQueue\Api\PoisonPillCompareInterface; -use Magento\MessageQueue\Api\PoisonPillReadInterface; +use Magento\Framework\MessageQueue\PoisonPill\PoisonPillCompareInterface; +use Magento\Framework\MessageQueue\PoisonPill\PoisonPillReadInterface; /** * Poison pill compare @@ -33,7 +33,7 @@ public function __construct( /** * @inheritdoc */ - public function isLatestVersion(int $poisonPillVersion): bool + public function isLatestVersion(string $poisonPillVersion): bool { return $poisonPillVersion === $this->poisonPillRead->getLatestVersion(); } diff --git a/app/code/Magento/MessageQueue/Model/ResourceModel/PoisonPill.php b/app/code/Magento/MessageQueue/Model/ResourceModel/PoisonPill.php index 283fff8ace7c7..e59abec8724fb 100644 --- a/app/code/Magento/MessageQueue/Model/ResourceModel/PoisonPill.php +++ b/app/code/Magento/MessageQueue/Model/ResourceModel/PoisonPill.php @@ -7,13 +7,12 @@ namespace Magento\MessageQueue\Model\ResourceModel; -use Magento\MessageQueue\Api\PoisonPillReadInterface; -use Magento\MessageQueue\Api\PoisonPillPutInterface; -use Magento\Framework\Model\ResourceModel\Db\Context; +use Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface; +use Magento\Framework\MessageQueue\PoisonPill\PoisonPillReadInterface; use Magento\Framework\Model\ResourceModel\Db\AbstractDb; /** - * PoisonPill. + * PoisonPill class that enclose read and put interface. */ class PoisonPill extends AbstractDb implements PoisonPillPutInterface, PoisonPillReadInterface { @@ -22,19 +21,6 @@ class PoisonPill extends AbstractDb implements PoisonPillPutInterface, PoisonPil */ const QUEUE_POISON_PILL_TABLE = 'queue_poison_pill'; - /** - * PoisonPill constructor. - * - * @param Context $context - * @param string|null $connectionName - */ - public function __construct( - Context $context, - string $connectionName = null - ) { - parent::__construct($context, $connectionName); - } - /** * @inheritdoc */ @@ -46,30 +32,43 @@ protected function _construct() /** * @inheritdoc */ - public function put(): int + public function put(): string { $connection = $this->getConnection(); $table = $this->getMainTable(); - $connection->insert($table, []); - return (int)$connection->lastInsertId($table); + $uuid = uniqid('version-'); + $version = $this->getVersionFromDb(); + if ($version !== '') { + $connection->update($table, ['version' => $uuid]); + } else { + $connection->insert($table, ['version' => $uuid]); + } + + return $uuid; } /** * @inheritdoc */ - public function getLatestVersion() : int + public function getLatestVersion(): string + { + return $this->getVersionFromDb(); + } + + /** + * Returns version form DB or null. + * + * @return string + */ + private function getVersionFromDb(): string { $select = $this->getConnection()->select()->from( $this->getTable(self::QUEUE_POISON_PILL_TABLE), 'version' - )->order( - 'version ' . \Magento\Framework\DB\Select::SQL_DESC - )->limit( - 1 ); - $version = (int)$this->getConnection()->fetchOne($select); + $result = $this->getConnection()->fetchOne($select); - return $version; + return (string)$result; } } diff --git a/app/code/Magento/MessageQueue/etc/db_schema.xml b/app/code/Magento/MessageQueue/etc/db_schema.xml index 9cdf414dd06e1..4403384e9311a 100644 --- a/app/code/Magento/MessageQueue/etc/db_schema.xml +++ b/app/code/Magento/MessageQueue/etc/db_schema.xml @@ -21,12 +21,7 @@ <column name="message_code"/> </constraint> </table> - <table name="queue_poison_pill" resource="default" engine="innodb" - comment="Sequence table for poison pill versions"> - <column xsi:type="int" name="version" padding="10" unsigned="true" nullable="false" identity="true" - comment="Poison Pill version."/> - <constraint xsi:type="primary" referenceId="PRIMARY"> - <column name="version"/> - </constraint> + <table name="queue_poison_pill" resource="default" engine="innodb" comment="Sequence table for poison pill versions"> + <column xsi:type="varchar" name="version" length="255" nullable="false" comment="Poison Pill version."/> </table> </schema> diff --git a/app/code/Magento/MessageQueue/etc/db_schema_whitelist.json b/app/code/Magento/MessageQueue/etc/db_schema_whitelist.json index d9d623a994b37..37a342b21e64c 100644 --- a/app/code/Magento/MessageQueue/etc/db_schema_whitelist.json +++ b/app/code/Magento/MessageQueue/etc/db_schema_whitelist.json @@ -13,9 +13,6 @@ "queue_poison_pill": { "column": { "version": true - }, - "constraint": { - "PRIMARY": true } } } diff --git a/app/code/Magento/MessageQueue/etc/di.xml b/app/code/Magento/MessageQueue/etc/di.xml index 22cfea976a722..f60eb5fbc20df 100644 --- a/app/code/Magento/MessageQueue/etc/di.xml +++ b/app/code/Magento/MessageQueue/etc/di.xml @@ -13,10 +13,9 @@ <preference for="Magento\Framework\MessageQueue\EnvelopeInterface" type="Magento\Framework\MessageQueue\Envelope"/> <preference for="Magento\Framework\MessageQueue\ConsumerInterface" type="Magento\Framework\MessageQueue\Consumer"/> <preference for="Magento\Framework\MessageQueue\MergedMessageInterface" type="Magento\Framework\MessageQueue\MergedMessage"/> - <preference for="Magento\MessageQueue\Api\PoisonPillCompareInterface" type="Magento\MessageQueue\Model\PoisonPillCompare"/> - <preference for="Magento\MessageQueue\Api\PoisonPillPutInterface" type="Magento\MessageQueue\Model\ResourceModel\PoisonPill"/> - <preference for="Magento\MessageQueue\Api\PoisonPillReadInterface" type="Magento\MessageQueue\Model\ResourceModel\PoisonPill"/> - <preference for="Magento\Framework\MessageQueue\CallbackInvokerInterface" type="Magento\MessageQueue\Model\CallbackInvoker"/> + <preference for="Magento\Framework\MessageQueue\PoisonPill\PoisonPillCompareInterface" type="Magento\MessageQueue\Model\PoisonPillCompare"/> + <preference for="Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface" type="Magento\MessageQueue\Model\ResourceModel\PoisonPill"/> + <preference for="Magento\Framework\MessageQueue\PoisonPill\PoisonPillReadInterface" type="Magento\MessageQueue\Model\ResourceModel\PoisonPill"/> <type name="Magento\Framework\Console\CommandListInterface"> <arguments> <argument name="commands" xsi:type="array"> diff --git a/app/code/Magento/Msrp/Pricing/MsrpPriceCalculator.php b/app/code/Magento/Msrp/Pricing/MsrpPriceCalculator.php index 3d1e5ef0b8e6c..af5a29eb288ea 100644 --- a/app/code/Magento/Msrp/Pricing/MsrpPriceCalculator.php +++ b/app/code/Magento/Msrp/Pricing/MsrpPriceCalculator.php @@ -23,7 +23,7 @@ class MsrpPriceCalculator implements MsrpPriceCalculatorInterface /** * @param array $msrpPriceCalculators */ - public function __construct(array $msrpPriceCalculators) + public function __construct(array $msrpPriceCalculators = []) { $this->msrpPriceCalculators = $this->getMsrpPriceCalculators($msrpPriceCalculators); } diff --git a/app/code/Magento/MysqlMq/Model/Driver/Queue.php b/app/code/Magento/MysqlMq/Model/Driver/Queue.php index b8dab6fac7b24..cbc2e951782f2 100644 --- a/app/code/Magento/MysqlMq/Model/Driver/Queue.php +++ b/app/code/Magento/MysqlMq/Model/Driver/Queue.php @@ -73,7 +73,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function dequeue() { @@ -92,7 +92,7 @@ public function dequeue() } /** - * {@inheritdoc} + * @inheritdoc */ public function acknowledge(EnvelopeInterface $envelope) { @@ -103,25 +103,26 @@ public function acknowledge(EnvelopeInterface $envelope) } /** - * {@inheritdoc} + * @inheritdoc */ public function subscribe($callback) { while (true) { while ($envelope = $this->dequeue()) { try { + // phpcs:ignore Magento2.Functions.DiscouragedFunction call_user_func($callback, $envelope); - $this->acknowledge($envelope); } catch (\Exception $e) { $this->reject($envelope); } } + // phpcs:ignore Magento2.Functions.DiscouragedFunction sleep($this->interval); } } /** - * {@inheritdoc} + * @inheritdoc */ public function reject(EnvelopeInterface $envelope, $requeue = true, $rejectionMessage = null) { @@ -139,7 +140,7 @@ public function reject(EnvelopeInterface $envelope, $requeue = true, $rejectionM } /** - * {@inheritDoc} + * @inheritDoc */ public function push(EnvelopeInterface $envelope) { diff --git a/app/code/Magento/NewRelicReporting/Model/Module/Collect.php b/app/code/Magento/NewRelicReporting/Model/Module/Collect.php index 7e381762f5d27..fe5389e258aa5 100644 --- a/app/code/Magento/NewRelicReporting/Model/Module/Collect.php +++ b/app/code/Magento/NewRelicReporting/Model/Module/Collect.php @@ -11,6 +11,9 @@ use Magento\NewRelicReporting\Model\Config; use Magento\NewRelicReporting\Model\Module; +/** + * Class for collecting data for the report + */ class Collect { /** @@ -92,7 +95,6 @@ protected function getAllModules() * @param string $active * @param string $setupVersion * @param string $state - * * @return array */ protected function getNewModuleChanges($moduleName, $active, $setupVersion, $state) @@ -277,9 +279,7 @@ public function getModuleData($refresh = true) $changes = array_diff($module, $changeTest); $changesCleanArray = $this->getCleanChangesArray($changes); - if (count($changesCleanArray) > 0 || - ($this->moduleManager->isOutputEnabled($changeTest['name']) && - $module['setup_version'] != null)) { + if (!empty($changesCleanArray)) { $data = [ 'entity_id' => $changeTest['entity_id'], 'name' => $changeTest['name'], diff --git a/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php b/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php index 9882a1ce9b0b8..bce42b4e90074 100644 --- a/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php +++ b/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php @@ -21,7 +21,7 @@ class NewRelicWrapper */ public function addCustomParameter($param, $value) { - if (extension_loaded('newrelic')) { + if ($this->isExtensionInstalled()) { newrelic_add_custom_parameter($param, $value); return true; } @@ -36,7 +36,7 @@ public function addCustomParameter($param, $value) */ public function reportError($exception) { - if (extension_loaded('newrelic')) { + if ($this->isExtensionInstalled()) { newrelic_notice_error($exception->getMessage(), $exception); } } @@ -49,11 +49,24 @@ public function reportError($exception) */ public function setAppName(string $appName) { - if (extension_loaded('newrelic')) { + if ($this->isExtensionInstalled()) { newrelic_set_appname($appName); } } + /** + * Wrapper for 'newrelic_name_transaction' + * + * @param string $transactionName + * @return void + */ + public function setTransactionName(string $transactionName): void + { + if ($this->isExtensionInstalled()) { + newrelic_name_transaction($transactionName); + } + } + /** * Checks whether newrelic-php5 agent is installed * diff --git a/app/code/Magento/NewRelicReporting/Plugin/CommandPlugin.php b/app/code/Magento/NewRelicReporting/Plugin/CommandPlugin.php new file mode 100644 index 0000000000000..04ad3d0504d34 --- /dev/null +++ b/app/code/Magento/NewRelicReporting/Plugin/CommandPlugin.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\NewRelicReporting\Plugin; + +use Magento\NewRelicReporting\Model\Config; +use Magento\NewRelicReporting\Model\NewRelicWrapper; +use Symfony\Component\Console\Command\Command; + +/** + * Describe NewRelic commands plugin. + */ +class CommandPlugin +{ + /** + * @var Config + */ + private $config; + + /** + * @var NewRelicWrapper + */ + private $newRelicWrapper; + + /** + * @param Config $config + * @param NewRelicWrapper $newRelicWrapper + */ + public function __construct( + Config $config, + NewRelicWrapper $newRelicWrapper + ) { + $this->config = $config; + $this->newRelicWrapper = $newRelicWrapper; + } + + /** + * Set NewRelic Transaction name before running command. + * + * @param Command $command + * @param array $args + * @return array + */ + public function beforeRun(Command $command, ...$args) + { + $this->newRelicWrapper->setTransactionName( + sprintf('CLI %s', $command->getName()) + ); + + return $args; + } +} diff --git a/app/code/Magento/NewRelicReporting/Test/Unit/Model/Module/CollectTest.php b/app/code/Magento/NewRelicReporting/Test/Unit/Model/Module/CollectTest.php index 8d8e6255ab8d3..4286406d6e9ab 100644 --- a/app/code/Magento/NewRelicReporting/Test/Unit/Model/Module/CollectTest.php +++ b/app/code/Magento/NewRelicReporting/Test/Unit/Model/Module/CollectTest.php @@ -162,10 +162,6 @@ public function testGetModuleDataWithoutRefresh() ->method('getNames') ->willReturn($enabledModulesMockArray); - $this->moduleManagerMock->expects($this->any())->method('isOutputEnabled')->will( - $this->returnValue(false) - ); - $this->assertInternalType( 'array', $this->model->getModuleData() @@ -256,10 +252,6 @@ public function testGetModuleDataRefresh($data) ->method('getNames') ->willReturn($enabledModulesMockArray); - $this->moduleManagerMock->expects($this->any())->method('isOutputEnabled')->will( - $this->returnValue(true) - ); - $this->assertInternalType( 'array', $this->model->getModuleData() @@ -350,10 +342,6 @@ public function testGetModuleDataRefreshOrStatement($data) ->method('getNames') ->willReturn($enabledModulesMockArray); - $this->moduleManagerMock->expects($this->any())->method('isOutputEnabled')->will( - $this->returnValue(true) - ); - $this->assertInternalType( 'array', $this->model->getModuleData() diff --git a/app/code/Magento/NewRelicReporting/etc/di.xml b/app/code/Magento/NewRelicReporting/etc/di.xml index bab7d6611f14b..15516f6df89be 100644 --- a/app/code/Magento/NewRelicReporting/etc/di.xml +++ b/app/code/Magento/NewRelicReporting/etc/di.xml @@ -40,4 +40,7 @@ </argument> </arguments> </type> + <type name="Symfony\Component\Console\Command\Command"> + <plugin name="newrelic-describe-commands" type="Magento\NewRelicReporting\Plugin\CommandPlugin"/> + </type> </config> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Newsletter/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..1df1cd5f8dae8 --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,31 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuMarketingCommunicationsNewsletterQueue"> + <data key="pageTitle">Newsletter Queue</data> + <data key="title">Newsletter Queue</data> + <data key="dataUiId">magento-newsletter-newsletter-queue</data> + </entity> + <entity name="AdminMenuMarketingCommunicationsNewsletterSubscribers"> + <data key="pageTitle">Newsletter Subscribers</data> + <data key="title">Newsletter Subscribers</data> + <data key="dataUiId">magento-newsletter-newsletter-subscriber</data> + </entity> + <entity name="AdminMenuMarketingCommunicationsNewsletterTemplate"> + <data key="pageTitle">Newsletter Template</data> + <data key="title">Newsletter Template</data> + <data key="dataUiId">magento-newsletter-newsletter-template</data> + </entity> + <entity name="AdminMenuReportsMarketingNewsletterProblemReports"> + <data key="pageTitle">Newsletter Problems Report</data> + <data key="title">Newsletter Problem Reports</data> + <data key="dataUiId">magento-newsletter-newsletter-problem</data> + </entity> +</entities> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingNewsletterQueueNavigateMenuTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingNewsletterQueueNavigateMenuTest.xml new file mode 100644 index 0000000000000..31da588250a0a --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingNewsletterQueueNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminMarketingNewsletterQueueNavigateMenuTest"> + <annotations> + <features value="Newsletter"/> + <stories value="Menu Navigation"/> + <title value="Admin marketing newsletter queue navigate menu test"/> + <description value="Admin should be able to navigate to Marketing > Newsletter Queue"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14189"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToNewsletterQueuePage"> + <argument name="menuUiId" value="{{AdminMenuMarketing.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuMarketingCommunicationsNewsletterQueue.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuMarketingCommunicationsNewsletterQueue.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingNewsletterSubscribersNavigateMenuTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingNewsletterSubscribersNavigateMenuTest.xml new file mode 100644 index 0000000000000..8ced2690322f8 --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingNewsletterSubscribersNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminMarketingNewsletterSubscribersNavigateMenuTest"> + <annotations> + <features value="Newsletter"/> + <stories value="Menu Navigation"/> + <title value="Admin marketing newsletter subscribers navigate menu test"/> + <description value="Admin should be able to navigate to Marketing > Newsletter Subscribers"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14190"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToNewsletterSubscribersPage"> + <argument name="menuUiId" value="{{AdminMenuMarketing.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuMarketingCommunicationsNewsletterSubscribers.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuMarketingCommunicationsNewsletterSubscribers.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingNewsletterTemplateNavigateMenuTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingNewsletterTemplateNavigateMenuTest.xml new file mode 100644 index 0000000000000..ca994aa1d6269 --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingNewsletterTemplateNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminMarketingNewsletterTemplateNavigateMenuTest"> + <annotations> + <features value="Newsletter"/> + <stories value="Menu Navigation"/> + <title value="Admin marketing newsletter template navigate menu test"/> + <description value="Admin should be able to navigate to Marketing > Newsletter Template"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14188"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToNewsletterTemplatePage"> + <argument name="menuUiId" value="{{AdminMenuMarketing.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuMarketingCommunicationsNewsletterTemplate.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuMarketingCommunicationsNewsletterTemplate.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminReportsNewsletterProblemReportsNavigateMenuTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminReportsNewsletterProblemReportsNavigateMenuTest.xml new file mode 100644 index 0000000000000..3891b90536a17 --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminReportsNewsletterProblemReportsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsNewsletterProblemReportsNavigateMenuTest"> + <annotations> + <features value="Newsletter"/> + <stories value="Menu Navigation"/> + <title value="Admin reports newsletter problem reports navigate menu test"/> + <description value="Admin should be able to navigate to Reports > Newsletter Problem Reports"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14191"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToNewsletterProblemsReportPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsMarketingNewsletterProblemReports.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsMarketingNewsletterProblemReports.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_problem_block.xml b/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_problem_block.xml index 5cc268333de71..838a9dbb41b4b 100644 --- a/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_problem_block.xml +++ b/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_problem_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.newslettrer.problem.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">problemGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\Newsletter\Model\ResourceModel\Grid\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Newsletter\Model\ResourceModel\Grid\Collection</argument> <argument name="message_block_visibility" xsi:type="string">true</argument> <argument name="use_ajax" xsi:type="string">true</argument> <argument name="save_parameters_in_session" xsi:type="string">1</argument> diff --git a/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_queue_grid_block.xml b/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_queue_grid_block.xml index 3bfb52157bb99..8a2c891c68f81 100644 --- a/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_queue_grid_block.xml +++ b/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_queue_grid_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.newsletter.queue.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">queueGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\Newsletter\Model\ResourceModel\Queue\Grid\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Newsletter\Model\ResourceModel\Queue\Grid\Collection</argument> <argument name="default_sort" xsi:type="string">start_at</argument> <argument name="default_dir" xsi:type="string">DESC</argument> <argument name="save_parameters_in_session" xsi:type="string">1</argument> diff --git a/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_subscriber_block.xml b/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_subscriber_block.xml index 9de1807af18ec..e8600c2d49d7b 100644 --- a/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_subscriber_block.xml +++ b/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_subscriber_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Newsletter\Block\Adminhtml\Subscriber\Grid" name="adminhtml.newslettrer.subscriber.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">subscriberGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\Newsletter\Model\ResourceModel\Subscriber\Grid\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Newsletter\Model\ResourceModel\Subscriber\Grid\Collection</argument> <argument name="default_sort" xsi:type="string">subscriber_id</argument> <argument name="default_dir" xsi:type="string">desc</argument> <argument name="use_ajax" xsi:type="string">1</argument> diff --git a/app/code/Magento/Newsletter/view/frontend/templates/subscribe.phtml b/app/code/Magento/Newsletter/view/frontend/templates/subscribe.phtml index c1a526ee95148..554437095f36c 100644 --- a/app/code/Magento/Newsletter/view/frontend/templates/subscribe.phtml +++ b/app/code/Magento/Newsletter/view/frontend/templates/subscribe.phtml @@ -19,16 +19,24 @@ data-mage-init='{"validation": {"errorClass": "mage-error"}}' id="newsletter-validate-detail"> <div class="field newsletter"> - <label class="label" for="newsletter"><span><?= $block->escapeHtml(__('Sign Up for Our Newsletter:')) ?></span></label> <div class="control"> - <input name="email" type="email" id="newsletter" - placeholder="<?= $block->escapeHtml(__('Enter your email address')) ?>" - data-mage-init='{"mage/trim-input":{}}' - data-validate="{required:true, 'validate-email':true}"/> + <label for="newsletter"> + <span class="label"> + <?= $block->escapeHtml(__('Sign Up for Our Newsletter:')) ?> + </span> + <input name="email" type="email" id="newsletter" + placeholder="<?= $block->escapeHtml(__('Enter your email address')) ?>" + data-mage-init='{"mage/trim-input":{}}' + data-validate="{required:true, 'validate-email':true}" + /> + </label> </div> </div> <div class="actions"> - <button class="action subscribe primary" title="<?= $block->escapeHtmlAttr(__('Subscribe')) ?>" type="submit"> + <button class="action subscribe primary sr-only" + title="<?= $block->escapeHtmlAttr(__('Subscribe')) ?>" + type="submit" + aria-label="Subscribe"> <span><?= $block->escapeHtml(__('Subscribe')) ?></span> </button> </div> diff --git a/app/code/Magento/OfflinePayments/Test/Mftf/Data/ConfigData.xml b/app/code/Magento/OfflinePayments/Test/Mftf/Data/ConfigData.xml new file mode 100644 index 0000000000000..ec8dd46a00d8e --- /dev/null +++ b/app/code/Magento/OfflinePayments/Test/Mftf/Data/ConfigData.xml @@ -0,0 +1,33 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="DisableCheckMoneyOrderPaymentMethod"> + <data key="path">payment/checkmo/active</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> + <entity name="EnableCheckMoneyOrderPaymentMethod"> + <!-- Magento default value --> + <data key="path">payment/checkmo/active</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="DisableCashOnDeliveryPaymentMethod"> + <!-- Magento default value --> + <data key="path">payment/cashondelivery/active</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> + <entity name="EnableCashOnDeliveryPaymentMethod"> + <data key="path">payment/cashondelivery/active</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> +</entities> diff --git a/app/code/Magento/PageCache/etc/varnish4.vcl b/app/code/Magento/PageCache/etc/varnish4.vcl index c73c4e39170e6..6de6b4e917044 100644 --- a/app/code/Magento/PageCache/etc/varnish4.vcl +++ b/app/code/Magento/PageCache/etc/varnish4.vcl @@ -92,8 +92,8 @@ sub vcl_recv { } # Remove all marketing get parameters to minimize the cache objects - if (req.url ~ "(\?|&)(gclid|cx|ie|cof|siteurl|zanpid|origin|mc_[a-z]+|utm_[a-z]+)=") { - set req.url = regsuball(req.url, "(gclid|cx|ie|cof|siteurl|zanpid|origin|mc_[a-z]+|utm_[a-z]+)=[-_A-z0-9+()%.]+&?", ""); + if (req.url ~ "(\?|&)(gclid|cx|ie|cof|siteurl|zanpid|origin|fbclid|mc_[a-z]+|utm_[a-z]+|_bta_[a-z]+)=") { + set req.url = regsuball(req.url, "(gclid|cx|ie|cof|siteurl|zanpid|origin|fbclid|mc_[a-z]+|utm_[a-z]+|_bta_[a-z]+)=[-_A-z0-9+()%.]+&?", ""); set req.url = regsub(req.url, "[?|&]+$", ""); } @@ -123,6 +123,10 @@ sub vcl_hash { hash_data(server.ip); } + if (req.url ~ "/graphql") { + call process_graphql_headers; + } + # To make sure http users don't see ssl warning if (req.http./* {{ ssl_offloaded_header }} */) { hash_data(req.http./* {{ ssl_offloaded_header }} */); @@ -130,6 +134,15 @@ sub vcl_hash { /* {{ design_exceptions_code }} */ } +sub process_graphql_headers { + if (req.http.Store) { + hash_data(req.http.Store); + } + if (req.http.Content-Currency) { + hash_data(req.http.Content-Currency); + } +} + sub vcl_backend_response { set beresp.grace = 3d; diff --git a/app/code/Magento/PageCache/etc/varnish5.vcl b/app/code/Magento/PageCache/etc/varnish5.vcl index ea586858eff75..4505e74629714 100644 --- a/app/code/Magento/PageCache/etc/varnish5.vcl +++ b/app/code/Magento/PageCache/etc/varnish5.vcl @@ -93,8 +93,8 @@ sub vcl_recv { } # Remove all marketing get parameters to minimize the cache objects - if (req.url ~ "(\?|&)(gclid|cx|ie|cof|siteurl|zanpid|origin|mc_[a-z]+|utm_[a-z]+)=") { - set req.url = regsuball(req.url, "(gclid|cx|ie|cof|siteurl|zanpid|origin|mc_[a-z]+|utm_[a-z]+)=[-_A-z0-9+()%.]+&?", ""); + if (req.url ~ "(\?|&)(gclid|cx|ie|cof|siteurl|zanpid|origin|fbclid|mc_[a-z]+|utm_[a-z]+|_bta_[a-z]+)=") { + set req.url = regsuball(req.url, "(gclid|cx|ie|cof|siteurl|zanpid|origin|fbclid|mc_[a-z]+|utm_[a-z]+|_bta_[a-z]+)=[-_A-z0-9+()%.]+&?", ""); set req.url = regsub(req.url, "[?|&]+$", ""); } @@ -129,6 +129,19 @@ sub vcl_hash { hash_data(req.http./* {{ ssl_offloaded_header }} */); } /* {{ design_exceptions_code }} */ + + if (req.url ~ "/graphql") { + call process_graphql_headers; + } +} + +sub process_graphql_headers { + if (req.http.Store) { + hash_data(req.http.Store); + } + if (req.http.Content-Currency) { + hash_data(req.http.Content-Currency); + } } sub vcl_backend_response { diff --git a/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php b/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php index da5599984b701..8a2825a16d33a 100644 --- a/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php +++ b/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php @@ -64,6 +64,7 @@ public function requestToken(Quote $quote) $request->setTrxtype(Payflowpro::TRXTYPE_AUTH_ONLY); $request->setVerbosity('HIGH'); $request->setAmt(0); + $request->setCurrency($quote->getBaseCurrencyCode()); $request->setCreatesecuretoken('Y'); $request->setSecuretokenid($this->mathRandom->getUniqueHash()); $request->setReturnurl($this->url->getUrl('paypal/transparent/response')); diff --git a/app/code/Magento/Paypal/Model/Payflow/Service/Response/Transaction.php b/app/code/Magento/Paypal/Model/Payflow/Service/Response/Transaction.php index 06a8a5b680bf4..7143576b71a07 100644 --- a/app/code/Magento/Paypal/Model/Payflow/Service/Response/Transaction.php +++ b/app/code/Magento/Paypal/Model/Payflow/Service/Response/Transaction.php @@ -3,9 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Paypal\Model\Payflow\Service\Response; use Magento\Framework\DataObject; +use Magento\Framework\Intl\DateTimeFactory; use Magento\Payment\Model\Method\Logger; use Magento\Paypal\Model\Payflow\Service\Response\Handler\HandlerInterface; use Magento\Framework\Session\Generic; @@ -18,6 +21,7 @@ /** * Class Transaction + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Transaction { @@ -51,6 +55,11 @@ class Transaction */ private $logger; + /** + * @var DateTimeFactory + */ + private $dateTimeFactory; + /** * @param Generic $sessionTransparent * @param CartRepositoryInterface $quoteRepository @@ -58,6 +67,7 @@ class Transaction * @param PaymentMethodManagementInterface $paymentManagement * @param HandlerInterface $errorHandler * @param Logger $logger + * @param DateTimeFactory $dateTimeFactory */ public function __construct( Generic $sessionTransparent, @@ -65,7 +75,8 @@ public function __construct( Transparent $transparent, PaymentMethodManagementInterface $paymentManagement, HandlerInterface $errorHandler, - Logger $logger + Logger $logger, + DateTimeFactory $dateTimeFactory ) { $this->sessionTransparent = $sessionTransparent; $this->quoteRepository = $quoteRepository; @@ -73,6 +84,7 @@ public function __construct( $this->paymentManagement = $paymentManagement; $this->errorHandler = $errorHandler; $this->logger = $logger; + $this->dateTimeFactory = $dateTimeFactory; } /** @@ -114,8 +126,45 @@ public function savePaymentInQuote($response) $payment->setData(OrderPaymentInterface::CC_TYPE, $response->getData(OrderPaymentInterface::CC_TYPE)); $payment->setAdditionalInformation(Payflowpro::PNREF, $response->getData(Payflowpro::PNREF)); + $expDate = $response->getData('expdate'); + $expMonth = $this->getCcExpMonth($expDate); + $payment->setCcExpMonth($expMonth); + $expYear = $this->getCcExpYear($expDate); + $payment->setCcExpYear($expYear); + $this->errorHandler->handle($payment, $response); $this->paymentManagement->set($quote->getId(), $payment); } + + /** + * Extracts expiration month from PayPal response expiration date. + * + * @param string $expDate format {MMYY} + * @return int + */ + private function getCcExpMonth(string $expDate): int + { + return (int)substr($expDate, 0, 2); + } + + /** + * Extracts expiration year from PayPal response expiration date. + * + * @param string $expDate format {MMYY} + * @return int + */ + private function getCcExpYear(string $expDate): int + { + $last2YearDigits = (int)substr($expDate, 2, 2); + $currentDate = $this->dateTimeFactory->create('now', new \DateTimeZone('UTC')); + $first2YearDigits = (int)substr($currentDate->format('Y'), 0, 2); + + // case when credit card expires at next century + if ((int)$currentDate->format('y') > $last2YearDigits) { + $first2YearDigits++; + } + + return 100 * $first2YearDigits + $last2YearDigits; + } } diff --git a/app/code/Magento/Paypal/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Paypal/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..207bf62abf3ce --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,26 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuReportsSalesPayPalSettlement"> + <data key="pageTitle">PayPal Settlement Reports</data> + <data key="title">PayPal Settlement</data> + <data key="dataUiId">magento-paypal-report-salesroot-paypal-settlement-reports</data> + </entity> + <entity name="AdminMenuSales"> + <data key="pageTitle">Sales</data> + <data key="title">Sales</data> + <data key="dataUiId">magento-sales-sales</data> + </entity> + <entity name="AdminMenuSalesBillingAgreements"> + <data key="pageTitle">Billing Agreements</data> + <data key="title">Billing Agreements</data> + <data key="dataUiId">magento-paypal-paypal-billing-agreement</data> + </entity> +</entities> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminReportsPayPalSettlementNavigateMenuTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminReportsPayPalSettlementNavigateMenuTest.xml new file mode 100644 index 0000000000000..03f0167230e9f --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminReportsPayPalSettlementNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsPayPalSettlementNavigateMenuTest"> + <annotations> + <features value="Paypal"/> + <stories value="Menu Navigation"/> + <title value="Admin reports paypal settlement navigate menu test"/> + <description value="Admin should be able to navigate to Reports > PayPal Settlement"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14193"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToPayPalSettlementPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsSalesPayPalSettlement.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsSalesPayPalSettlement.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminSalesBillingAgreementsNavigateMenuTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminSalesBillingAgreementsNavigateMenuTest.xml new file mode 100644 index 0000000000000..8c3735fcbd253 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminSalesBillingAgreementsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminSalesBillingAgreementsNavigateMenuTest"> + <annotations> + <features value="Paypal"/> + <stories value="Menu Navigation"/> + <title value="Admin sales billing agreements navigate menu test"/> + <description value="Admin should be able to navigate to Sales > Billing Agreements"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14194"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToBillingAgreementsPage"> + <argument name="menuUiId" value="{{AdminMenuSales.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSalesBillingAgreements.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSalesBillingAgreements.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Unit/Block/Adminhtml/System/Config/Fieldset/GroupTest.php b/app/code/Magento/Paypal/Test/Unit/Block/Adminhtml/System/Config/Fieldset/GroupTest.php deleted file mode 100644 index cfdfe17b1e004..0000000000000 --- a/app/code/Magento/Paypal/Test/Unit/Block/Adminhtml/System/Config/Fieldset/GroupTest.php +++ /dev/null @@ -1,107 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Paypal\Test\Unit\Block\Adminhtml\System\Config\Fieldset; - -class GroupTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var Group - */ - protected $_model; - - /** - * @var \Magento\Framework\Data\Form\Element\AbstractElement - */ - protected $_element; - - /** - * @var \Magento\Backend\Model\Auth\Session|\PHPUnit_Framework_MockObject_MockObject - */ - protected $_authSession; - - /** - * @var \Magento\User\Model\User|\PHPUnit_Framework_MockObject_MockObject - */ - protected $_user; - - /** - * @var \Magento\Config\Model\Config\Structure\Element\Group|\PHPUnit_Framework_MockObject_MockObject - */ - protected $_group; - - protected function setUp() - { - $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->_group = $this->createMock(\Magento\Config\Model\Config\Structure\Element\Group::class); - $this->_element = $this->getMockForAbstractClass( - \Magento\Framework\Data\Form\Element\AbstractElement::class, - [], - '', - false, - true, - true, - ['getHtmlId', 'getElementHtml', 'getName', 'getElements', 'getId'] - ); - $this->_element->expects($this->any()) - ->method('getHtmlId') - ->will($this->returnValue('html id')); - $this->_element->expects($this->any()) - ->method('getElementHtml') - ->will($this->returnValue('element html')); - $this->_element->expects($this->any()) - ->method('getName') - ->will($this->returnValue('name')); - $this->_element->expects($this->any()) - ->method('getElements') - ->will($this->returnValue([])); - $this->_element->expects($this->any()) - ->method('getId') - ->will($this->returnValue('id')); - $this->_user = $this->createMock(\Magento\User\Model\User::class); - $this->_authSession = $this->createMock(\Magento\Backend\Model\Auth\Session::class); - $this->_authSession->expects($this->any()) - ->method('__call') - ->with('getUser') - ->will($this->returnValue($this->_user)); - $this->_model = $helper->getObject( - \Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Group::class, - ['authSession' => $this->_authSession] - ); - $this->_model->setGroup($this->_group); - } - - /** - * @param mixed $expanded - * @param int $expected - * @dataProvider isCollapseStateDataProvider - */ - public function testIsCollapseState($expanded, $expected) - { - $this->_user->setExtra(['configState' => []]); - $this->_element->setGroup(isset($expanded) ? ['expanded' => $expanded] : []); - $html = $this->_model->render($this->_element); - $this->assertContains( - '<input id="' . $this->_element->getHtmlId() . '-state" name="config_state[' - . $this->_element->getId() . ']" type="hidden" value="' . $expected . '" />', - $html - ); - } - - /** - * @return array - */ - public function isCollapseStateDataProvider() - { - return [ - [null, 0], - [false, 0], - ['', 0], - [1, 1], - ['1', 1], - ]; - } -} diff --git a/app/code/Magento/Paypal/view/adminhtml/layout/adminhtml_paypal_reports_block.xml b/app/code/Magento/Paypal/view/adminhtml/layout/adminhtml_paypal_reports_block.xml index 12dcc46e3ecee..596381a4b1143 100644 --- a/app/code/Magento/Paypal/view/adminhtml/layout/adminhtml_paypal_reports_block.xml +++ b/app/code/Magento/Paypal/view/adminhtml/layout/adminhtml_paypal_reports_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="paypal.report.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">settlementGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\Paypal\Model\ResourceModel\Report\Settlement\Row\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Paypal\Model\ResourceModel\Report\Settlement\Row\Collection</argument> <argument name="default_sort" xsi:type="string">row_id</argument> <argument name="default_dir" xsi:type="string">DESC</argument> <argument name="save_parameters_in_session" xsi:type="string">1</argument> diff --git a/app/code/Magento/Paypal/view/adminhtml/layout/paypal_billing_agreement_ordersgrid.xml b/app/code/Magento/Paypal/view/adminhtml/layout/paypal_billing_agreement_ordersgrid.xml index 76a105f5abcdf..0510011a6a559 100644 --- a/app/code/Magento/Paypal/view/adminhtml/layout/paypal_billing_agreement_ordersgrid.xml +++ b/app/code/Magento/Paypal/view/adminhtml/layout/paypal_billing_agreement_ordersgrid.xml @@ -9,7 +9,7 @@ <update handle="sales_order_grid_block"/> <referenceBlock name="sales.order.grid"> <arguments> - <argument name="dataSource" xsi:type="object"> + <argument name="dataSource" xsi:type="object" shared="false"> <updater>Magento\Paypal\Model\Billing\Agreement\OrdersUpdater</updater> </argument> <argument name="grid_url" xsi:type="url" path="*/*/ordersgrid"/> diff --git a/app/code/Magento/Paypal/view/adminhtml/layout/paypal_billing_agreement_view.xml b/app/code/Magento/Paypal/view/adminhtml/layout/paypal_billing_agreement_view.xml index c76539f5cb206..d9c376701db33 100644 --- a/app/code/Magento/Paypal/view/adminhtml/layout/paypal_billing_agreement_view.xml +++ b/app/code/Magento/Paypal/view/adminhtml/layout/paypal_billing_agreement_view.xml @@ -12,7 +12,7 @@ <referenceBlock name="sales.order.grid"> <arguments> - <argument name="dataSource" xsi:type="object"> + <argument name="dataSource" xsi:type="object" shared="false"> <updater>Magento\Paypal\Model\Billing\Agreement\OrdersUpdater</updater> </argument> <argument name="grid_url" xsi:type="url" path="*/*/ordersgrid"/> diff --git a/app/code/Magento/Persistent/Model/QuoteManager.php b/app/code/Magento/Persistent/Model/QuoteManager.php index 35c2c70be30dc..8ae22e4c26c6f 100644 --- a/app/code/Magento/Persistent/Model/QuoteManager.php +++ b/app/code/Magento/Persistent/Model/QuoteManager.php @@ -7,6 +7,8 @@ /** * Class QuoteManager + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class QuoteManager { @@ -87,6 +89,7 @@ public function setGuest($checkQuote = false) ->setCustomerLastname(null) ->setCustomerGroupId(\Magento\Customer\Api\Data\GroupInterface::NOT_LOGGED_IN_ID) ->setIsPersistent(false) + ->setCustomerIsGuest(true) ->removeAllAddresses(); //Create guest addresses $quote->getShippingAddress(); diff --git a/app/code/Magento/Persistent/Observer/CheckExpirePersistentQuoteObserver.php b/app/code/Magento/Persistent/Observer/CheckExpirePersistentQuoteObserver.php index f3720960ca6e5..79fdf44c3c551 100644 --- a/app/code/Magento/Persistent/Observer/CheckExpirePersistentQuoteObserver.php +++ b/app/code/Magento/Persistent/Observer/CheckExpirePersistentQuoteObserver.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -8,6 +7,11 @@ use Magento\Framework\Event\ObserverInterface; +/** + * Observer of expired session + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ class CheckExpirePersistentQuoteObserver implements ObserverInterface { /** @@ -107,8 +111,12 @@ public function execute(\Magento\Framework\Event\Observer $observer) !$this->_persistentSession->isPersistent() && !$this->_customerSession->isLoggedIn() && $this->_checkoutSession->getQuoteId() && - !$this->isRequestFromCheckoutPage($this->request) + !$this->isRequestFromCheckoutPage($this->request) && // persistent session does not expire on onepage checkout page + ( + $this->_checkoutSession->getQuote()->getIsPersistent() || + $this->_checkoutSession->getQuote()->getCustomerIsGuest() + ) ) { $this->_eventManager->dispatch('persistent_session_expired'); $this->quoteManager->expire(); diff --git a/app/code/Magento/Persistent/Observer/SetQuotePersistentDataObserver.php b/app/code/Magento/Persistent/Observer/SetQuotePersistentDataObserver.php index db6b6d1ee370d..2803bc998dcbe 100644 --- a/app/code/Magento/Persistent/Observer/SetQuotePersistentDataObserver.php +++ b/app/code/Magento/Persistent/Observer/SetQuotePersistentDataObserver.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -8,6 +7,11 @@ use Magento\Framework\Event\ObserverInterface; +/** + * Observer for setting "is_persistent" value to quote + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ class SetQuotePersistentDataObserver implements ObserverInterface { /** @@ -73,8 +77,8 @@ public function execute(\Magento\Framework\Event\Observer $observer) } if (( - ($this->_persistentSession->isPersistent() && !$this->_customerSession->isLoggedIn()) - && !$this->_persistentData->isShoppingCartPersist() + ($this->_persistentSession->isPersistent()) + && $this->_persistentData->isShoppingCartPersist() ) && $this->quoteManager->isPersistent() ) { diff --git a/app/code/Magento/Persistent/Test/Mftf/Test/ShippingQuotePersistedForGuestTest.xml b/app/code/Magento/Persistent/Test/Mftf/Test/ShippingQuotePersistedForGuestTest.xml new file mode 100644 index 0000000000000..e5c77ee414362 --- /dev/null +++ b/app/code/Magento/Persistent/Test/Mftf/Test/ShippingQuotePersistedForGuestTest.xml @@ -0,0 +1,84 @@ +<?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="ShippingQuotePersistedForGuestTest"> + <annotations> + <features value="Persistent"/> + <stories value="Guest checkout"/> + <title value="Estimate Shipping and Tax block sections on shipping cart saving correctly for Guest."/> + <description value="Verify that 'Estimate Shipping and Tax' block sections on shipping cart saving correctly for Guest after switching to another page. And check that the shopping cart is cleared after reset persistent cookie."/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-99025"/> + <useCaseId value="MAGETWO-98620"/> + <group value="persistent"/> + </annotations> + <before> + <!--Enabled The Persistent Shopping Cart feature --> + <createData entity="PersistentConfigEnabled" stepKey="enablePersistent"/> + <createData entity="PersistentLogoutClearDisable" stepKey="persistentLogoutClearDisable"/> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="createProduct"> + <field key="price">150</field> + </createData> + <!--Create customer--> + <createData entity="Simple_US_Customer" stepKey="createCustomer"> + <field key="firstname">John1</field> + <field key="lastname">Doe1</field> + </createData> + </before> + <after> + <!--Revert persistent configuration to default--> + <createData entity="PersistentConfigDefault" stepKey="setDefaultPersistentState"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Step 1: Login as a Customer with remember me checked--> + <actionGroup ref="CustomerLoginOnStorefrontWithRememberMeChecked" stepKey="loginToStorefrontAccountWithRememberMeChecked"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <!--Step 2: Open the Product Page and add the product to shopping cart--> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToProductPageAsLoggedUser"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProductToCartAsLoggedUser"> + <argument name="productName" value="$$createProduct.name$$"/> + </actionGroup> + <!--Step 3: Log out, reset persistent cookie and go to homepage--> + <amOnPage url="{{StorefrontCustomerSignOutPage.url}}" stepKey="signOut"/> + <waitForLoadingMaskToDisappear stepKey="waitSignOutPage"/> + <resetCookie userInput="persistent_shopping_cart" stepKey="resetPersistentCookie"/> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnHomePageAfterResetPersistentCookie"/> + <waitForPageLoad stepKey="waitHomePageLoadAfterResetCookie"/> + <!--Check that the minicart is empty--> + <actionGroup ref="assertMiniCartEmpty" after="waitHomePageLoadAfterResetCookie" stepKey="seeMinicartEmpty"/> + <!--Step 4: Add the product to shopping cart and open cart--> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToProductPageAsGuestUser"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProductToCartAsGuestUser"> + <argument name="productName" value="$$createProduct.name$$"/> + </actionGroup> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartBeforeChangeShippingAndTaxSection"/> + <!--Step 5: Open Estimate Shipping and Tax block and fill the sections--> + <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingAndTax" /> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="{{US_Address_CA.country}}" stepKey="selectUSCountry"/> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{US_Address_CA.state}}" stepKey="selectCaliforniaRegion"/> + <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="{{US_Address_CA.postcode}}" stepKey="inputPostCode"/> + <!--Step 6: Go to Homepage--> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToHomePageAfterChangingShippingAndTaxSection"/> + <!--Step 7: Go to shopping cart and check "Estimate Shipping and Tax" fields values are saved--> + <actionGroup ref="clickViewAndEditCartFromMiniCart" after="goToHomePageAfterChangingShippingAndTaxSection" stepKey="goToShoppingCartAfterChangingShippingAndTaxSection"/> + <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingAndTaxAfterChanging" /> + <seeOptionIsSelected selector="{{CheckoutCartSummarySection.country}}" userInput="{{US_Address_CA.country}}" stepKey="checkCustomerCountry" /> + <seeOptionIsSelected selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{US_Address_CA.state}}" stepKey="checkCustomerRegion" /> + <grabValueFrom selector="{{CheckoutCartSummarySection.postcode}}" stepKey="grabTextPostCode"/> + <assertEquals message="Customer postcode is invalid" stepKey="checkCustomerPostcode"> + <expectedResult type="string">{{US_Address_CA.postcode}}</expectedResult> + <actualResult type="variable">grabTextPostCode</actualResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontVerifyThatInformationAboutViewingComparisonWishlistIsPersistedUnderLongTermCookieTest.xml b/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontVerifyThatInformationAboutViewingComparisonWishlistIsPersistedUnderLongTermCookieTest.xml new file mode 100644 index 0000000000000..dc6f87bef0ba8 --- /dev/null +++ b/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontVerifyThatInformationAboutViewingComparisonWishlistIsPersistedUnderLongTermCookieTest.xml @@ -0,0 +1,193 @@ +<?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="StorefrontVerifyThatInformationAboutViewingComparisonWishlistIsPersistedUnderLongTermCookieTest"> + <annotations> + <features value="Persistent"/> + <stories value="Catalog widget"/> + <title value="Verify that information about viewing, comparison, wishlist and last ordered items is persisted under long-term cookie"/> + <description value="Verify that information about viewing, comparison, wishlist and last ordered items is persisted under long-term cookie"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-12180"/> + <group value="persistent"/> + <group value="widget"/> + <group value="catalog_widget"/> + <skip> + <issueId value="MC-15741"/> + </skip> + </annotations> + <before> + <createData entity="PersistentConfigEnabled" stepKey="enablePersistent"/> + <createData entity="PersistentLogoutClearDisable" stepKey="persistentLogoutClearDisable"/> + <createData entity="EnableSynchronizeWidgetProductsWithBackendStorage" stepKey="enableSynchronizeWidgetProductsWithBackendStorage"/> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSecondSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + <actionGroup ref="AdminCreateRecentlyProductsWidgetActionGroup" stepKey="createRecentlyComparedProductsWidget"> + <argument name="widget" value="RecentlyComparedProductsWidget"/> + </actionGroup> + <actionGroup ref="AdminCreateRecentlyProductsWidgetActionGroup" stepKey="createRecentlyViewedProductsWidget"> + <argument name="widget" value="RecentlyViewedProductsWidget"/> + </actionGroup> + </before> + <after> + <createData entity="PersistentConfigDefault" stepKey="setDefaultPersistentState"/> + <createData entity="PersistentLogoutClearEnabled" stepKey="persistentLogoutClearEnabled"/> + <createData entity="DisableSynchronizeWidgetProductsWithBackendStorage" stepKey="disableSynchronizeWidgetProductsWithBackendStorage"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createSecondSimpleProduct" stepKey="deleteSecondSimpleProduct"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutFromCustomer"/> + <actionGroup ref="AdminDeleteWidgetActionGroup" stepKey="deleteRecentlyComparedProductsWidget"> + <argument name="widget" value="RecentlyComparedProductsWidget"/> + </actionGroup> + <actionGroup ref="AdminDeleteWidgetActionGroup" stepKey="deleteRecentlyViewedProductsWidget"> + <argument name="widget" value="RecentlyViewedProductsWidget"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Login to storefront from customer--> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <see userInput="Welcome, $$createCustomer.firstname$$ $$createCustomer.lastname$$!" selector="{{StorefrontPanelHeaderSection.WelcomeMessage}}" stepKey="checkWelcomeMessage"/> + + <!--Open the details page of Simple Product 1, Simple Product 2 and add to cart, get to the category--> + <actionGroup ref="AddSimpleProductToCart" stepKey="addSimpleProductProductToCart"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="AddSimpleProductToCart" stepKey="addSecondSimpleProductProductToCart"> + <argument name="product" value="$$createSecondSimpleProduct$$"/> + </actionGroup> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.custom_attributes[url_key]$$)}}" stepKey="openCategoryPageAfterAddedProductToCart"/> + <!--The Recently Viewed widget displays Simple Product 1 and Simple Product 2--> + <actionGroup ref="StorefrontAssertProductInRecentlyViewedWidgetActionGroup" stepKey="seeSimpleProductInRecentlyViewedWidget"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontAssertProductInRecentlyViewedWidgetActionGroup" stepKey="seeSecondSimpleProductInRecentlyViewedWidget"> + <argument name="product" value="$$createSecondSimpleProduct$$"/> + </actionGroup> + + <!--Add Simple Product 1 and Simple Product 2 to Wishlist--> + <actionGroup ref="StorefrontCustomerAddCategoryProductToWishlistActionGroup" stepKey="addSimpleProductToWishlist"> + <argument name="productVar" value="$$createSimpleProduct$$"/> + </actionGroup> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.custom_attributes[url_key]$$)}}" stepKey="openCategoryPageAfterProductAddToWishlist"/> + <actionGroup ref="StorefrontCustomerAddCategoryProductToWishlistActionGroup" stepKey="addSecondSimpleProductToWishlist"> + <argument name="productVar" value="$$createSecondSimpleProduct$$"/> + </actionGroup> + <!--The My Wishlist widget displays Simple Product 1 and Simple Product 2--> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.custom_attributes[url_key]$$)}}" stepKey="openCategoryPageToCheckProductsInWishlistSidebar"/> + <actionGroup ref="StorefrontCustomerCheckProductInWishlistSidebar" stepKey="checkSimpleProductInWishlistSidebar"> + <argument name="productVar" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontCustomerCheckProductInWishlistSidebar" stepKey="checkSecondSimpleProductInWishlistSidebar"> + <argument name="productVar" value="$$createSecondSimpleProduct$$"/> + </actionGroup> + + <!--Add to compare Simple Product and Simple Product 2--> + <actionGroup ref="StorefrontAddCategoryProductToCompareActionGroup" stepKey="addSimpleProductToCompare" > + <argument name="productVar" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontAddCategoryProductToCompareActionGroup" stepKey="addSecondSimpleProductToCompare" > + <argument name="productVar" value="$$createSecondSimpleProduct$$"/> + </actionGroup> + <!--The Compare Products widget displays Simple Product 1 and Simple Product 2--> + <actionGroup ref="StorefrontCheckCompareSidebarProductActionGroup" stepKey="checkSimpleProductInCompareSidebar"> + <argument name="productVar" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontCheckCompareSidebarProductActionGroup" stepKey="checkSecondSimpleProductInCompareSidebar"> + <argument name="productVar" value="$$createSecondSimpleProduct$$"/> + </actionGroup> + + <!--Click Clear all in the Compare Products widget--> + <actionGroup ref="StorefrontClearCompareActionGroup" stepKey="clearCompareList"/> + <!--The Recently Compared widget displays Simple Product 1 and Simple Product 2--> + <actionGroup ref="StorefrontAssertProductInRecentlyComparedWidgetActionGroup" stepKey="checkSimpleProductInRecentlyComparedWidget"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontAssertProductInRecentlyComparedWidgetActionGroup" stepKey="checkSecondSimpleProductInRecentlyComparedWidget"> + <argument name="product" value="$$createSecondSimpleProduct$$"/> + </actionGroup> + + <!--Place the order--> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToShoppingCartPage"/> + <actionGroup ref="PlaceOrderWithLoggedUserActionGroup" stepKey="placeOrder"> + <argument name="shippingMethod" value="Flat Rate"/> + </actionGroup> + <!--The Recently Ordered widget displays Simple Product 1 and Simple Product 2--> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.custom_attributes[url_key]$$)}}" stepKey="openCategoryPageToCheckProductsInRecentlyOrderedWidget"/> + <actionGroup ref="StorefrontAssertProductInRecentlyOrderedWidgetActionGroup" stepKey="checkSimpleProductInRecentlyOrderedWidget"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontAssertProductInRecentlyOrderedWidgetActionGroup" stepKey="checkSecondSimpleProductInRecentlyOrderedWidget"> + <argument name="product" value="$$createSecondSimpleProduct$$"/> + </actionGroup> + + <!--Sign out and check that widgets persist the information about the items--> + <actionGroup ref="StorefrontSignOutActionGroup" stepKey="logoutFromCustomerToCheckThatWidgetsPersist"/> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.custom_attributes[url_key]$$)}}" stepKey="openCategoryPageAfterLogoutFromCustomer"/> + <see userInput="Welcome, $$createCustomer.firstname$$ $$createCustomer.lastname$$!" selector="{{StorefrontPanelHeaderSection.WelcomeMessage}}" stepKey="checkWelcomeMessageAfterLogoutFromCustomer"/> + <seeElement selector="{{StorefrontPanelHeaderSection.notYouLink}}" stepKey="checkLinkNotYouAfterLogoutFromCustomer"/> + + <actionGroup ref="StorefrontAssertProductInRecentlyViewedWidgetActionGroup" stepKey="checkSimpleProductInRecentlyViewedWidgetAfterLogout"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontCustomerCheckProductInWishlistSidebar" stepKey="checkSimpleProductInWishlistSidebarAfterLogout"> + <argument name="productVar" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontAssertProductInRecentlyComparedWidgetActionGroup" stepKey="checkSimpleProductInRecentlyComparedWidgetAfterLogout"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontAssertProductInRecentlyOrderedWidgetActionGroup" stepKey="checkSimpleProductInRecentlyOrderedWidgetAfterLogout"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + + <!--Click the *Not you?* link and check the price for Simple Product--> + <click selector="{{StorefrontPanelHeaderSection.notYouLink}}" stepKey="clickLinkNotYou"/> + <waitForPageLoad stepKey="waitForPageLoadAfterClickLinkNotYou"/> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.custom_attributes[url_key]$$)}}" stepKey="openCategoryPageAfterClickNotYou"/> + <see userInput="Default welcome msg!" selector="{{StorefrontPanelHeaderSection.WelcomeMessage}}" stepKey="checkWelcomeMessageAfterClickLinkNotYou"/> + <dontSee selector="{{StorefrontWidgetsSection.widgetRecentlyViewedProductsGrid}}" userInput="$$createSimpleProduct.name$$" stepKey="dontSeeProductInRecentlyViewedWidget"/> + <dontSee selector="{{StorefrontCustomerWishlistSidebarSection.ProductTitleByName($$createSimpleProduct.name$$)}}" stepKey="dontSeeProductInWishlistWidget"/> + <dontSee selector="{{StorefrontWidgetsSection.widgetRecentlyComparedProductsGrid}}" userInput="$$createSimpleProduct.name$$" stepKey="dontSeeProductInRecentlyComparedWidget"/> + <dontSee selector="{{StorefrontWidgetsSection.widgetRecentlyOrderedProductsGrid}}" userInput="$$createSimpleProduct.name$$" stepKey="dontSeeProductInRecentlyOrderedWidget"/> + + <!--Login to storefront from customer again--> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="logInFromCustomerAfterClearLongTermCookie"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.custom_attributes[url_key]$$)}}" stepKey="openCategoryPageToCheckWidgets"/> + <see userInput="Welcome, $$createCustomer.firstname$$ $$createCustomer.lastname$$!" selector="{{StorefrontPanelHeaderSection.WelcomeMessage}}" stepKey="checkWelcomeMessageAfterLogin"/> + + <actionGroup ref="StorefrontCustomerCheckProductInWishlistSidebar" stepKey="checkSimpleProductNameInWishlistSidebarAfterLogin"> + <argument name="productVar" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontAssertProductInRecentlyViewedWidgetActionGroup" stepKey="checkSimpleProductInRecentlyViewedWidgetAfterLogin"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontAssertProductInRecentlyComparedWidgetActionGroup" stepKey="checkSimpleProductInRecentlyComparedWidgetAfterLogin"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontAssertProductInRecentlyOrderedWidgetActionGroup" stepKey="checkSimpleProductInRecentlyOrderedWidgetAfterLogin"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Persistent/Test/Unit/Observer/CheckExpirePersistentQuoteObserverTest.php b/app/code/Magento/Persistent/Test/Unit/Observer/CheckExpirePersistentQuoteObserverTest.php index 46dda1be365d4..b096dd2317a33 100644 --- a/app/code/Magento/Persistent/Test/Unit/Observer/CheckExpirePersistentQuoteObserverTest.php +++ b/app/code/Magento/Persistent/Test/Unit/Observer/CheckExpirePersistentQuoteObserverTest.php @@ -1,12 +1,16 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Persistent\Test\Unit\Observer; +use Magento\Quote\Model\Quote; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class CheckExpirePersistentQuoteObserverTest extends \PHPUnit\Framework\TestCase { /** @@ -54,6 +58,11 @@ class CheckExpirePersistentQuoteObserverTest extends \PHPUnit\Framework\TestCase */ private $requestMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject|Quote + */ + private $quoteMock; + /** * @inheritdoc */ @@ -83,6 +92,10 @@ protected function setUp() $this->checkoutSessionMock, $this->requestMock ); + $this->quoteMock = $this->getMockBuilder(Quote::class) + ->setMethods(['getCustomerIsGuest', 'getIsPersistent']) + ->disableOriginalConstructor() + ->getMock(); } public function testExecuteWhenCanNotApplyPersistentData() @@ -133,6 +146,11 @@ public function testExecuteWhenPersistentIsEnabled( ->willReturn(true); $this->persistentHelperMock->expects($this->once())->method('isEnabled')->willReturn(true); $this->sessionMock->expects($this->once())->method('isPersistent')->willReturn(false); + $this->checkoutSessionMock + ->method('getQuote') + ->willReturn($this->quoteMock); + $this->quoteMock->method('getCustomerIsGuest')->willReturn(true); + $this->quoteMock->method('getIsPersistent')->willReturn(true); $this->customerSessionMock ->expects($this->atLeastOnce()) ->method('isLoggedIn') diff --git a/app/code/Magento/Persistent/Test/Unit/Observer/SetQuotePersistentDataObserverTest.php b/app/code/Magento/Persistent/Test/Unit/Observer/SetQuotePersistentDataObserverTest.php index 6724743789cea..ffa829e8456cc 100644 --- a/app/code/Magento/Persistent/Test/Unit/Observer/SetQuotePersistentDataObserverTest.php +++ b/app/code/Magento/Persistent/Test/Unit/Observer/SetQuotePersistentDataObserverTest.php @@ -7,6 +7,9 @@ namespace Magento\Persistent\Test\Unit\Observer; +/** + * Observer test for setting "is_persistent" value to quote + */ class SetQuotePersistentDataObserverTest extends \PHPUnit\Framework\TestCase { /** @@ -83,7 +86,6 @@ public function testExecuteWhenQuoteNotExist() ->method('getEvent') ->will($this->returnValue($this->eventManagerMock)); $this->eventManagerMock->expects($this->once())->method('getQuote'); - $this->customerSessionMock->expects($this->never())->method('isLoggedIn'); $this->model->execute($this->observerMock); } @@ -98,8 +100,7 @@ public function testExecuteWhenSessionIsPersistent() ->expects($this->once()) ->method('getQuote') ->will($this->returnValue($this->quoteMock)); - $this->customerSessionMock->expects($this->once())->method('isLoggedIn')->will($this->returnValue(false)); - $this->helperMock->expects($this->once())->method('isShoppingCartPersist')->will($this->returnValue(false)); + $this->helperMock->expects($this->once())->method('isShoppingCartPersist')->will($this->returnValue(true)); $this->quoteManagerMock->expects($this->once())->method('isPersistent')->will($this->returnValue(true)); $this->quoteMock->expects($this->once())->method('setIsPersistent')->with(true); $this->model->execute($this->observerMock); diff --git a/app/code/Magento/Quote/Model/Quote/Item/Compare.php b/app/code/Magento/Quote/Model/Quote/Item/Compare.php index ddaa636ef32b3..76ba324518dc1 100644 --- a/app/code/Magento/Quote/Model/Quote/Item/Compare.php +++ b/app/code/Magento/Quote/Model/Quote/Item/Compare.php @@ -50,7 +50,7 @@ protected function getOptionValues($value) if (is_string($value) && $this->jsonValidator->isValid($value)) { $value = $this->serializer->unserialize($value); if (is_array($value)) { - unset($value['qty'], $value['uenc']); + unset($value['qty'], $value['uenc'], $value['related_product'], $value['item']); $value = array_filter($value, function ($optionValue) { return !empty($optionValue); }); diff --git a/app/code/Magento/Quote/Model/QuoteIdMask.php b/app/code/Magento/Quote/Model/QuoteIdMask.php index 7950ab47c3665..47b02db7650df 100644 --- a/app/code/Magento/Quote/Model/QuoteIdMask.php +++ b/app/code/Magento/Quote/Model/QuoteIdMask.php @@ -53,11 +53,14 @@ protected function _construct() * Initialize quote identifier before save * * @return $this + * @throws \Magento\Framework\Exception\LocalizedException */ public function beforeSave() { parent::beforeSave(); - $this->setMaskedId($this->randomDataGenerator->getUniqueHash()); + if (empty($this->getMaskedId())) { + $this->setMaskedId($this->randomDataGenerator->getUniqueHash()); + } return $this; } } diff --git a/app/code/Magento/Quote/Model/QuoteManagement.php b/app/code/Magento/Quote/Model/QuoteManagement.php index 2fcfd2dfadabb..0ad99ffe759f6 100644 --- a/app/code/Magento/Quote/Model/QuoteManagement.php +++ b/app/code/Magento/Quote/Model/QuoteManagement.php @@ -146,6 +146,16 @@ class QuoteManagement implements \Magento\Quote\Api\CartManagementInterface */ private $addressesToSync = []; + /** + * @var \Magento\Framework\App\RequestInterface + */ + private $request; + + /** + * @var \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress + */ + private $remoteAddress; + /** * @param EventManager $eventManager * @param QuoteValidator $quoteValidator @@ -169,6 +179,8 @@ class QuoteManagement implements \Magento\Quote\Api\CartManagementInterface * @param QuoteFactory $quoteFactory * @param \Magento\Quote\Model\QuoteIdMaskFactory|null $quoteIdMaskFactory * @param \Magento\Customer\Api\AddressRepositoryInterface|null $addressRepository + * @param \Magento\Framework\App\RequestInterface|null $request + * @param \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress $remoteAddress * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -193,7 +205,9 @@ public function __construct( \Magento\Customer\Api\AccountManagementInterface $accountManagement, \Magento\Quote\Model\QuoteFactory $quoteFactory, \Magento\Quote\Model\QuoteIdMaskFactory $quoteIdMaskFactory = null, - \Magento\Customer\Api\AddressRepositoryInterface $addressRepository = null + \Magento\Customer\Api\AddressRepositoryInterface $addressRepository = null, + \Magento\Framework\App\RequestInterface $request = null, + \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress $remoteAddress = null ) { $this->eventManager = $eventManager; $this->quoteValidator = $quoteValidator; @@ -219,6 +233,10 @@ public function __construct( ->get(\Magento\Quote\Model\QuoteIdMaskFactory::class); $this->addressRepository = $addressRepository ?: ObjectManager::getInstance() ->get(\Magento\Customer\Api\AddressRepositoryInterface::class); + $this->request = $request ?: ObjectManager::getInstance() + ->get(\Magento\Framework\App\RequestInterface::class); + $this->remoteAddress = $remoteAddress ?: ObjectManager::getInstance() + ->get(\Magento\Framework\HTTP\PhpEnvironment\RemoteAddress::class); } /** @@ -281,6 +299,7 @@ public function assignCustomer($cartId, $customerId, $storeId) throw new StateException( __("The customer can't be assigned to the cart because the customer already has an active cart.") ); + // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { } @@ -368,6 +387,14 @@ public function placeOrder($cartId, PaymentInterface $paymentMethod = null) $quote->setCustomerGroupId(\Magento\Customer\Api\Data\GroupInterface::NOT_LOGGED_IN_ID); } + $remoteAddress = $this->remoteAddress->getRemoteAddress(); + if ($remoteAddress !== false) { + $quote->setRemoteIp($remoteAddress); + $quote->setXForwardedFor( + $this->request->getServer('HTTP_X_FORWARDED_FOR') + ); + } + $this->eventManager->dispatch('checkout_submit_before', ['quote' => $quote]); $order = $this->submit($quote); @@ -627,12 +654,14 @@ private function rollbackAddresses( 'exception' => $e, ] ); + // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Exception $consecutiveException) { $message = sprintf( "An exception occurred on 'sales_model_service_quote_submit_failure' event: %s", $consecutiveException->getMessage() ); + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception($message, 0, $e); } } diff --git a/app/code/Magento/Quote/Model/QuoteRepository.php b/app/code/Magento/Quote/Model/QuoteRepository.php index 01c21bbbe50a7..30931821ddc7d 100644 --- a/app/code/Magento/Quote/Model/QuoteRepository.php +++ b/app/code/Magento/Quote/Model/QuoteRepository.php @@ -3,27 +3,33 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Quote\Model; +use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface; +use Magento\Framework\Api\Search\FilterGroup; +use Magento\Framework\Api\SearchCriteria\CollectionProcessor; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; +use Magento\Framework\Api\SearchCriteriaInterface; use Magento\Framework\App\ObjectManager; -use Magento\Framework\Api\SortOrder; +use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Quote\Api\CartRepositoryInterface; use Magento\Quote\Api\Data\CartInterface; -use Magento\Quote\Model\Quote; -use Magento\Store\Model\StoreManagerInterface; -use Magento\Framework\Api\Search\FilterGroup; -use Magento\Quote\Model\ResourceModel\Quote\Collection as QuoteCollection; -use Magento\Quote\Model\ResourceModel\Quote\CollectionFactory as QuoteCollectionFactory; -use Magento\Framework\Exception\InputException; -use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface; +use Magento\Quote\Api\Data\CartInterfaceFactory; +use Magento\Quote\Api\Data\CartSearchResultsInterfaceFactory; use Magento\Quote\Model\QuoteRepository\SaveHandler; use Magento\Quote\Model\QuoteRepository\LoadHandler; +use Magento\Quote\Model\ResourceModel\Quote\Collection as QuoteCollection; +use Magento\Quote\Model\ResourceModel\Quote\CollectionFactory as QuoteCollectionFactory; +use Magento\Store\Model\StoreManagerInterface; /** + * Quote repository. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class QuoteRepository implements \Magento\Quote\Api\CartRepositoryInterface +class QuoteRepository implements CartRepositoryInterface { /** * @var Quote[] @@ -37,6 +43,7 @@ class QuoteRepository implements \Magento\Quote\Api\CartRepositoryInterface /** * @var QuoteFactory + * @deprecated */ protected $quoteFactory; @@ -46,13 +53,13 @@ class QuoteRepository implements \Magento\Quote\Api\CartRepositoryInterface protected $storeManager; /** - * @var \Magento\Quote\Model\ResourceModel\Quote\Collection + * @var QuoteCollection * @deprecated 100.2.0 */ protected $quoteCollection; /** - * @var \Magento\Quote\Api\Data\CartSearchResultsInterfaceFactory + * @var CartSearchResultsInterfaceFactory */ protected $searchResultsDataFactory; @@ -77,43 +84,51 @@ class QuoteRepository implements \Magento\Quote\Api\CartRepositoryInterface private $collectionProcessor; /** - * @var \Magento\Quote\Model\ResourceModel\Quote\CollectionFactory + * @var QuoteCollectionFactory */ private $quoteCollectionFactory; + /** + * @var CartInterfaceFactory + */ + private $cartFactory; + /** * Constructor * * @param QuoteFactory $quoteFactory * @param StoreManagerInterface $storeManager - * @param \Magento\Quote\Model\ResourceModel\Quote\Collection $quoteCollection - * @param \Magento\Quote\Api\Data\CartSearchResultsInterfaceFactory $searchResultsDataFactory + * @param QuoteCollection $quoteCollection + * @param CartSearchResultsInterfaceFactory $searchResultsDataFactory * @param JoinProcessorInterface $extensionAttributesJoinProcessor * @param CollectionProcessorInterface|null $collectionProcessor - * @param \Magento\Quote\Model\ResourceModel\Quote\CollectionFactory|null $quoteCollectionFactory + * @param QuoteCollectionFactory|null $quoteCollectionFactory + * @param CartInterfaceFactory|null $cartFactory * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( QuoteFactory $quoteFactory, StoreManagerInterface $storeManager, - \Magento\Quote\Model\ResourceModel\Quote\Collection $quoteCollection, - \Magento\Quote\Api\Data\CartSearchResultsInterfaceFactory $searchResultsDataFactory, + QuoteCollection $quoteCollection, + CartSearchResultsInterfaceFactory $searchResultsDataFactory, JoinProcessorInterface $extensionAttributesJoinProcessor, CollectionProcessorInterface $collectionProcessor = null, - \Magento\Quote\Model\ResourceModel\Quote\CollectionFactory $quoteCollectionFactory = null + QuoteCollectionFactory $quoteCollectionFactory = null, + CartInterfaceFactory $cartFactory = null ) { $this->quoteFactory = $quoteFactory; $this->storeManager = $storeManager; $this->searchResultsDataFactory = $searchResultsDataFactory; $this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor; - $this->collectionProcessor = $collectionProcessor ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\Api\SearchCriteria\CollectionProcessor::class); - $this->quoteCollectionFactory = $quoteCollectionFactory ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Quote\Model\ResourceModel\Quote\CollectionFactory::class); + $this->collectionProcessor = $collectionProcessor ?: ObjectManager::getInstance() + ->get(CollectionProcessor::class); + $this->quoteCollectionFactory = $quoteCollectionFactory ?: ObjectManager::getInstance() + ->get(QuoteCollectionFactory::class); + $this->cartFactory = $cartFactory ?: ObjectManager::getInstance()->get(CartInterfaceFactory::class); } /** - * {@inheritdoc} + * @inheritdoc */ public function get($cartId, array $sharedStoreIds = []) { @@ -126,7 +141,7 @@ public function get($cartId, array $sharedStoreIds = []) } /** - * {@inheritdoc} + * @inheritdoc */ public function getForCustomer($customerId, array $sharedStoreIds = []) { @@ -140,7 +155,7 @@ public function getForCustomer($customerId, array $sharedStoreIds = []) } /** - * {@inheritdoc} + * @inheritdoc */ public function getActive($cartId, array $sharedStoreIds = []) { @@ -152,7 +167,7 @@ public function getActive($cartId, array $sharedStoreIds = []) } /** - * {@inheritdoc} + * @inheritdoc */ public function getActiveForCustomer($customerId, array $sharedStoreIds = []) { @@ -164,9 +179,9 @@ public function getActiveForCustomer($customerId, array $sharedStoreIds = []) } /** - * {@inheritdoc} + * @inheritdoc */ - public function save(\Magento\Quote\Api\Data\CartInterface $quote) + public function save(CartInterface $quote) { if ($quote->getId()) { $currentQuote = $this->get($quote->getId(), [$quote->getStoreId()]); @@ -184,9 +199,9 @@ public function save(\Magento\Quote\Api\Data\CartInterface $quote) } /** - * {@inheritdoc} + * @inheritdoc */ - public function delete(\Magento\Quote\Api\Data\CartInterface $quote) + public function delete(CartInterface $quote) { $quoteId = $quote->getId(); $customerId = $quote->getCustomerId(); @@ -203,13 +218,13 @@ public function delete(\Magento\Quote\Api\Data\CartInterface $quote) * @param int $identifier * @param int[] $sharedStoreIds * @throws NoSuchEntityException - * @return Quote + * @return CartInterface */ protected function loadQuote($loadMethod, $loadField, $identifier, array $sharedStoreIds = []) { - /** @var Quote $quote */ - $quote = $this->quoteFactory->create(); - if ($sharedStoreIds) { + /** @var CartInterface $quote */ + $quote = $this->cartFactory->create(); + if ($sharedStoreIds && method_exists($quote, 'setSharedStoreIds')) { $quote->setSharedStoreIds($sharedStoreIds); } $quote->setStoreId($this->storeManager->getStore()->getId())->$loadMethod($identifier); @@ -220,9 +235,9 @@ protected function loadQuote($loadMethod, $loadField, $identifier, array $shared } /** - * {@inheritdoc} + * @inheritdoc */ - public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria) + public function getList(SearchCriteriaInterface $searchCriteria) { $this->quoteCollection = $this->quoteCollectionFactory->create(); /** @var \Magento\Quote\Api\Data\CartSearchResultsInterface $searchData */ @@ -265,6 +280,7 @@ protected function addFilterGroupToCollection(FilterGroup $filterGroup, QuoteCol /** * Get new SaveHandler dependency for application code. + * * @return SaveHandler * @deprecated 100.1.0 */ @@ -277,6 +293,8 @@ private function getSaveHandler() } /** + * Get load handler instance. + * * @return LoadHandler * @deprecated 100.1.0 */ diff --git a/app/code/Magento/Quote/Observer/Frontend/Quote/Address/CollectTotalsObserver.php b/app/code/Magento/Quote/Observer/Frontend/Quote/Address/CollectTotalsObserver.php index 76c9a49b290c5..77dfec9603a5c 100644 --- a/app/code/Magento/Quote/Observer/Frontend/Quote/Address/CollectTotalsObserver.php +++ b/app/code/Magento/Quote/Observer/Frontend/Quote/Address/CollectTotalsObserver.php @@ -7,6 +7,11 @@ use Magento\Framework\Event\ObserverInterface; +/** + * Handle customer VAT number on collect_totals_before event of quote address. + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ class CollectTotalsObserver implements ObserverInterface { /** @@ -124,7 +129,7 @@ public function execute(\Magento\Framework\Event\Observer $observer) ); } - if ($groupId) { + if ($groupId !== null) { $address->setPrevQuoteCustomerGroupId($quote->getCustomerGroupId()); $quote->setCustomerGroupId($groupId); $this->customerSession->setCustomerGroupId($groupId); diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php index b61f95b4eee6c..8d8200cd6ef62 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php @@ -6,9 +6,12 @@ namespace Magento\Quote\Test\Unit\Model; +use Magento\Framework\App\RequestInterface; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\HTTP\PhpEnvironment\RemoteAddress; use Magento\Quote\Model\CustomerManagement; +use Magento\Quote\Model\QuoteIdMaskFactory; use Magento\Sales\Api\Data\OrderAddressInterface; /** @@ -137,6 +140,21 @@ class QuoteManagementTest extends \PHPUnit\Framework\TestCase */ private $quoteFactoryMock; + /** + * @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $requestMock; + + /** + * @var \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress|\PHPUnit_Framework_MockObject_MockObject + */ + private $remoteAddressMock; + + /** + * @var \Magento\Quote\Model\QuoteIdMaskFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $quoteIdMaskFactoryMock; + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -178,18 +196,20 @@ protected function setUp() ); $this->quoteMock = $this->createPartialMock(\Magento\Quote\Model\Quote::class, [ - 'getId', - 'getCheckoutMethod', - 'setCheckoutMethod', - 'setCustomerId', - 'setCustomerEmail', - 'getBillingAddress', - 'setCustomerIsGuest', - 'setCustomerGroupId', - 'assignCustomer', - 'getPayment', - 'collectTotals' - ]); + 'assignCustomer', + 'collectTotals', + 'getBillingAddress', + 'getCheckoutMethod', + 'getPayment', + 'setCheckoutMethod', + 'setCustomerEmail', + 'setCustomerGroupId', + 'setCustomerId', + 'setCustomerIsGuest', + 'setRemoteIp', + 'setXForwardedFor', + 'getId', + ]); $this->quoteAddressFactory = $this->createPartialMock( \Magento\Quote\Model\Quote\AddressFactory::class, @@ -237,8 +257,11 @@ protected function setUp() // Set the new dependency $this->quoteIdMock = $this->createMock(\Magento\Quote\Model\QuoteIdMask::class); - $quoteIdFactoryMock = $this->createPartialMock(\Magento\Quote\Model\QuoteIdMaskFactory::class, ['create']); - $this->setPropertyValue($this->model, 'quoteIdMaskFactory', $quoteIdFactoryMock); + $this->quoteIdMaskFactoryMock = $this->createPartialMock(QuoteIdMaskFactory::class, ['create']); + $this->setPropertyValue($this->model, 'quoteIdMaskFactory', $this->quoteIdMaskFactoryMock); + + $this->requestMock = $this->createPartialMockForAbstractClass(RequestInterface::class, ['getServer']); + $this->remoteAddressMock = $this->createMock(RemoteAddress::class); } public function testCreateEmptyCartAnonymous() @@ -676,7 +699,11 @@ public function testPlaceOrderIfCustomerIsGuest() 'checkoutSession' => $this->checkoutSessionMock, 'customerSession' => $this->customerSessionMock, 'accountManagement' => $this->accountManagementMock, - 'quoteFactory' => $this->quoteFactoryMock + 'quoteFactory' => $this->quoteFactoryMock, + 'quoteIdMaskFactory' => $this->quoteIdMaskFactoryMock, + 'addressRepository' => $this->addressRepositoryMock, + 'request' => $this->requestMock, + 'remoteAddress' => $this->remoteAddressMock, ] ) ->getMock(); @@ -709,13 +736,15 @@ public function testPlaceOrder() $orderId = 332; $orderIncrementId = 100003332; $orderStatus = 'status1'; + $remoteAddress = '192.168.1.10'; + $forwardedForIp = '192.168.1.11'; /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Quote\Model\QuoteManagement $service */ $service = $this->getMockBuilder(\Magento\Quote\Model\QuoteManagement::class) ->setMethods(['submit']) ->setConstructorArgs( [ - 'eventManager' => $this->eventManager, + 'eventManager' => $this->eventManager, 'quoteValidator' => $this->quoteValidator, 'orderFactory' => $this->orderFactory, 'orderManagement' => $this->orderManagement, @@ -734,7 +763,11 @@ public function testPlaceOrder() 'checkoutSession' => $this->checkoutSessionMock, 'customerSession' => $this->customerSessionMock, 'accountManagement' => $this->accountManagementMock, - 'quoteFactory' => $this->quoteFactoryMock + 'quoteFactory' => $this->quoteFactoryMock, + 'quoteIdMaskFactory' => $this->quoteIdMaskFactoryMock, + 'addressRepository' => $this->addressRepositoryMock, + 'request' => $this->requestMock, + 'remoteAddress' => $this->remoteAddressMock, ] ) ->getMock(); @@ -762,6 +795,17 @@ public function testPlaceOrder() ->method('setCustomerIsGuest') ->with(true); + $this->remoteAddressMock + ->method('getRemoteAddress') + ->willReturn($remoteAddress); + + $this->requestMock + ->method('getServer') + ->willReturn($forwardedForIp); + + $this->quoteMock->expects($this->once())->method('setRemoteIp')->with($remoteAddress); + $this->quoteMock->expects($this->once())->method('setXForwardedFor')->with($forwardedForIp); + $service->expects($this->once())->method('submit')->willReturn($orderMock); $this->quoteMock->expects($this->atLeastOnce())->method('getId')->willReturn($cartId); diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteRepositoryTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteRepositoryTest.php index 3101c7d0677a9..095e1760df86f 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/QuoteRepositoryTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/QuoteRepositoryTest.php @@ -5,17 +5,31 @@ */ namespace Magento\Quote\Test\Unit\Model; +use Magento\Framework\Api\SearchCriteria; +use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; use Magento\Framework\Api\SortOrder; use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Quote\Api\Data\CartInterface; +use Magento\Quote\Api\Data\CartInterfaceFactory; +use Magento\Quote\Api\Data\CartSearchResultsInterface; +use Magento\Quote\Api\Data\CartSearchResultsInterfaceFactory; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\QuoteRepository; use Magento\Quote\Model\QuoteRepository\LoadHandler; use Magento\Quote\Model\QuoteRepository\SaveHandler; +use Magento\Quote\Model\ResourceModel\Quote\Collection; use Magento\Quote\Model\ResourceModel\Quote\CollectionFactory; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use PHPUnit\Framework\TestCase; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.TooManyMethods) */ -class QuoteRepositoryTest extends \PHPUnit\Framework\TestCase +class QuoteRepositoryTest extends TestCase { /** * @var \Magento\Quote\Api\CartRepositoryInterface @@ -23,32 +37,32 @@ class QuoteRepositoryTest extends \PHPUnit\Framework\TestCase private $model; /** - * @var \Magento\Quote\Model\QuoteFactory|\PHPUnit_Framework_MockObject_MockObject + * @var CartInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject */ - private $quoteFactoryMock; + private $cartFactoryMock; /** - * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ private $storeManagerMock; /** - * @var \Magento\Store\Model\Store|\PHPUnit_Framework_MockObject_MockObject + * @var Store|\PHPUnit_Framework_MockObject_MockObject */ private $storeMock; /** - * @var \Magento\Quote\Model\Quote|\PHPUnit_Framework_MockObject_MockObject + * @var Quote|\PHPUnit_Framework_MockObject_MockObject */ private $quoteMock; /** - * @var \Magento\Quote\Api\Data\CartSearchResultsInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + * @var CartSearchResultsInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject */ private $searchResultsDataFactory; /** - * @var \Magento\Quote\Model\ResourceModel\Quote\Collection|\PHPUnit_Framework_MockObject_MockObject + * @var Collection|\PHPUnit_Framework_MockObject_MockObject */ private $quoteCollectionMock; @@ -78,21 +92,21 @@ class QuoteRepositoryTest extends \PHPUnit\Framework\TestCase private $objectManagerMock; /** - * @var \Magento\Quote\Model\ResourceModel\Quote\CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + * @var CollectionFactory|\PHPUnit_Framework_MockObject_MockObject */ private $quoteCollectionFactoryMock; protected function setUp() { - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $objectManager = new ObjectManager($this); - $this->objectManagerMock = $this->createMock(\Magento\Framework\ObjectManagerInterface::class); + $this->objectManagerMock = $this->createMock(ObjectManagerInterface::class); \Magento\Framework\App\ObjectManager::setInstance($this->objectManagerMock); - $this->quoteFactoryMock = $this->createPartialMock(\Magento\Quote\Model\QuoteFactory::class, ['create']); - $this->storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); + $this->cartFactoryMock = $this->createPartialMock(CartInterfaceFactory::class, ['create']); + $this->storeManagerMock = $this->createMock(StoreManagerInterface::class); $this->quoteMock = $this->createPartialMock( - \Magento\Quote\Model\Quote::class, + Quote::class, [ 'load', 'loadByIdWithoutStore', @@ -108,35 +122,35 @@ protected function setUp() 'getData' ] ); - $this->storeMock = $this->createMock(\Magento\Store\Model\Store::class); + $this->storeMock = $this->createMock(Store::class); $this->searchResultsDataFactory = $this->createPartialMock( - \Magento\Quote\Api\Data\CartSearchResultsInterfaceFactory::class, + CartSearchResultsInterfaceFactory::class, ['create'] ); $this->quoteCollectionMock = - $this->createMock(\Magento\Quote\Model\ResourceModel\Quote\Collection::class); + $this->createMock(Collection::class); $this->extensionAttributesJoinProcessorMock = $this->createMock( - \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface::class + JoinProcessorInterface::class ); $this->collectionProcessor = $this->createMock( - \Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface::class + CollectionProcessorInterface::class ); $this->quoteCollectionFactoryMock = $this->createPartialMock( - \Magento\Quote\Model\ResourceModel\Quote\CollectionFactory::class, + CollectionFactory::class, ['create'] ); $this->model = $objectManager->getObject( - \Magento\Quote\Model\QuoteRepository::class, + QuoteRepository::class, [ - 'quoteFactory' => $this->quoteFactoryMock, 'storeManager' => $this->storeManagerMock, 'searchResultsDataFactory' => $this->searchResultsDataFactory, 'quoteCollection' => $this->quoteCollectionMock, 'extensionAttributesJoinProcessor' => $this->extensionAttributesJoinProcessorMock, 'collectionProcessor' => $this->collectionProcessor, - 'quoteCollectionFactory' => $this->quoteCollectionFactoryMock + 'quoteCollectionFactory' => $this->quoteCollectionFactoryMock, + 'cartFactory' => $this->cartFactoryMock ] ); @@ -161,7 +175,7 @@ public function testGetWithExceptionById() { $cartId = 14; - $this->quoteFactoryMock->expects($this->once())->method('create')->willReturn($this->quoteMock); + $this->cartFactoryMock->expects($this->once())->method('create')->willReturn($this->quoteMock); $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($this->storeMock); $this->storeMock->expects($this->once())->method('getId')->willReturn(1); $this->quoteMock->expects($this->never())->method('setSharedStoreIds'); @@ -178,7 +192,7 @@ public function testGet() { $cartId = 15; - $this->quoteFactoryMock->expects(static::once()) + $this->cartFactoryMock->expects(static::once()) ->method('create') ->willReturn($this->quoteMock); $this->storeManagerMock->expects(static::once()) @@ -211,7 +225,7 @@ public function testGetForCustomerAfterGet() $cartId = 15; $customerId = 23; - $this->quoteFactoryMock->expects(static::exactly(2)) + $this->cartFactoryMock->expects(static::exactly(2)) ->method('create') ->willReturn($this->quoteMock); $this->storeManagerMock->expects(static::exactly(2)) @@ -249,7 +263,7 @@ public function testGetWithSharedStoreIds() $cartId = 16; $sharedStoreIds = [1, 2]; - $this->quoteFactoryMock->expects($this->once())->method('create')->willReturn($this->quoteMock); + $this->cartFactoryMock->expects($this->once())->method('create')->willReturn($this->quoteMock); $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($this->storeMock); $this->storeMock->expects($this->once())->method('getId')->willReturn(1); $this->quoteMock->expects($this->once()) @@ -275,7 +289,7 @@ public function testGetForCustomer() $cartId = 17; $customerId = 23; - $this->quoteFactoryMock->expects(static::once()) + $this->cartFactoryMock->expects(static::once()) ->method('create') ->willReturn($this->quoteMock); $this->storeManagerMock->expects(static::once()) @@ -310,7 +324,7 @@ public function testGetActiveWithExceptionById() { $cartId = 14; - $this->quoteFactoryMock->expects($this->once())->method('create')->willReturn($this->quoteMock); + $this->cartFactoryMock->expects($this->once())->method('create')->willReturn($this->quoteMock); $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($this->storeMock); $this->storeMock->expects($this->once())->method('getId')->willReturn(1); $this->quoteMock->expects($this->never())->method('setSharedStoreIds'); @@ -332,7 +346,7 @@ public function testGetActiveWithExceptionByIsActive() { $cartId = 15; - $this->quoteFactoryMock->expects($this->once())->method('create')->willReturn($this->quoteMock); + $this->cartFactoryMock->expects($this->once())->method('create')->willReturn($this->quoteMock); $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($this->storeMock); $this->storeMock->expects($this->once())->method('getId')->willReturn(1); $this->quoteMock->expects($this->never())->method('setSharedStoreIds'); @@ -355,7 +369,7 @@ public function testGetActive() { $cartId = 15; - $this->quoteFactoryMock->expects($this->once())->method('create')->willReturn($this->quoteMock); + $this->cartFactoryMock->expects($this->once())->method('create')->willReturn($this->quoteMock); $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($this->storeMock); $this->storeMock->expects($this->once())->method('getId')->willReturn(1); $this->quoteMock->expects($this->never())->method('setSharedStoreIds'); @@ -379,7 +393,7 @@ public function testGetActiveWithSharedStoreIds() $cartId = 16; $sharedStoreIds = [1, 2]; - $this->quoteFactoryMock->expects($this->once())->method('create')->willReturn($this->quoteMock); + $this->cartFactoryMock->expects($this->once())->method('create')->willReturn($this->quoteMock); $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($this->storeMock); $this->storeMock->expects($this->once())->method('getId')->willReturn(1); $this->quoteMock->expects($this->once()) @@ -406,7 +420,7 @@ public function testGetActiveForCustomer() $cartId = 17; $customerId = 23; - $this->quoteFactoryMock->expects($this->once())->method('create')->willReturn($this->quoteMock); + $this->cartFactoryMock->expects($this->once())->method('create')->willReturn($this->quoteMock); $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($this->storeMock); $this->storeMock->expects($this->once())->method('getId')->willReturn(1); $this->quoteMock->expects($this->never())->method('setSharedStoreIds'); @@ -430,14 +444,14 @@ public function testSave() { $cartId = 100; $quoteMock = $this->createPartialMock( - \Magento\Quote\Model\Quote::class, + Quote::class, ['getId', 'getCustomerId', 'getStoreId', 'hasData', 'setData'] ); $quoteMock->expects($this->exactly(3))->method('getId')->willReturn($cartId); $quoteMock->expects($this->once())->method('getCustomerId')->willReturn(2); $quoteMock->expects($this->once())->method('getStoreId')->willReturn(5); - $this->quoteFactoryMock->expects($this->once())->method('create')->willReturn($this->quoteMock); + $this->cartFactoryMock->expects($this->once())->method('create')->willReturn($this->quoteMock); $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($this->storeMock); $this->storeMock->expects($this->once())->method('getId')->willReturn(1); $this->quoteMock->expects($this->once())->method('getId')->willReturn($cartId); @@ -481,8 +495,8 @@ public function testGetList() ->method('load') ->with($cartMock); - $searchResult = $this->createMock(\Magento\Quote\Api\Data\CartSearchResultsInterface::class); - $searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteria::class); + $searchResult = $this->createMock(CartSearchResultsInterface::class); + $searchCriteriaMock = $this->createMock(SearchCriteria::class); $this->searchResultsDataFactory ->expects($this->once()) ->method('create') @@ -495,7 +509,7 @@ public function testGetList() $this->extensionAttributesJoinProcessorMock->expects($this->once()) ->method('process') ->with( - $this->isInstanceOf(\Magento\Quote\Model\ResourceModel\Quote\Collection::class) + $this->isInstanceOf(Collection::class) ); $this->quoteCollectionMock->expects($this->atLeastOnce())->method('getItems')->willReturn([$cartMock]); $searchResult->expects($this->once())->method('setTotalCount')->with($pageSize); diff --git a/app/code/Magento/Quote/Test/Unit/Observer/Frontend/Quote/Address/CollectTotalsObserverTest.php b/app/code/Magento/Quote/Test/Unit/Observer/Frontend/Quote/Address/CollectTotalsObserverTest.php index f3357f8aacd31..4ea067c9be8f6 100644 --- a/app/code/Magento/Quote/Test/Unit/Observer/Frontend/Quote/Address/CollectTotalsObserverTest.php +++ b/app/code/Magento/Quote/Test/Unit/Observer/Frontend/Quote/Address/CollectTotalsObserverTest.php @@ -199,7 +199,7 @@ public function testDispatchWithCustomerCountryNotInEUAndNotLoggedCustomerInGrou ->method('getNotLoggedInGroup') ->will($this->returnValue($this->groupInterfaceMock)); $this->groupInterfaceMock->expects($this->once()) - ->method('getId')->will($this->returnValue(0)); + ->method('getId')->will($this->returnValue(null)); $this->vatValidatorMock->expects($this->once()) ->method('isEnabled') ->with($this->quoteAddressMock, $this->storeId) @@ -220,9 +220,6 @@ public function testDispatchWithCustomerCountryNotInEUAndNotLoggedCustomerInGrou $this->returnValue(false) ); - $groupMock = $this->getMockBuilder(\Magento\Customer\Api\Data\GroupInterface::class) - ->disableOriginalConstructor() - ->getMock(); $this->customerMock->expects($this->once())->method('getId')->will($this->returnValue(null)); /** Assertions */ diff --git a/app/code/Magento/Quote/etc/db_schema.xml b/app/code/Magento/Quote/etc/db_schema.xml index 48954f1af90fc..b4c75fc1d21d0 100644 --- a/app/code/Magento/Quote/etc/db_schema.xml +++ b/app/code/Magento/Quote/etc/db_schema.xml @@ -108,7 +108,7 @@ default="0" comment="Quote Id"/> <column xsi:type="timestamp" name="created_at" on_update="false" nullable="false" default="CURRENT_TIMESTAMP" comment="Created At"/> - <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="0" + <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="CURRENT_TIMESTAMP" comment="Updated At"/> <column xsi:type="int" name="customer_id" padding="10" unsigned="true" nullable="true" identity="false" comment="Customer Id"/> @@ -220,7 +220,7 @@ default="0" comment="Quote Id"/> <column xsi:type="timestamp" name="created_at" on_update="false" nullable="false" default="CURRENT_TIMESTAMP" comment="Created At"/> - <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="0" + <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="CURRENT_TIMESTAMP" comment="Updated At"/> <column xsi:type="int" name="product_id" padding="10" unsigned="true" nullable="true" identity="false" comment="Product Id"/> @@ -324,7 +324,7 @@ default="0" comment="Quote Item Id"/> <column xsi:type="timestamp" name="created_at" on_update="false" nullable="false" default="CURRENT_TIMESTAMP" comment="Created At"/> - <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="0" + <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="CURRENT_TIMESTAMP" comment="Updated At"/> <column xsi:type="text" name="applied_rule_ids" nullable="true" comment="Applied Rule Ids"/> <column xsi:type="text" name="additional_data" nullable="true" comment="Additional Data"/> @@ -436,7 +436,7 @@ default="0" comment="Quote Id"/> <column xsi:type="timestamp" name="created_at" on_update="false" nullable="false" default="CURRENT_TIMESTAMP" comment="Created At"/> - <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="0" + <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="CURRENT_TIMESTAMP" comment="Updated At"/> <column xsi:type="varchar" name="method" nullable="true" length="255" comment="Method"/> <column xsi:type="varchar" name="cc_type" nullable="true" length="255" comment="Cc Type"/> @@ -472,7 +472,7 @@ default="0" comment="Address Id"/> <column xsi:type="timestamp" name="created_at" on_update="false" nullable="false" default="CURRENT_TIMESTAMP" comment="Created At"/> - <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="0" + <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="CURRENT_TIMESTAMP" comment="Updated At"/> <column xsi:type="varchar" name="carrier" nullable="true" length="255" comment="Carrier"/> <column xsi:type="varchar" name="carrier_title" nullable="true" length="255" comment="Carrier Title"/> diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php index 6868ce3f7f1ff..ad730288e5cc2 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php @@ -66,8 +66,8 @@ public function __construct( public function execute(Quote $cart, array $cartItemData): void { $sku = $this->extractSku($cartItemData); - $qty = $this->extractQty($cartItemData); - if ($qty <= 0) { + $quantity = $this->extractQuantity($cartItemData); + if ($quantity <= 0) { throw new GraphQlInputException( __('Please enter a number greater than 0 in this field.') ); @@ -81,7 +81,7 @@ public function execute(Quote $cart, array $cartItemData): void } try { - $result = $cart->addProduct($product, $this->createBuyRequest($qty, $customizableOptions)); + $result = $cart->addProduct($product, $this->createBuyRequest($quantity, $customizableOptions)); } catch (\Exception $e) { throw new GraphQlInputException( __( @@ -113,19 +113,19 @@ private function extractSku(array $cartItemData): string } /** - * Extract Qty from cart item data + * Extract quantity from cart item data * * @param array $cartItemData * @return float * @throws GraphQlInputException */ - private function extractQty(array $cartItemData): float + private function extractQuantity(array $cartItemData): float { - $qty = $this->arrayManager->get('data/qty', $cartItemData); - if (!isset($qty)) { - throw new GraphQlInputException(__('Missing key "qty" in cart item data')); + $quantity = $this->arrayManager->get('data/quantity', $cartItemData); + if (!isset($quantity)) { + throw new GraphQlInputException(__('Missing key "quantity" in cart item data')); } - return (float)$qty; + return (float)$quantity; } /** @@ -148,15 +148,15 @@ private function extractCustomizableOptions(array $cartItemData): array /** * Format GraphQl input data to a shape that buy request has * - * @param float $qty + * @param float $quantity * @param array $customOptions * @return DataObject */ - private function createBuyRequest(float $qty, array $customOptions): DataObject + private function createBuyRequest(float $quantity, array $customOptions): DataObject { return $this->dataObjectFactory->create([ 'data' => [ - 'qty' => $qty, + 'qty' => $quantity, 'options' => $customOptions, ], ]); diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/CreateEmptyCartForCustomer.php b/app/code/Magento/QuoteGraphQl/Model/Cart/CreateEmptyCartForCustomer.php new file mode 100644 index 0000000000000..481bad090dac1 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/CreateEmptyCartForCustomer.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\QuoteGraphQl\Model\Cart; + +use Magento\Quote\Api\CartManagementInterface; +use Magento\Quote\Model\QuoteIdMaskFactory; +use Magento\Quote\Model\ResourceModel\Quote\QuoteIdMask as QuoteIdMaskResourceModel; + +/** + * Create empty cart for customer + */ +class CreateEmptyCartForCustomer +{ + /** + * @var CartManagementInterface + */ + private $cartManagement; + + /** + * @var QuoteIdMaskFactory + */ + private $quoteIdMaskFactory; + + /** + * @var QuoteIdMaskResourceModel + */ + private $quoteIdMaskResourceModel; + + /** + * @param CartManagementInterface $cartManagement + * @param QuoteIdMaskFactory $quoteIdMaskFactory + * @param QuoteIdMaskResourceModel $quoteIdMaskResourceModel + */ + public function __construct( + CartManagementInterface $cartManagement, + QuoteIdMaskFactory $quoteIdMaskFactory, + QuoteIdMaskResourceModel $quoteIdMaskResourceModel + ) { + $this->cartManagement = $cartManagement; + $this->quoteIdMaskFactory = $quoteIdMaskFactory; + $this->quoteIdMaskResourceModel = $quoteIdMaskResourceModel; + } + + /** + * Create empty cart for customer + * + * @param int $customerId + * @param string|null $predefinedMaskedQuoteId + * @return string + */ + public function execute(int $customerId, string $predefinedMaskedQuoteId = null): string + { + $quoteId = $this->cartManagement->createEmptyCartForCustomer($customerId); + + $quoteIdMask = $this->quoteIdMaskFactory->create(); + $quoteIdMask->setQuoteId($quoteId); + + if (isset($predefinedMaskedQuoteId)) { + $quoteIdMask->setMaskedId($predefinedMaskedQuoteId); + } + + $this->quoteIdMaskResourceModel->save($quoteIdMask); + return $quoteIdMask->getMaskedId(); + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/CreateEmptyCartForGuest.php b/app/code/Magento/QuoteGraphQl/Model/Cart/CreateEmptyCartForGuest.php new file mode 100644 index 0000000000000..a6396ed6352ab --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/CreateEmptyCartForGuest.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\QuoteGraphQl\Model\Cart; + +use Magento\Quote\Api\GuestCartManagementInterface; +use Magento\Quote\Model\QuoteIdMaskFactory; +use Magento\Quote\Model\ResourceModel\Quote\QuoteIdMask as QuoteIdMaskResourceModel; + +/** + * Create empty cart for guest + */ +class CreateEmptyCartForGuest +{ + /** + * @var GuestCartManagementInterface + */ + private $guestCartManagement; + + /** + * @var QuoteIdMaskFactory + */ + private $quoteIdMaskFactory; + + /** + * @var QuoteIdMaskResourceModel + */ + private $quoteIdMaskResourceModel; + + /** + * @param GuestCartManagementInterface $guestCartManagement + * @param QuoteIdMaskFactory $quoteIdMaskFactory + * @param QuoteIdMaskResourceModel $quoteIdMaskResourceModel + */ + public function __construct( + GuestCartManagementInterface $guestCartManagement, + QuoteIdMaskFactory $quoteIdMaskFactory, + QuoteIdMaskResourceModel $quoteIdMaskResourceModel + ) { + $this->guestCartManagement = $guestCartManagement; + $this->quoteIdMaskFactory = $quoteIdMaskFactory; + $this->quoteIdMaskResourceModel = $quoteIdMaskResourceModel; + } + + /** + * Create empty cart for guest + * + * @param string|null $predefinedMaskedQuoteId + * @return string + */ + public function execute(string $predefinedMaskedQuoteId = null): string + { + $maskedQuoteId = $this->guestCartManagement->createEmptyCart(); + + if (isset($predefinedMaskedQuoteId)) { + $quoteIdMask = $this->quoteIdMaskFactory->create(); + $this->quoteIdMaskResourceModel->load($quoteIdMask, $maskedQuoteId, 'masked_id'); + + $quoteIdMask->setMaskedId($predefinedMaskedQuoteId); + $this->quoteIdMaskResourceModel->save($quoteIdMask); + } + return $predefinedMaskedQuoteId ?? $maskedQuoteId; + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractQuoteAddressData.php b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractQuoteAddressData.php index 840dedb4f274e..c4d795293220f 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractQuoteAddressData.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractQuoteAddressData.php @@ -41,17 +41,7 @@ public function execute(QuoteAddress $address): array $addressData = $this->dataObjectConverter->toFlatArray($address, [], AddressInterface::class); $addressData['model'] = $address; - if ($address->getAddressType() == AbstractAddress::TYPE_SHIPPING) { - $addressType = 'SHIPPING'; - } elseif ($address->getAddressType() == AbstractAddress::TYPE_BILLING) { - $addressType = 'BILLING'; - } else { - $addressType = null; - } - $addressData = array_merge($addressData, [ - 'address_id' => $address->getId(), - 'address_type' => $addressType, 'country' => [ 'code' => $address->getCountryId(), 'label' => $address->getCountry() diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/GetQuoteAddress.php b/app/code/Magento/QuoteGraphQl/Model/Cart/GetQuoteAddress.php deleted file mode 100644 index 89124c594dd87..0000000000000 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/GetQuoteAddress.php +++ /dev/null @@ -1,83 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\QuoteGraphQl\Model\Cart; - -use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; -use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; -use Magento\Quote\Api\Data\AddressInterface; -use Magento\Quote\Api\Data\AddressInterfaceFactory; -use Magento\Quote\Api\Data\CartInterface; -use Magento\Quote\Model\ResourceModel\Quote\Address as AddressResource; - -/** - * Get quote address - */ -class GetQuoteAddress -{ - /** - * @var AddressInterfaceFactory - */ - private $quoteAddressFactory; - - /** - * @var AddressResource - */ - private $quoteAddressResource; - - /** - * @param AddressInterfaceFactory $quoteAddressFactory - * @param AddressResource $quoteAddressResource - */ - public function __construct( - AddressInterfaceFactory $quoteAddressFactory, - AddressResource $quoteAddressResource - ) { - $this->quoteAddressFactory = $quoteAddressFactory; - $this->quoteAddressResource = $quoteAddressResource; - } - - /** - * Get quote address - * - * @param CartInterface $cart - * @param int $quoteAddressId - * @param int|null $customerId - * @return AddressInterface - * @throws GraphQlAuthorizationException - * @throws GraphQlNoSuchEntityException - */ - public function execute(CartInterface $cart, int $quoteAddressId, ?int $customerId): AddressInterface - { - $quoteAddress = $this->quoteAddressFactory->create(); - - $this->quoteAddressResource->load($quoteAddress, $quoteAddressId); - if (null === $quoteAddress->getId()) { - throw new GraphQlNoSuchEntityException( - __('Could not find a cart address with ID "%cart_address_id"', ['cart_address_id' => $quoteAddressId]) - ); - } - - // TODO: GetQuoteAddress::execute should depend only on AddressInterface contract - // https://github.com/magento/graphql-ce/issues/550 - if ($quoteAddress->getQuoteId() !== $cart->getId()) { - throw new GraphQlNoSuchEntityException( - __('Cart does not contain address with ID "%cart_address_id"', ['cart_address_id' => $quoteAddressId]) - ); - } - - if ((int)$quoteAddress->getCustomerId() !== (int)$customerId) { - throw new GraphQlAuthorizationException( - __( - 'The current user cannot use cart address with ID "%cart_address_id"', - ['cart_address_id' => $quoteAddressId] - ) - ); - } - return $quoteAddress; - } -} diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCart.php index 730cf1b0ffee3..b2526bdc04e98 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCart.php @@ -16,25 +16,17 @@ */ class SetShippingMethodsOnCart implements SetShippingMethodsOnCartInterface { - /** - * @var GetQuoteAddress - */ - private $getQuoteAddress; - /** * @var AssignShippingMethodToCart */ private $assignShippingMethodToCart; /** - * @param GetQuoteAddress $getQuoteAddress * @param AssignShippingMethodToCart $assignShippingMethodToCart */ public function __construct( - GetQuoteAddress $getQuoteAddress, AssignShippingMethodToCart $assignShippingMethodToCart ) { - $this->getQuoteAddress = $getQuoteAddress; $this->assignShippingMethodToCart = $assignShippingMethodToCart; } @@ -50,11 +42,6 @@ public function execute(ContextInterface $context, CartInterface $cart, array $s } $shippingMethodInput = current($shippingMethodsInput); - if (!isset($shippingMethodInput['cart_address_id']) || empty($shippingMethodInput['cart_address_id'])) { - throw new GraphQlInputException(__('Required parameter "cart_address_id" is missing.')); - } - $cartAddressId = $shippingMethodInput['cart_address_id']; - if (!isset($shippingMethodInput['carrier_code']) || empty($shippingMethodInput['carrier_code'])) { throw new GraphQlInputException(__('Required parameter "carrier_code" is missing.')); } @@ -65,7 +52,7 @@ public function execute(ContextInterface $context, CartInterface $cart, array $s } $methodCode = $shippingMethodInput['method_code']; - $quoteAddress = $this->getQuoteAddress->execute($cart, $cartAddressId, $context->getUserId()); - $this->assignShippingMethodToCart->execute($cart, $quoteAddress, $carrierCode, $methodCode); + $shippingAddress = $cart->getShippingAddress(); + $this->assignShippingMethodToCart->execute($cart, $shippingAddress, $carrierCode, $methodCode); } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartAddressTypeResolver.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartAddressTypeResolver.php new file mode 100644 index 0000000000000..6f7c70b09cb0c --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartAddressTypeResolver.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\QuoteGraphQl\Model\Resolver; + +use Magento\Customer\Model\Address\AbstractAddress; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Query\Resolver\TypeResolverInterface; +use Magento\Quote\Model\Quote\Address; + +/** + * @inheritdoc + */ +class CartAddressTypeResolver implements TypeResolverInterface +{ + /** + * @inheritdoc + */ + public function resolveType(array $data): string + { + if (!isset($data['model'])) { + throw new LocalizedException(__('Missing key "model" in cart address data')); + } + /** @var Address $address */ + $address = $data['model']; + + if ($address->getAddressType() == AbstractAddress::TYPE_SHIPPING) { + $addressType = 'ShippingCartAddress'; + } elseif ($address->getAddressType() == AbstractAddress::TYPE_BILLING) { + $addressType = 'BillingCartAddress'; + } else { + throw new LocalizedException(__('Unsupported cart address type')); + } + return $addressType; + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartEmail.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartEmail.php new file mode 100644 index 0000000000000..8d0cb114d8315 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartEmail.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\QuoteGraphQl\Model\Resolver; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Quote\Model\Quote; +use Magento\QuoteGraphQl\Model\Cart\GetCartForUser; + +/** + * @inheritdoc + */ +class CartEmail implements ResolverInterface +{ + /** + * @var GetCartForUser + */ + private $getCartForUser; + + /** + * @param GetCartForUser $getCartForUser + */ + public function __construct( + GetCartForUser $getCartForUser + ) { + $this->getCartForUser = $getCartForUser; + } + + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + if (!isset($value['model'])) { + throw new LocalizedException(__('"model" value should be specified')); + } + /** @var Quote $cart */ + $cart = $value['model']; + + return $cart->getCustomerEmail(); + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartItems.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartItems.php index da6619d15a489..f259dcd4a78ff 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartItems.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartItems.php @@ -38,7 +38,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $itemsData[] = [ 'id' => $cartItem->getItemId(), - 'qty' => $cartItem->getQty(), + 'quantity' => $cartItem->getQty(), 'product' => $productData, 'model' => $cartItem, ]; diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php new file mode 100644 index 0000000000000..7a9bdd926764c --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartPrices.php @@ -0,0 +1,87 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\QuoteGraphQl\Model\Resolver; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\Quote\Address\Total; +use Magento\Quote\Model\Quote\TotalsCollector; + +/** + * @inheritdoc + */ +class CartPrices implements ResolverInterface +{ + /** + * @var TotalsCollector + */ + private $totalsCollector; + + /** + * @param TotalsCollector $totalsCollector + */ + public function __construct( + TotalsCollector $totalsCollector + ) { + $this->totalsCollector = $totalsCollector; + } + + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + if (!isset($value['model'])) { + throw new LocalizedException(__('"model" value should be specified')); + } + + /** @var Quote $quote */ + $quote = $value['model']; + $cartTotals = $this->totalsCollector->collectQuoteTotals($quote); + $currency = $quote->getQuoteCurrencyCode(); + + return [ + 'grand_total' => ['value' => $cartTotals->getGrandTotal(), 'currency' => $currency], + 'subtotal_including_tax' => ['value' => $cartTotals->getSubtotalInclTax(), 'currency' => $currency], + 'subtotal_excluding_tax' => ['value' => $cartTotals->getSubtotal(), 'currency' => $currency], + 'subtotal_with_discount_excluding_tax' => [ + 'value' => $cartTotals->getSubtotalWithDiscount(), 'currency' => $currency + ], + 'applied_taxes' => $this->getAppliedTaxes($cartTotals, $currency), + 'model' => $quote + ]; + } + + /** + * Returns taxes applied to the current quote + * + * @param Total $total + * @param string $currency + * @return array + */ + private function getAppliedTaxes(Total $total, string $currency): array + { + $appliedTaxesData = []; + $appliedTaxes = $total->getAppliedTaxes(); + + if (count($appliedTaxes) === 0) { + return $appliedTaxesData; + } + + foreach ($appliedTaxes as $appliedTax) { + $appliedTaxesData[] = [ + 'label' => $appliedTax['id'], + 'amount' => ['value' => $appliedTax['amount'], 'currency' => $currency] + ]; + } + return $appliedTaxesData; + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CreateEmptyCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CreateEmptyCart.php index 06123abe615e6..f020527d958e4 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CreateEmptyCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CreateEmptyCart.php @@ -7,13 +7,15 @@ namespace Magento\QuoteGraphQl\Model\Resolver; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlAlreadyExistsException; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\Quote\Api\CartManagementInterface; -use Magento\Quote\Api\GuestCartManagementInterface; -use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; -use Magento\Quote\Model\QuoteIdMaskFactory; +use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface; +use Magento\QuoteGraphQl\Model\Cart\CreateEmptyCartForCustomer; +use Magento\QuoteGraphQl\Model\Cart\CreateEmptyCartForGuest; /** * @inheritdoc @@ -21,41 +23,33 @@ class CreateEmptyCart implements ResolverInterface { /** - * @var CartManagementInterface + * @var CreateEmptyCartForCustomer */ - private $cartManagement; + private $createEmptyCartForCustomer; /** - * @var GuestCartManagementInterface + * @var CreateEmptyCartForGuest */ - private $guestCartManagement; + private $createEmptyCartForGuest; /** - * @var QuoteIdToMaskedQuoteIdInterface + * @var MaskedQuoteIdToQuoteIdInterface */ - private $quoteIdToMaskedId; + private $maskedQuoteIdToQuoteId; /** - * @var QuoteIdMaskFactory - */ - private $quoteIdMaskFactory; - - /** - * @param CartManagementInterface $cartManagement - * @param GuestCartManagementInterface $guestCartManagement - * @param QuoteIdToMaskedQuoteIdInterface $quoteIdToMaskedId - * @param QuoteIdMaskFactory $quoteIdMaskFactory + * @param CreateEmptyCartForCustomer $createEmptyCartForCustomer + * @param CreateEmptyCartForGuest $createEmptyCartForGuest + * @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId */ public function __construct( - CartManagementInterface $cartManagement, - GuestCartManagementInterface $guestCartManagement, - QuoteIdToMaskedQuoteIdInterface $quoteIdToMaskedId, - QuoteIdMaskFactory $quoteIdMaskFactory + CreateEmptyCartForCustomer $createEmptyCartForCustomer, + CreateEmptyCartForGuest $createEmptyCartForGuest, + MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId ) { - $this->cartManagement = $cartManagement; - $this->guestCartManagement = $guestCartManagement; - $this->quoteIdToMaskedId = $quoteIdToMaskedId; - $this->quoteIdMaskFactory = $quoteIdMaskFactory; + $this->createEmptyCartForCustomer = $createEmptyCartForCustomer; + $this->createEmptyCartForGuest = $createEmptyCartForGuest; + $this->maskedQuoteIdToQuoteId = $maskedQuoteIdToQuoteId; } /** @@ -65,19 +59,49 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value { $customerId = $context->getUserId(); - if (0 !== $customerId && null !== $customerId) { - $quoteId = $this->cartManagement->createEmptyCartForCustomer($customerId); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quoteId); - - if (empty($maskedQuoteId)) { - $quoteIdMask = $this->quoteIdMaskFactory->create(); - $quoteIdMask->setQuoteId($quoteId)->save(); - $maskedQuoteId = $quoteIdMask->getMaskedId(); - } - } else { - $maskedQuoteId = $this->guestCartManagement->createEmptyCart(); + $predefinedMaskedQuoteId = null; + if (isset($args['input']['cart_id'])) { + $predefinedMaskedQuoteId = $args['input']['cart_id']; + $this->validateMaskedId($predefinedMaskedQuoteId); } + $maskedQuoteId = (0 === $customerId || null === $customerId) + ? $this->createEmptyCartForGuest->execute($predefinedMaskedQuoteId) + : $this->createEmptyCartForCustomer->execute($customerId, $predefinedMaskedQuoteId); return $maskedQuoteId; } + + /** + * Validate masked id + * + * @param string $maskedId + * @throws GraphQlAlreadyExistsException + * @throws GraphQlInputException + */ + private function validateMaskedId(string $maskedId): void + { + if (mb_strlen($maskedId) != 32) { + throw new GraphQlInputException(__('Cart ID length should to be 32 symbols.')); + } + + if ($this->isQuoteWithSuchMaskedIdAlreadyExists($maskedId)) { + throw new GraphQlAlreadyExistsException(__('Cart with ID "%1" already exists.', $maskedId)); + } + } + + /** + * Check is quote with such maskedId already exists + * + * @param string $maskedId + * @return bool + */ + private function isQuoteWithSuchMaskedIdAlreadyExists(string $maskedId): bool + { + try { + $this->maskedQuoteIdToQuoteId->execute($maskedId); + return true; + } catch (NoSuchEntityException $e) { + return false; + } + } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php index 3bd46a664f2ab..1672474bb3ddd 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php @@ -65,6 +65,13 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId()); + if ($context->getUserId() === 0) { + if (!$cart->getCustomerEmail()) { + throw new GraphQlInputException(__("Guest email for cart is missing. Please enter")); + } + $cart->setCheckoutMethod(CartManagementInterface::METHOD_GUEST); + } + try { $orderId = $this->cartManagement->placeOrder($cart->getId()); $order = $this->orderRepository->get($orderId); diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SelectedPaymentMethod.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SelectedPaymentMethod.php index 7a99b04638ac3..8cda06eba3c91 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/SelectedPaymentMethod.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SelectedPaymentMethod.php @@ -36,6 +36,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value return [ 'code' => $payment->getMethod(), + 'title' => $payment->getMethodInstance()->getTitle(), 'purchase_order_number' => $payment->getPoNumber(), ]; } diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetGuestEmailOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetGuestEmailOnCart.php new file mode 100644 index 0000000000000..d621057348b54 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetGuestEmailOnCart.php @@ -0,0 +1,95 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\QuoteGraphQl\Model\Resolver; + +use Magento\Framework\Exception\CouldNotSaveException; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Framework\Validator\EmailAddress as EmailAddressValidator; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\QuoteGraphQl\Model\Cart\GetCartForUser; + +/** + * @inheritdoc + */ +class SetGuestEmailOnCart implements ResolverInterface +{ + /** + * @var CartRepositoryInterface + */ + private $cartRepository; + + /** + * @var GetCartForUser + */ + private $getCartForUser; + + /** + * @var EmailAddressValidator + */ + private $emailValidator; + + /** + * @param GetCartForUser $getCartForUser + * @param CartRepositoryInterface $cartRepository + * @param EmailAddressValidator $emailValidator + */ + public function __construct( + GetCartForUser $getCartForUser, + CartRepositoryInterface $cartRepository, + EmailAddressValidator $emailValidator + ) { + $this->getCartForUser = $getCartForUser; + $this->cartRepository = $cartRepository; + $this->emailValidator = $emailValidator; + } + + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + if (!isset($args['input']['cart_id']) || empty($args['input']['cart_id'])) { + throw new GraphQlInputException(__('Required parameter "cart_id" is missing')); + } + $maskedCartId = $args['input']['cart_id']; + + if (!isset($args['input']['email']) || empty($args['input']['email'])) { + throw new GraphQlInputException(__('Required parameter "email" is missing')); + } + + if (false === $this->emailValidator->isValid($args['input']['email'])) { + throw new GraphQlInputException(__('Invalid email format')); + } + $email = $args['input']['email']; + + $currentUserId = $context->getUserId(); + + if ($currentUserId !== 0) { + throw new GraphQlInputException(__('The request is not allowed for logged in customers')); + } + + $cart = $this->getCartForUser->execute($maskedCartId, $currentUserId); + $cart->setCustomerEmail($email); + + try { + $this->cartRepository->save($cart); + } catch (CouldNotSaveException $e) { + throw new LocalizedException(__($e->getMessage()), $e); + } + + return [ + 'cart' => [ + 'model' => $cart, + ], + ]; + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php index d1dcb4a48a76b..7b81964f111c6 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php @@ -69,17 +69,15 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value } $paymentMethodCode = $args['input']['payment_method']['code']; - $poNumber = isset($args['input']['payment_method']['purchase_order_number']) - && empty($args['input']['payment_method']['purchase_order_number']) - ? $args['input']['payment_method']['purchase_order_number'] - : null; + $poNumber = $args['input']['payment_method']['purchase_order_number'] ?? null; + $additionalData = $args['input']['payment_method']['additional_data'] ?? []; $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId()); $payment = $this->paymentFactory->create([ 'data' => [ PaymentInterface::KEY_METHOD => $paymentMethodCode, PaymentInterface::KEY_PO_NUMBER => $poNumber, - PaymentInterface::KEY_ADDITIONAL_DATA => [], + PaymentInterface::KEY_ADDITIONAL_DATA => $additionalData, ] ]); diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php index c58affa064c89..cd8f20c3f164f 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php @@ -11,6 +11,8 @@ use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Quote\Model\Quote\Address; +use Magento\Quote\Model\Quote\Address\Rate; /** * @inheritdoc @@ -25,19 +27,39 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value if (!isset($value['model'])) { throw new LocalizedException(__('"model" value should be specified')); } - + /** @var Address $address */ $address = $value['model']; + $rates = $address->getAllShippingRates(); - if ($address->getShippingMethod()) { + if (count($rates) > 0) { list($carrierCode, $methodCode) = explode('_', $address->getShippingMethod(), 2); - $shippingAmount = $address->getShippingAmount(); - } + /** @var Rate $rate */ + $rate = current($rates); - return [ - 'carrier_code' => $carrierCode ?? null, - 'method_code' => $methodCode ?? null, - 'label' => $address->getShippingDescription(), - 'amount' => $shippingAmount ?? null, - ]; + $data = [ + 'carrier_code' => $carrierCode, + 'method_code' => $methodCode, + 'carrier_title' => $rate->getCarrierTitle(), + 'method_title' => $rate->getMethodTitle(), + 'amount' => [ + 'value' => $address->getShippingAmount(), + 'currency' => $address->getQuote()->getQuoteCurrencyCode(), + ], + 'base_amount' => [ + 'value' => $address->getBaseShippingAmount(), + 'currency' => $address->getQuote()->getBaseCurrencyCode(), + ], + ]; + } else { + $data = [ + 'carrier_code' => null, + 'method_code' => null, + 'carrier_title' => null, + 'method_title' => null, + 'amount' => null, + 'base_amount' => null, + ]; + } + return $data; } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php index 78a07506556c0..b76c9159d8590 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php @@ -16,6 +16,7 @@ use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Quote\Api\CartItemRepositoryInterface; use Magento\Quote\Model\Quote; +use Magento\Quote\Model\Quote\Item; use Magento\QuoteGraphQl\Model\Cart\GetCartForUser; /** @@ -98,7 +99,7 @@ private function processCartItems(Quote $cart, array $items): void if (!isset($item['quantity'])) { throw new GraphQlInputException(__('Required parameter "quantity" for "cart_items" is missing.')); } - $qty = (float)$item['quantity']; + $quantity = (float)$item['quantity']; $cartItem = $cart->getItemById($itemId); if ($cartItem === false) { @@ -107,12 +108,39 @@ private function processCartItems(Quote $cart, array $items): void ); } - if ($qty <= 0.0) { + if ($quantity <= 0.0) { $this->cartItemRepository->deleteById((int)$cart->getId(), $itemId); } else { - $cartItem->setQty($qty); + $cartItem->setQty($quantity); + $this->validateCartItem($cartItem); $this->cartItemRepository->save($cartItem); } } } + + /** + * Validate cart item + * + * @param Item $cartItem + * @return void + * @throws GraphQlInputException + */ + private function validateCartItem(Item $cartItem): void + { + if ($cartItem->getHasError()) { + $errors = []; + foreach ($cartItem->getMessage(false) as $message) { + $errors[] = $message; + } + + if (!empty($errors)) { + throw new GraphQlInputException( + __( + 'Could not update the product with SKU %sku: %message', + ['sku' => $cartItem->getSku(), 'message' => __(implode("\n", $errors))] + ) + ); + } + } + } } diff --git a/app/code/Magento/QuoteGraphQl/composer.json b/app/code/Magento/QuoteGraphQl/composer.json index 22ca9cfdfae9a..a3c07f7df2cee 100644 --- a/app/code/Magento/QuoteGraphQl/composer.json +++ b/app/code/Magento/QuoteGraphQl/composer.json @@ -14,7 +14,8 @@ "magento/module-sales": "*" }, "suggest": { - "magento/module-graph-ql": "*" + "magento/module-graph-ql": "*", + "magento/module-graph-ql-cache": "*" }, "license": [ "OSL-3.0", diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index 9ec3492f64531..e2d29e93db574 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -2,11 +2,11 @@ # See COPYING.txt for license details. type Query { - cart(cart_id: String!): Cart @resolver (class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\Cart") @doc(description:"Returns information about shopping cart") + cart(cart_id: String!): Cart @resolver (class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\Cart") @doc(description:"Returns information about shopping cart") @cache(cacheable: false) } type Mutation { - createEmptyCart: String @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CreateEmptyCart") @doc(description:"Creates an empty shopping cart for a guest or logged in user") + createEmptyCart(input: createEmptyCartInput): String @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CreateEmptyCart") @doc(description:"Creates an empty shopping cart for a guest or logged in user") addSimpleProductsToCart(input: AddSimpleProductsToCartInput): AddSimpleProductsToCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AddSimpleProductsToCart") addVirtualProductsToCart(input: AddVirtualProductsToCartInput): AddVirtualProductsToCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AddSimpleProductsToCart") applyCouponToCart(input: ApplyCouponToCartInput): ApplyCouponToCartOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ApplyCouponToCart") @@ -17,9 +17,14 @@ type Mutation { setBillingAddressOnCart(input: SetBillingAddressOnCartInput): SetBillingAddressOnCartOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SetBillingAddressOnCart") setShippingMethodsOnCart(input: SetShippingMethodsOnCartInput): SetShippingMethodsOnCartOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SetShippingMethodsOnCart") setPaymentMethodOnCart(input: SetPaymentMethodOnCartInput): SetPaymentMethodOnCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\SetPaymentMethodOnCart") + setGuestEmailOnCart(input: SetGuestEmailOnCartInput): SetGuestEmailOnCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\SetGuestEmailOnCart") placeOrder(input: PlaceOrderInput): PlaceOrderOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\PlaceOrder") } +input createEmptyCartInput { + cart_id: String +} + input AddSimpleProductsToCartInput { cart_id: String! cartItems: [SimpleProductCartItemInput!]! @@ -42,7 +47,7 @@ input VirtualProductCartItemInput { input CartItemInput { sku: String! - qty: Float! + quantity: Float! } input CustomizableOptionInput { @@ -57,10 +62,10 @@ input ApplyCouponToCartInput { input UpdateCartItemsInput { cart_id: String! - cart_items: [CartItemQuantityInput!]! + cart_items: [CartItemUpdateInput!]! } -input CartItemQuantityInput { +input CartItemUpdateInput { cart_item_id: Int! quantity: Float! } @@ -110,7 +115,6 @@ input SetShippingMethodsOnCartInput { } input ShippingMethodInput { - cart_address_id: Int! carrier_code: String! method_code: String! } @@ -126,9 +130,31 @@ input SetPaymentMethodOnCartInput { input PaymentMethodInput { code: String! @doc(description:"Payment method code") + additional_data: PaymentMethodAdditionalDataInput @doc(description:"Additional payment data") purchase_order_number: String @doc(description:"Purchase order number") } +input PaymentMethodAdditionalDataInput { +} + +input SetGuestEmailOnCartInput { + cart_id: String! + email: String! +} + +type CartPrices { + grand_total: Money + subtotal_including_tax: Money + subtotal_excluding_tax: Money + subtotal_with_discount_excluding_tax: Money + applied_taxes: [CartTaxItem] +} + +type CartTaxItem { + amount: Money! + label: String! +} + type SetPaymentMethodOnCartOutput { cart: Cart! } @@ -156,14 +182,15 @@ type PlaceOrderOutput { type Cart { items: [CartItemInterface] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartItems") applied_coupon: AppliedCoupon @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\AppliedCoupon") - shipping_addresses: [CartAddress]! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAddresses") - billing_address: CartAddress! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\BillingAddress") + email: String @resolver (class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartEmail") + shipping_addresses: [ShippingCartAddress]! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAddresses") + billing_address: BillingCartAddress! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\BillingAddress") available_payment_methods: [AvailablePaymentMethod] @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AvailablePaymentMethods") @doc(description: "Available payment methods") selected_payment_method: SelectedPaymentMethod @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SelectedPaymentMethod") + prices: CartPrices @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartPrices") } -type CartAddress { - address_id: Int +interface CartAddressInterface @typeResolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartAddressTypeResolver") { firstname: String lastname: String company: String @@ -173,14 +200,19 @@ type CartAddress { postcode: String country: CartAddressCountry telephone: String - address_type: AdressTypeEnum + customer_notes: String +} + +type ShippingCartAddress implements CartAddressInterface { available_shipping_methods: [AvailableShippingMethod] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAddress\\AvailableShippingMethods") selected_shipping_method: SelectedShippingMethod @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAddress\\SelectedShippingMethod") items_weight: Float - customer_notes: String cart_items: [CartItemQuantity] } +type BillingCartAddress implements CartAddressInterface { +} + type CartItemQuantity { cart_item_id: Int! quantity: Float! @@ -199,35 +231,38 @@ type CartAddressCountry { type SelectedShippingMethod { carrier_code: String method_code: String - label: String - amount: Float + carrier_title: String + method_title: String + amount: Money + base_amount: Money } type AvailableShippingMethod { carrier_code: String! carrier_title: String! - method_code: String! - method_title: String! + method_code: String @doc(description: "Could be null if method is not available") + method_title: String @doc(description: "Could be null if method is not available") error_message: String amount: Float! - base_amount: Float! + base_amount: Float @doc(description: "Could be null if method is not available") price_excl_tax: Float! price_incl_tax: Float! + available: Boolean! } type AvailablePaymentMethod { - code: String @doc(description: "The payment method code") - title: String @doc(description: "The payment method title.") + code: String! @doc(description: "The payment method code") + title: String! @doc(description: "The payment method title.") } type SelectedPaymentMethod { - code: String @doc(description: "The payment method code") + code: String! @doc(description: "The payment method code") + title: String! @doc(description: "The payment method title.") + additional_data: SelectedPaymentMethodAdditionalData @doc(description: "Additional payment data") purchase_order_number: String @doc(description: "The purchase order number.") } -enum AdressTypeEnum { - SHIPPING - BILLING +type SelectedPaymentMethodAdditionalData { } type AppliedCoupon { @@ -258,6 +293,10 @@ type RemoveItemFromCartOutput { cart: Cart! } +type SetGuestEmailOnCartOutput { + cart: Cart! +} + type SimpleCartItem implements CartItemInterface @doc(description: "Simple Cart Item") { customizable_options: [SelectedCustomizableOption] @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\CustomizableOptions") } @@ -268,7 +307,7 @@ type VirtualCartItem implements CartItemInterface @doc(description: "Virtual Car interface CartItemInterface @typeResolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\CartItemTypeResolver") { id: String! - qty: Float! + quantity: Float! product: ProductInterface! } diff --git a/app/code/Magento/ReleaseNotification/Test/Unit/Model/Condition/CanViewNotificationTest.php b/app/code/Magento/ReleaseNotification/Test/Unit/Model/Condition/CanViewNotificationTest.php index 55f448730a506..b86f8dff2b3b1 100644 --- a/app/code/Magento/ReleaseNotification/Test/Unit/Model/Condition/CanViewNotificationTest.php +++ b/app/code/Magento/ReleaseNotification/Test/Unit/Model/Condition/CanViewNotificationTest.php @@ -12,6 +12,7 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Backend\Model\Auth\Session; use Magento\Framework\App\CacheInterface; +use Magento\User\Model\User; class CanViewNotificationTest extends \PHPUnit\Framework\TestCase { @@ -33,6 +34,11 @@ class CanViewNotificationTest extends \PHPUnit\Framework\TestCase /** @var $cacheStorageMock \PHPUnit_Framework_MockObject_MockObject|CacheInterface */ private $cacheStorageMock; + /** + * @var User|\PHPUnit_Framework_MockObject_MockObject + */ + private $userMock; + public function setUp() { $this->cacheStorageMock = $this->getMockBuilder(CacheInterface::class) @@ -41,7 +47,6 @@ public function setUp() ->getMock(); $this->sessionMock = $this->getMockBuilder(Session::class) ->disableOriginalConstructor() - ->setMethods(['getUser', 'getId']) ->getMock(); $this->viewerLoggerMock = $this->getMockBuilder(Logger::class) ->disableOriginalConstructor() @@ -49,6 +54,7 @@ public function setUp() $this->productMetadataMock = $this->getMockBuilder(ProductMetadataInterface::class) ->disableOriginalConstructor() ->getMock(); + $this->userMock = $this->createMock(User::class); $objectManager = new ObjectManager($this); $this->canViewNotification = $objectManager->getObject( CanViewNotification::class, @@ -65,8 +71,8 @@ public function testIsVisibleLoadDataFromCache() { $this->sessionMock->expects($this->once()) ->method('getUser') - ->willReturn($this->sessionMock); - $this->sessionMock->expects($this->once()) + ->willReturn($this->userMock); + $this->userMock->expects($this->once()) ->method('getId') ->willReturn(1); $this->cacheStorageMock->expects($this->once()) @@ -90,8 +96,8 @@ public function testIsVisible($expected, $version, $lastViewVersion) ->willReturn(false); $this->sessionMock->expects($this->once()) ->method('getUser') - ->willReturn($this->sessionMock); - $this->sessionMock->expects($this->once()) + ->willReturn($this->userMock); + $this->userMock->expects($this->once()) ->method('getId') ->willReturn(1); $this->productMetadataMock->expects($this->once()) diff --git a/app/code/Magento/ReleaseNotification/etc/di.xml b/app/code/Magento/ReleaseNotification/etc/di.xml index 1404a6adb0a10..a4c434ff7f623 100644 --- a/app/code/Magento/ReleaseNotification/etc/di.xml +++ b/app/code/Magento/ReleaseNotification/etc/di.xml @@ -6,7 +6,6 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> - <preference for="Magento\Framework\HTTP\ClientInterface" type="Magento\Framework\HTTP\Client\Curl" /> <preference for="Magento\ReleaseNotification\Model\ContentProviderInterface" type="Magento\ReleaseNotification\Model\ContentProvider\Http\HttpContentProvider" /> <type name="Magento\Config\Model\Config\TypePool"> <arguments> diff --git a/app/code/Magento/Reports/Model/ResourceModel/Customer/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Customer/Collection.php index aa01e33caf3d2..b6e55af96f4c1 100644 --- a/app/code/Magento/Reports/Model/ResourceModel/Customer/Collection.php +++ b/app/code/Magento/Reports/Model/ResourceModel/Customer/Collection.php @@ -6,8 +6,6 @@ namespace Magento\Reports\Model\ResourceModel\Customer; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; - /** * Customers Report collection. * @@ -75,6 +73,7 @@ class Collection extends \Magento\Customer\Model\ResourceModel\Customer\Collecti protected $orderResource; /** + * Collection constructor. * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory * @param \Psr\Log\LoggerInterface $logger * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy @@ -89,10 +88,9 @@ class Collection extends \Magento\Customer\Model\ResourceModel\Customer\Collecti * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository * @param \Magento\Quote\Model\ResourceModel\Quote\Item\CollectionFactory $quoteItemFactory * @param \Magento\Sales\Model\ResourceModel\Order\Collection $orderResource - * @param mixed $connection + * @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection * @param string $modelName * - * @param ResourceModelPoolInterface|null $resourceModelPool * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -111,8 +109,7 @@ public function __construct( \Magento\Quote\Model\ResourceModel\Quote\Item\CollectionFactory $quoteItemFactory, \Magento\Sales\Model\ResourceModel\Order\Collection $orderResource, \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, - $modelName = self::CUSTOMER_MODEL_NAME, - ResourceModelPoolInterface $resourceModelPool = null + $modelName = self::CUSTOMER_MODEL_NAME ) { parent::__construct( $entityFactory, @@ -127,8 +124,7 @@ public function __construct( $entitySnapshot, $fieldsetConfig, $connection, - $modelName, - $resourceModelPool + $modelName ); $this->orderResource = $orderResource; $this->quoteRepository = $quoteRepository; diff --git a/app/code/Magento/Reports/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Product/Collection.php index 451007960a1ce..966ee14c2cb64 100644 --- a/app/code/Magento/Reports/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Reports/Model/ResourceModel/Product/Collection.php @@ -9,17 +9,11 @@ */ namespace Magento\Reports\Model\ResourceModel\Product; -use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; -use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; -use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; -use Magento\Framework\EntityManager\MetadataPool; -use Magento\Framework\Indexer\DimensionFactory; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; - /** * Products Report collection. * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @api * @since 100.0.2 */ @@ -95,14 +89,8 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection * @param \Magento\Reports\Model\Event\TypeFactory $eventTypeFactory * @param \Magento\Catalog\Model\Product\Type $productType * @param \Magento\Quote\Model\ResourceModel\Quote\Collection $quoteResource - * @param mixed $connection - * @param ProductLimitationFactory|null $productLimitationFactory - * @param MetadataPool|null $metadataPool - * @param TableMaintainer|null $tableMaintainer - * @param PriceTableResolver|null $priceTableResolver - * @param DimensionFactory|null $dimensionFactory - * @param ResourceModelPoolInterface $resourceModelPool - * @throws \Magento\Framework\Exception\LocalizedException + * @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection + * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -129,13 +117,7 @@ public function __construct( \Magento\Reports\Model\Event\TypeFactory $eventTypeFactory, \Magento\Catalog\Model\Product\Type $productType, \Magento\Quote\Model\ResourceModel\Quote\Collection $quoteResource, - \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, - ProductLimitationFactory $productLimitationFactory = null, - MetadataPool $metadataPool = null, - TableMaintainer $tableMaintainer = null, - PriceTableResolver $priceTableResolver = null, - DimensionFactory $dimensionFactory = null, - ResourceModelPoolInterface $resourceModelPool = null + \Magento\Framework\DB\Adapter\AdapterInterface $connection = null ) { $this->setProductEntityId($product->getEntityIdField()); $this->setProductEntityTableName($product->getEntityTable()); @@ -160,13 +142,7 @@ public function __construct( $customerSession, $dateTime, $groupManagement, - $connection, - $productLimitationFactory, - $metadataPool, - $tableMaintainer, - $priceTableResolver, - $dimensionFactory, - $resourceModelPool + $connection ); $this->_eventTypeFactory = $eventTypeFactory; $this->_productType = $productType; diff --git a/app/code/Magento/Reports/Model/ResourceModel/Product/Downloads/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Product/Downloads/Collection.php index 1985db0b90e2a..2009cd3ff9d92 100644 --- a/app/code/Magento/Reports/Model/ResourceModel/Product/Downloads/Collection.php +++ b/app/code/Magento/Reports/Model/ResourceModel/Product/Downloads/Collection.php @@ -4,16 +4,16 @@ * See COPYING.txt for license details. */ +namespace Magento\Reports\Model\ResourceModel\Product\Downloads; + /** * Product Downloads Report collection * * @author Magento Core Team <core@magentocommerce.com> - */ -namespace Magento\Reports\Model\ResourceModel\Product\Downloads; - -/** + * * @api * @since 100.0.2 + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection { @@ -97,4 +97,14 @@ public function addFieldToFilter($field, $condition = null) } return $this; } + + /** + * @inheritDoc + */ + public function getSelectCountSql() + { + $countSelect = parent::getSelectCountSql(); + $countSelect->reset(\Zend\Db\Sql\Select::GROUP); + return $countSelect; + } } diff --git a/app/code/Magento/Reports/Model/ResourceModel/Product/Index/Collection/AbstractCollection.php b/app/code/Magento/Reports/Model/ResourceModel/Product/Index/Collection/AbstractCollection.php index bec8faaee0ca7..5b4cf39d65def 100644 --- a/app/code/Magento/Reports/Model/ResourceModel/Product/Index/Collection/AbstractCollection.php +++ b/app/code/Magento/Reports/Model/ResourceModel/Product/Index/Collection/AbstractCollection.php @@ -9,17 +9,12 @@ */ namespace Magento\Reports\Model\ResourceModel\Product\Index\Collection; -use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; -use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; -use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; -use Magento\Framework\EntityManager\MetadataPool; -use Magento\Framework\Indexer\DimensionFactory; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; - /** * Reports Product Index Abstract Product Resource Collection. * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + * phpcs:disable Magento2.Classes.AbstractApi * @api * @since 100.0.2 */ @@ -59,13 +54,8 @@ abstract class AbstractCollection extends \Magento\Catalog\Model\ResourceModel\P * @param \Magento\Framework\Stdlib\DateTime $dateTime * @param \Magento\Customer\Api\GroupManagementInterface $groupManagement * @param \Magento\Customer\Model\Visitor $customerVisitor - * @param mixed $connection - * @param ProductLimitationFactory|null $productLimitationFactory - * @param MetadataPool|null $metadataPool - * @param TableMaintainer|null $tableMaintainer - * @param PriceTableResolver|null $priceTableResolver - * @param DimensionFactory|null $dimensionFactory - * @param ResourceModelPoolInterface|null $resourceModelPool + * @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection + * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -89,13 +79,7 @@ public function __construct( \Magento\Framework\Stdlib\DateTime $dateTime, \Magento\Customer\Api\GroupManagementInterface $groupManagement, \Magento\Customer\Model\Visitor $customerVisitor, - \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, - ProductLimitationFactory $productLimitationFactory = null, - MetadataPool $metadataPool = null, - TableMaintainer $tableMaintainer = null, - PriceTableResolver $priceTableResolver = null, - DimensionFactory $dimensionFactory = null, - ResourceModelPoolInterface $resourceModelPool = null + \Magento\Framework\DB\Adapter\AdapterInterface $connection = null ) { parent::__construct( $entityFactory, @@ -117,13 +101,7 @@ public function __construct( $customerSession, $dateTime, $groupManagement, - $connection, - $productLimitationFactory, - $metadataPool, - $tableMaintainer, - $priceTableResolver, - $dimensionFactory, - $resourceModelPool + $connection ); $this->_customerVisitor = $customerVisitor; } diff --git a/app/code/Magento/Reports/Model/ResourceModel/Product/Lowstock/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Product/Lowstock/Collection.php index 8bf50f4c1b8e7..39d673911111f 100644 --- a/app/code/Magento/Reports/Model/ResourceModel/Product/Lowstock/Collection.php +++ b/app/code/Magento/Reports/Model/ResourceModel/Product/Lowstock/Collection.php @@ -5,22 +5,19 @@ */ /** + * Product Low Stock Report Collection + * * @author Magento Core Team <core@magentocommerce.com> */ namespace Magento\Reports\Model\ResourceModel\Product\Lowstock; -use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; -use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; -use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; -use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Indexer\DimensionFactory; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; /** * Product Low Stock Report Collection. * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @api * @since 100.0.2 */ @@ -56,7 +53,6 @@ class Collection extends \Magento\Reports\Model\ResourceModel\Product\Collection protected $_itemResource; /** - * Collection constructor. * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory * @param \Psr\Log\LoggerInterface $logger * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy @@ -84,13 +80,7 @@ class Collection extends \Magento\Reports\Model\ResourceModel\Product\Collection * @param \Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration * @param \Magento\CatalogInventory\Model\ResourceModel\Stock\Item $itemResource * @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection - * @param ProductLimitationFactory|null $productLimitationFactory - * @param MetadataPool|null $metadataPool - * @param TableMaintainer|null $tableMaintainer - * @param PriceTableResolver|null $priceTableResolver - * @param DimensionFactory|null $dimensionFactory - * @param ResourceModelPoolInterface|null $resourceModelPool - * @throws LocalizedException + * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -120,13 +110,7 @@ public function __construct( \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry, \Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration, \Magento\CatalogInventory\Model\ResourceModel\Stock\Item $itemResource, - \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, - ProductLimitationFactory $productLimitationFactory = null, - MetadataPool $metadataPool = null, - TableMaintainer $tableMaintainer = null, - PriceTableResolver $priceTableResolver = null, - DimensionFactory $dimensionFactory = null, - ResourceModelPoolInterface $resourceModelPool = null + \Magento\Framework\DB\Adapter\AdapterInterface $connection = null ) { parent::__construct( $entityFactory, @@ -152,13 +136,7 @@ public function __construct( $eventTypeFactory, $productType, $quoteResource, - $connection, - $productLimitationFactory, - $metadataPool, - $tableMaintainer, - $priceTableResolver, - $dimensionFactory, - $resourceModelPool + $connection ); $this->stockRegistry = $stockRegistry; $this->stockConfiguration = $stockConfiguration; diff --git a/app/code/Magento/Reports/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Reports/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..e77e3ee8abd87 --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,86 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuReportsMarketingAbandonedCarts"> + <data key="pageTitle">Abandoned Carts</data> + <data key="title">Abandoned Carts</data> + <data key="dataUiId">magento-reports-report-shopcart-abandoned</data> + </entity> + <entity name="AdminMenuReportsProductsBestsellers"> + <data key="pageTitle">Bestsellers Report</data> + <data key="title">Bestsellers</data> + <data key="dataUiId">magento-reports-report-products-bestsellers</data> + </entity> + <entity name="AdminMenuReportsSalesCoupons"> + <data key="pageTitle">Coupons Report</data> + <data key="title">Coupons</data> + <data key="dataUiId">magento-reports-report-salesroot-coupons</data> + </entity> + <entity name="AdminMenuReportsProductsDownloads"> + <data key="pageTitle">Downloads Report</data> + <data key="title">Downloads</data> + <data key="dataUiId">magento-downloadable-report-products-downloads</data> + </entity> + <entity name="AdminMenuReportsSalesInvoiced"> + <data key="pageTitle">Invoice Report</data> + <data key="title">Invoiced</data> + <data key="dataUiId">magento-reports-report-salesroot-invoiced</data> + </entity> + <entity name="AdminMenuReportsProductsLowStock"> + <data key="pageTitle">Low Stock Report</data> + <data key="title">Low Stock</data> + <data key="dataUiId">magento-reports-report-products-lowstock</data> + </entity> + <entity name="AdminMenuReportsCustomersNew"> + <data key="pageTitle">New Accounts Report</data> + <data key="title">New</data> + <data key="dataUiId">magento-reports-report-customers-accounts</data> + </entity> + <entity name="AdminMenuReportsCustomersOrderCount"> + <data key="pageTitle">Order Count Report</data> + <data key="title">Order Count</data> + <data key="dataUiId">magento-reports-report-customers-orders</data> + </entity> + <entity name="AdminMenuReportsProductsOrdered"> + <data key="pageTitle">Ordered Products Report</data> + <data key="title">Ordered</data> + <data key="dataUiId">magento-reports-report-products-sold</data> + </entity> + <entity name="AdminMenuReportsSalesOrders"> + <data key="pageTitle">Orders Report</data> + <data key="title">Orders</data> + <data key="dataUiId">magento-reports-report-salesroot-sales</data> + </entity> + <entity name="AdminMenuReportsCustomersOrderTotal"> + <data key="pageTitle">Order Total Report</data> + <data key="title">Order Total</data> + <data key="dataUiId">magento-reports-report-customers-totals</data> + </entity> + <entity name="AdminMenuReportsMarketingProductsInCarts"> + <data key="pageTitle">Products in Carts</data> + <data key="title">Products in Cart</data> + <data key="dataUiId">magento-reports-report-shopcart-product</data> + </entity> + <entity name="AdminMenuReportsStatisticsRefreshStatistics"> + <data key="pageTitle">Refresh Statistics</data> + <data key="title">Refresh Statistics</data> + <data key="dataUiId">magento-reports-report-statistics-refresh</data> + </entity> + <entity name="AdminMenuReportsSalesTax"> + <data key="pageTitle">Tax Report</data> + <data key="title">Tax</data> + <data key="dataUiId">magento-reports-report-salesroot-tax</data> + </entity> + <entity name="AdminMenuReportsProductsViews"> + <data key="pageTitle">Product Views Report</data> + <data key="title">Views</data> + <data key="dataUiId">magento-reports-report-products-viewed</data> + </entity> +</entities> diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsAbandonedCartsNavigateMenuTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsAbandonedCartsNavigateMenuTest.xml new file mode 100644 index 0000000000000..342955e0684b3 --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsAbandonedCartsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsAbandonedCartsNavigateMenuTest"> + <annotations> + <features value="Reports"/> + <stories value="Menu Navigation"/> + <title value="Admin reports abandoned carts navigate menu test"/> + <description value="Admin should be able to navigate to Reports > Abandoned Carts"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14159"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToAbandonedCartsPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsMarketingAbandonedCarts.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsMarketingAbandonedCarts.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsBestsellersNavigateMenuTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsBestsellersNavigateMenuTest.xml new file mode 100644 index 0000000000000..259f2cde2786a --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsBestsellersNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsBestsellersNavigateMenuTest"> + <annotations> + <features value="Reports"/> + <stories value="Menu Navigation"/> + <title value="Admin reports bestsellers navigate menu test"/> + <description value="Admin should be able to navigate to Reports > Bestsellers"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14168"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsBestsellersPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsProductsBestsellers.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsProductsBestsellers.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsCouponsNavigateMenuTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsCouponsNavigateMenuTest.xml new file mode 100644 index 0000000000000..321f3078bc63f --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsCouponsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsCouponsNavigateMenuTest"> + <annotations> + <features value="Reports"/> + <stories value="Menu Navigation"/> + <title value="Admin reports coupons navigate menu test"/> + <description value="Admin should be able to navigate to Reports > Coupons"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14163"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsCouponsPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsSalesCoupons.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsSalesCoupons.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsDownloadsNavigateMenuTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsDownloadsNavigateMenuTest.xml new file mode 100644 index 0000000000000..584c1af6683aa --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsDownloadsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsDownloadsNavigateMenuTest"> + <annotations> + <features value="Reports"/> + <stories value="Menu Navigation"/> + <title value="Admin reports downloads navigate menu test"/> + <description value="Admin should be able to navigate to Reports > Downloads"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14171"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsDownloadsPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsProductsDownloads.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsProductsDownloads.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsInvoicedNavigateMenuTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsInvoicedNavigateMenuTest.xml new file mode 100644 index 0000000000000..34aec0620cad9 --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsInvoicedNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsInvoicedNavigateMenuTest"> + <annotations> + <features value="Reports"/> + <stories value="Menu Navigation"/> + <title value="Admin reports invoiced navigate menu test"/> + <description value="Admin should be able to navigate to Reports > Invoiced"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14162"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsInvoicedPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsSalesInvoiced.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsSalesInvoiced.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsLowStockNavigateMenuTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsLowStockNavigateMenuTest.xml new file mode 100644 index 0000000000000..5d91d65a3a457 --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsLowStockNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsLowStockNavigateMenuTest"> + <annotations> + <features value="Reports"/> + <stories value="Menu Navigation"/> + <title value="Admin reports low stock navigate menu test"/> + <description value="Admin should be able to navigate to Reports > Low Stock"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14169"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsLowStockPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsProductsLowStock.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsProductsLowStock.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsNewNavigateMenuTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsNewNavigateMenuTest.xml new file mode 100644 index 0000000000000..aeb35ba65a380 --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsNewNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsNewNavigateMenuTest"> + <annotations> + <features value="Reports"/> + <stories value="Menu Navigation"/> + <title value="Admin reports new navigate menu test"/> + <description value="Admin should be able to navigate to Reports > New"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14166"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsNewPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsCustomersNew.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsCustomersNew.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderCountNavigateMenuTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderCountNavigateMenuTest.xml new file mode 100644 index 0000000000000..1bfbc654746e6 --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderCountNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsOrderCountNavigateMenuTest"> + <annotations> + <features value="Reports"/> + <stories value="Menu Navigation"/> + <title value="Admin reports order count navigate menu test"/> + <description value="Admin should be able to navigate to Reports > Order Count"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14165"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsOrderCountPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsCustomersOrderCount.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsCustomersOrderCount.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderTotalNavigateMenuTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderTotalNavigateMenuTest.xml new file mode 100644 index 0000000000000..88c94b53f5233 --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderTotalNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsOrderTotalNavigateMenuTest"> + <annotations> + <features value="Reports"/> + <stories value="Menu Navigation"/> + <title value="Admin reports order total navigate menu test"/> + <description value="Admin should be able to navigate to Reports > Order Total"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14164"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsOrderTotalPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsCustomersOrderTotal.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsCustomersOrderTotal.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderedNavigateMenuTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderedNavigateMenuTest.xml new file mode 100644 index 0000000000000..e81239539a5b5 --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderedNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsOrderedNavigateMenuTest"> + <annotations> + <features value="Reports"/> + <stories value="Menu Navigation"/> + <title value="Admin reports ordered navigate menu test"/> + <description value="Admin should be able to navigate to Reports > Ordered"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14170"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsOrderedPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsProductsOrdered.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsProductsOrdered.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrdersNavigateMenuTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrdersNavigateMenuTest.xml new file mode 100644 index 0000000000000..13fc8e7353139 --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrdersNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsOrdersNavigateMenuTest"> + <annotations> + <features value="Reports"/> + <stories value="Menu Navigation"/> + <title value="Admin reports orders navigate menu test"/> + <description value="Admin should be able to navigate to Reports > Orders"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14160"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsOrdersPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsSalesOrders.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsSalesOrders.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsProductsInCartNavigateMenuTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsProductsInCartNavigateMenuTest.xml new file mode 100644 index 0000000000000..03877f8e58ecc --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsProductsInCartNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsProductsInCartNavigateMenuTest"> + <annotations> + <features value="Reports"/> + <stories value="Menu Navigation"/> + <title value="Admin reports products in cart navigate menu test"/> + <description value="Admin should be able to navigate to Reports > Products in Cart"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14158"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsProductsInCartPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsMarketingProductsInCarts.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsMarketingProductsInCarts.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsRefreshStatisticsNavigateMenuTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsRefreshStatisticsNavigateMenuTest.xml new file mode 100644 index 0000000000000..d05fc091357df --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsRefreshStatisticsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsRefreshStatisticsNavigateMenuTest"> + <annotations> + <features value="Reports"/> + <stories value="Menu Navigation"/> + <title value="Admin reports refresh statistics navigate menu test"/> + <description value="Admin should be able to navigate to Reports > Refresh Statistics"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14172"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsRefreshStatisticsPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsStatisticsRefreshStatistics.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsStatisticsRefreshStatistics.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsTaxNavigateMenuTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsTaxNavigateMenuTest.xml new file mode 100644 index 0000000000000..11a065c933a3b --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsTaxNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsTaxNavigateMenuTest"> + <annotations> + <features value="Reports"/> + <stories value="Menu Navigation"/> + <title value="Admin reports tax navigate menu test"/> + <description value="Admin should be able to navigate to Reports > Tax"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14161"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsTaxPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsSalesTax.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsSalesTax.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsViewsNavigateMenuTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsViewsNavigateMenuTest.xml new file mode 100644 index 0000000000000..9154b96c71e38 --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsViewsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsViewsNavigateMenuTest"> + <annotations> + <features value="Reports"/> + <stories value="Menu Navigation"/> + <title value="Admin reports views navigate menu test"/> + <description value="Admin should be able to navigate to Reports > Views"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14167"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsViewsPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsProductsViews.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsProductsViews.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Reports/Test/Unit/Model/ResourceModel/Product/CollectionTest.php b/app/code/Magento/Reports/Test/Unit/Model/ResourceModel/Product/CollectionTest.php index cb4d51e0c540d..038d37a990442 100644 --- a/app/code/Magento/Reports/Test/Unit/Model/ResourceModel/Product/CollectionTest.php +++ b/app/code/Magento/Reports/Test/Unit/Model/ResourceModel/Product/CollectionTest.php @@ -12,7 +12,6 @@ use Magento\Catalog\Model\Product\Type as ProductType; use Magento\Catalog\Model\ResourceModel\Helper; use Magento\Catalog\Model\ResourceModel\Product as ResourceProduct; -use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; use Magento\Catalog\Model\ResourceModel\Url; use Magento\Customer\Api\GroupManagementInterface; use Magento\Customer\Model\Session; @@ -26,9 +25,7 @@ use Magento\Framework\Data\Collection\EntityFactory; use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\DB\Select; -use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Event\ManagerInterface; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; use Magento\Framework\Module\Manager; use Magento\Framework\Stdlib\DateTime; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; @@ -37,7 +34,6 @@ use Magento\Quote\Model\ResourceModel\Quote\Collection; use Magento\Reports\Model\Event\TypeFactory; use Magento\Reports\Model\ResourceModel\Product\Collection as ProductCollection; -use Magento\Store\Api\Data\StoreInterface; use Magento\Store\Model\StoreManagerInterface; use Psr\Log\LoggerInterface; @@ -82,6 +78,46 @@ class CollectionTest extends \PHPUnit\Framework\TestCase protected function setUp() { $this->objectManager = new ObjectManager($this); + $context = $this->createPartialMock(Context::class, ['getResource', 'getEavConfig']); + $entityFactoryMock = $this->createMock(EntityFactory::class); + $loggerMock = $this->createMock(LoggerInterface::class); + $fetchStrategyMock = $this->createMock(FetchStrategyInterface::class); + $eventManagerMock = $this->createMock(ManagerInterface::class); + $eavConfigMock = $this->createMock(Config::class); + $this->resourceMock = $this->createPartialMock(ResourceConnection::class, ['getTableName', 'getConnection']); + $eavEntityFactoryMock = $this->createMock(EavEntityFactory::class); + $resourceHelperMock = $this->createMock(Helper::class); + $universalFactoryMock = $this->createMock(UniversalFactory::class); + $storeManagerMock = $this->createPartialMockForAbstractClass( + StoreManagerInterface::class, + ['getStore', 'getId'] + ); + $moduleManagerMock = $this->createMock(Manager::class); + $productFlatStateMock = $this->createMock(State::class); + $scopeConfigMock = $this->createMock(ScopeConfigInterface::class); + $optionFactoryMock = $this->createMock(OptionFactory::class); + $catalogUrlMock = $this->createMock(Url::class); + $localeDateMock = $this->createMock(TimezoneInterface::class); + $customerSessionMock = $this->createMock(Session::class); + $dateTimeMock = $this->createMock(DateTime::class); + $groupManagementMock = $this->createMock(GroupManagementInterface::class); + $eavConfig = $this->createPartialMock(Config::class, ['getEntityType']); + $entityType = $this->createMock(Type::class); + + $eavConfig->expects($this->atLeastOnce())->method('getEntityType')->willReturn($entityType); + $context->expects($this->atLeastOnce())->method('getResource')->willReturn($this->resourceMock); + $context->expects($this->atLeastOnce())->method('getEavConfig')->willReturn($eavConfig); + + $defaultAttributes = $this->createPartialMock(DefaultAttributes::class, ['_getDefaultAttributes']); + $productMock = $this->objectManager->getObject( + ResourceProduct::class, + ['context' => $context, 'defaultAttributes' => $defaultAttributes] + ); + + $this->eventTypeFactoryMock = $this->createMock(TypeFactory::class); + $productTypeMock = $this->createMock(ProductType::class); + $quoteResourceMock = $this->createMock(Collection::class); + $this->connectionMock = $this->createPartialMockForAbstractClass(AdapterInterface::class, ['select']); $this->selectMock = $this->createPartialMock( Select::class, [ @@ -94,65 +130,39 @@ protected function setUp() 'having', ] ); - $this->connectionMock = $this->createMock(AdapterInterface::class); - $this->connectionMock->expects($this->atLeastOnce())->method('select')->willReturn($this->selectMock); - $this->resourceMock = $this->createPartialMock(ResourceConnection::class, ['getTableName', 'getConnection']); + + $storeManagerMock->expects($this->atLeastOnce())->method('getStore')->willReturn($storeManagerMock); + $storeManagerMock->expects($this->atLeastOnce())->method('getId')->willReturn(1); + $universalFactoryMock->expects($this->atLeastOnce())->method('create')->willReturn($productMock); $this->resourceMock->expects($this->atLeastOnce())->method('getTableName')->willReturn('test_table'); $this->resourceMock->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->connectionMock); - $eavConfig = $this->createPartialMock(Config::class, ['getEntityType']); - $eavConfig->expects($this->atLeastOnce())->method('getEntityType')->willReturn($this->createMock(Type::class)); - $context = $this->createPartialMock(Context::class, ['getResource', 'getEavConfig']); - $context->expects($this->atLeastOnce())->method('getResource')->willReturn($this->resourceMock); - $context->expects($this->atLeastOnce())->method('getEavConfig')->willReturn($eavConfig); - $storeMock = $this->createMock(StoreInterface::class); - $storeMock->expects($this->atLeastOnce())->method('getId')->willReturn(1); - $storeManagerMock = $this->createMock(StoreManagerInterface::class); - $storeManagerMock->expects($this->atLeastOnce())->method('getStore')->willReturn($storeMock); - $productMock = $this->objectManager->getObject( - ResourceProduct::class, - [ - 'context' => $context, - 'defaultAttributes' => $this->createPartialMock( - DefaultAttributes::class, - ['_getDefaultAttributes'] - ) - ] - ); - $resourceModelPoolMock = $this->createMock(ResourceModelPoolInterface::class); - $resourceModelPoolMock->expects($this->atLeastOnce())->method('get')->willReturn($productMock); - $this->eventTypeFactoryMock = $this->createMock(TypeFactory::class); + $this->connectionMock->expects($this->atLeastOnce())->method('select')->willReturn($this->selectMock); $this->collection = new ProductCollection( - $this->createMock(EntityFactory::class), - $this->createMock(LoggerInterface::class), - $this->createMock(FetchStrategyInterface::class), - $this->createMock(ManagerInterface::class), - $this->createMock(Config::class), + $entityFactoryMock, + $loggerMock, + $fetchStrategyMock, + $eventManagerMock, + $eavConfigMock, $this->resourceMock, - $this->createMock(EavEntityFactory::class), - $this->createMock(Helper::class), - $this->createMock(UniversalFactory::class), + $eavEntityFactoryMock, + $resourceHelperMock, + $universalFactoryMock, $storeManagerMock, - $this->createMock(Manager::class), - $this->createMock(State::class), - $this->createMock(ScopeConfigInterface::class), - $this->createMock(OptionFactory::class), - $this->createMock(Url::class), - $this->createMock(TimezoneInterface::class), - $this->createMock(Session::class), - $this->createMock(DateTime::class), - $this->createMock(GroupManagementInterface::class), + $moduleManagerMock, + $productFlatStateMock, + $scopeConfigMock, + $optionFactoryMock, + $catalogUrlMock, + $localeDateMock, + $customerSessionMock, + $dateTimeMock, + $groupManagementMock, $productMock, $this->eventTypeFactoryMock, - $this->createMock(ProductType::class), - $this->createMock(Collection::class), - $this->connectionMock, - $this->createMock(ProductLimitationFactory::class), - $this->createMock(MetadataPool::class), - $this->createMock(\Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer::class), - $this->createMock(\Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver::class), - $this->createMock(\Magento\Framework\Indexer\DimensionFactory::class), - $resourceModelPoolMock + $productTypeMock, + $quoteResourceMock, + $this->connectionMock ); } @@ -252,4 +262,25 @@ public function testAddViewsCount() $this->collection->addViewsCount(); } + + /** + * Get mock for abstract class with methods. + * + * @param string $className + * @param array $methods + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function createPartialMockForAbstractClass($className, $methods) + { + return $this->getMockForAbstractClass( + $className, + [], + '', + true, + true, + true, + $methods + ); + } } diff --git a/app/code/Magento/Reports/view/adminhtml/layout/reports_report_customer_accounts_grid.xml b/app/code/Magento/Reports/view/adminhtml/layout/reports_report_customer_accounts_grid.xml index 900dc08d571da..55ca286ad3d47 100644 --- a/app/code/Magento/Reports/view/adminhtml/layout/reports_report_customer_accounts_grid.xml +++ b/app/code/Magento/Reports/view/adminhtml/layout/reports_report_customer_accounts_grid.xml @@ -11,7 +11,7 @@ <referenceBlock name="adminhtml.report.grid"> <arguments> <argument name="id" xsi:type="string">gridAccounts</argument> - <argument name="dataSource" xsi:type="object">Magento\Reports\Model\ResourceModel\Accounts\Collection\Initial</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Reports\Model\ResourceModel\Accounts\Collection\Initial</argument> </arguments> </referenceBlock> <referenceBlock name="adminhtml.report.grid.export"> diff --git a/app/code/Magento/Reports/view/adminhtml/layout/reports_report_customer_orders_grid.xml b/app/code/Magento/Reports/view/adminhtml/layout/reports_report_customer_orders_grid.xml index d886e5724cb0b..f97bec3c15253 100644 --- a/app/code/Magento/Reports/view/adminhtml/layout/reports_report_customer_orders_grid.xml +++ b/app/code/Magento/Reports/view/adminhtml/layout/reports_report_customer_orders_grid.xml @@ -11,7 +11,7 @@ <referenceBlock name="adminhtml.report.grid"> <arguments> <argument name="id" xsi:type="string">gridOrdersCustomer</argument> - <argument name="dataSource" xsi:type="object">Magento\Reports\Model\ResourceModel\Customer\Orders\Collection\Initial</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Reports\Model\ResourceModel\Customer\Orders\Collection\Initial</argument> </arguments> </referenceBlock> <referenceBlock name="adminhtml.report.grid.export"> diff --git a/app/code/Magento/Reports/view/adminhtml/layout/reports_report_customer_totals_grid.xml b/app/code/Magento/Reports/view/adminhtml/layout/reports_report_customer_totals_grid.xml index 4914829cf6ebc..e1df04237c2a6 100644 --- a/app/code/Magento/Reports/view/adminhtml/layout/reports_report_customer_totals_grid.xml +++ b/app/code/Magento/Reports/view/adminhtml/layout/reports_report_customer_totals_grid.xml @@ -11,7 +11,7 @@ <referenceBlock name="adminhtml.report.grid"> <arguments> <argument name="id" xsi:type="string">gridTotalsCustomer</argument> - <argument name="dataSource" xsi:type="object">Magento\Reports\Model\ResourceModel\Customer\Totals\Collection\Initial</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Reports\Model\ResourceModel\Customer\Totals\Collection\Initial</argument> </arguments> </referenceBlock> <referenceBlock name="adminhtml.report.grid.export"> diff --git a/app/code/Magento/Reports/view/adminhtml/layout/reports_report_grid.xml b/app/code/Magento/Reports/view/adminhtml/layout/reports_report_grid.xml index 82aa475807a25..0f6fbabb6a55c 100644 --- a/app/code/Magento/Reports/view/adminhtml/layout/reports_report_grid.xml +++ b/app/code/Magento/Reports/view/adminhtml/layout/reports_report_grid.xml @@ -24,7 +24,7 @@ <argument name="use_ajax" xsi:type="string">0</argument> <argument name="pager_visibility" xsi:type="string">0</argument> <argument name="id" xsi:type="string">gridReport</argument> - <argument name="dataSource" xsi:type="object">Magento\Reports\Model\ResourceModel\Report\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Reports\Model\ResourceModel\Report\Collection</argument> </arguments> <block class="Magento\Backend\Block\Widget\Grid\Export" name="adminhtml.report.grid.export" as="grid.export"> <arguments> diff --git a/app/code/Magento/Reports/view/adminhtml/layout/reports_report_product_lowstock_grid.xml b/app/code/Magento/Reports/view/adminhtml/layout/reports_report_product_lowstock_grid.xml index 070c39259aabd..62916fe1c1d78 100644 --- a/app/code/Magento/Reports/view/adminhtml/layout/reports_report_product_lowstock_grid.xml +++ b/app/code/Magento/Reports/view/adminhtml/layout/reports_report_product_lowstock_grid.xml @@ -12,7 +12,7 @@ <arguments> <argument name="id" xsi:type="string">gridLowstock</argument> <argument name="use_ajax" xsi:type="string">0</argument> - <argument name="dataSource" xsi:type="object">Magento\Reports\Model\ResourceModel\Product\Lowstock\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Reports\Model\ResourceModel\Product\Lowstock\Collection</argument> </arguments> <block class="Magento\Backend\Block\Widget\Grid\Export" name="adminhtml.block.report.product.lowstock.export" as="grid.export"> <arguments> diff --git a/app/code/Magento/Reports/view/adminhtml/layout/reports_report_product_sold_grid.xml b/app/code/Magento/Reports/view/adminhtml/layout/reports_report_product_sold_grid.xml index a1b01aeeb526f..22c66352b32e4 100644 --- a/app/code/Magento/Reports/view/adminhtml/layout/reports_report_product_sold_grid.xml +++ b/app/code/Magento/Reports/view/adminhtml/layout/reports_report_product_sold_grid.xml @@ -11,7 +11,7 @@ <referenceBlock name="adminhtml.report.grid"> <arguments> <argument name="id" xsi:type="string">gridProductsSold</argument> - <argument name="dataSource" xsi:type="object">Magento\Reports\Model\ResourceModel\Product\Sold\Collection\Initial</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Reports\Model\ResourceModel\Product\Sold\Collection\Initial</argument> </arguments> </referenceBlock> <referenceBlock name="adminhtml.report.grid.export"> diff --git a/app/code/Magento/Reports/view/adminhtml/layout/reports_report_review_customer_grid.xml b/app/code/Magento/Reports/view/adminhtml/layout/reports_report_review_customer_grid.xml index f941ca52eef59..a728f471e4def 100644 --- a/app/code/Magento/Reports/view/adminhtml/layout/reports_report_review_customer_grid.xml +++ b/app/code/Magento/Reports/view/adminhtml/layout/reports_report_review_customer_grid.xml @@ -13,7 +13,7 @@ <argument name="id" xsi:type="string">customers_grid</argument> <argument name="default_sort" xsi:type="string">review_cnt</argument> <argument name="default_dir" xsi:type="string">desc</argument> - <argument name="dataSource" xsi:type="object">Magento\Reports\Model\ResourceModel\Review\Customer\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Reports\Model\ResourceModel\Review\Customer\Collection</argument> </arguments> <block class="Magento\Backend\Block\Widget\Grid\Export" name="adminhtml.block.report.review.customer.export" as="grid.export"> <arguments> diff --git a/app/code/Magento/Reports/view/adminhtml/layout/reports_report_review_product_grid.xml b/app/code/Magento/Reports/view/adminhtml/layout/reports_report_review_product_grid.xml index 1275e761ade3c..26d0e8b13659d 100644 --- a/app/code/Magento/Reports/view/adminhtml/layout/reports_report_review_product_grid.xml +++ b/app/code/Magento/Reports/view/adminhtml/layout/reports_report_review_product_grid.xml @@ -13,7 +13,7 @@ <argument name="id" xsi:type="string">gridProducts</argument> <argument name="default_sort" xsi:type="string">review_cnt</argument> <argument name="default_dir" xsi:type="string">desc</argument> - <argument name="dataSource" xsi:type="object">Magento\Reports\Model\ResourceModel\Review\Product\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Reports\Model\ResourceModel\Review\Product\Collection</argument> </arguments> <block class="Magento\Backend\Block\Widget\Grid\Export" name="adminhtml.block.report.review.product.export" as="grid.export"> <arguments> diff --git a/app/code/Magento/Reports/view/adminhtml/layout/reports_report_statistics_index.xml b/app/code/Magento/Reports/view/adminhtml/layout/reports_report_statistics_index.xml index 4ec984ef9fc11..649dc7ceeb065 100644 --- a/app/code/Magento/Reports/view/adminhtml/layout/reports_report_statistics_index.xml +++ b/app/code/Magento/Reports/view/adminhtml/layout/reports_report_statistics_index.xml @@ -15,7 +15,7 @@ <argument name="use_ajax" xsi:type="string">0</argument> <argument name="sortable" xsi:type="string">0</argument> <argument name="pager_visibility" xsi:type="string">0</argument> - <argument name="dataSource" xsi:type="object">Magento\Reports\Model\ResourceModel\Refresh\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Reports\Model\ResourceModel\Refresh\Collection</argument> </arguments> <block class="Magento\Backend\Block\Widget\Grid\Massaction" name="adminhtml.block.refresh.statistics.massactions" as="grid.massaction"> <arguments> diff --git a/app/code/Magento/Review/Block/Adminhtml/Edit/Form.php b/app/code/Magento/Review/Block/Adminhtml/Edit/Form.php index 8a8395de72b62..4f7237a0b44be 100644 --- a/app/code/Magento/Review/Block/Adminhtml/Edit/Form.php +++ b/app/code/Magento/Review/Block/Adminhtml/Edit/Form.php @@ -4,11 +4,11 @@ * See COPYING.txt for license details. */ +namespace Magento\Review\Block\Adminhtml\Edit; + /** * Adminhtml Review Edit Form */ -namespace Magento\Review\Block\Adminhtml\Edit; - class Form extends \Magento\Backend\Block\Widget\Form\Generic { /** @@ -84,7 +84,8 @@ protected function _prepareForm() 'review/*/save', [ 'id' => $this->getRequest()->getParam('id'), - 'ret' => $this->_coreRegistry->registry('ret') + 'ret' => $this->_coreRegistry->registry('ret'), + 'productId' => $this->getRequest()->getParam('productId') ] ), 'method' => 'post', diff --git a/app/code/Magento/Review/Controller/Adminhtml/Product/Save.php b/app/code/Magento/Review/Controller/Adminhtml/Product/Save.php index 6217729f53e50..57b1e538ddb6b 100644 --- a/app/code/Magento/Review/Controller/Adminhtml/Product/Save.php +++ b/app/code/Magento/Review/Controller/Adminhtml/Product/Save.php @@ -73,6 +73,10 @@ public function execute() } else { $resultRedirect->setPath('*/*/'); } + $productId = (int)$this->getRequest()->getParam('productId'); + if ($productId) { + $resultRedirect->setPath("catalog/product/edit/id/$productId"); + } return $resultRedirect; } $resultRedirect->setPath('review/*/'); diff --git a/app/code/Magento/Review/Model/ResourceModel/Review/Product/Collection.php b/app/code/Magento/Review/Model/ResourceModel/Review/Product/Collection.php index d4e50a9e43d68..ab264ef1b6179 100644 --- a/app/code/Magento/Review/Model/ResourceModel/Review/Product/Collection.php +++ b/app/code/Magento/Review/Model/ResourceModel/Review/Product/Collection.php @@ -5,20 +5,17 @@ */ namespace Magento\Review\Model\ResourceModel\Review\Product; -use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; -use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\MetadataPool; -use Magento\Framework\Indexer\DimensionFactory; -use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface; /** * Review Product Collection * * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @since 100.0.2 */ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection @@ -92,10 +89,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection * @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection * @param ProductLimitationFactory|null $productLimitationFactory * @param MetadataPool|null $metadataPool - * @param TableMaintainer|null $tableMaintainer - * @param PriceTableResolver|null $priceTableResolver - * @param DimensionFactory|null $dimensionFactory - * @param ResourceModelPoolInterface|null $resourceModelPool + * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -122,11 +116,7 @@ public function __construct( \Magento\Review\Model\Rating\Option\VoteFactory $voteFactory, \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, ProductLimitationFactory $productLimitationFactory = null, - MetadataPool $metadataPool = null, - TableMaintainer $tableMaintainer = null, - PriceTableResolver $priceTableResolver = null, - DimensionFactory $dimensionFactory = null, - ResourceModelPoolInterface $resourceModelPool = null + MetadataPool $metadataPool = null ) { $this->_ratingFactory = $ratingFactory; $this->_voteFactory = $voteFactory; @@ -152,11 +142,7 @@ public function __construct( $groupManagement, $connection, $productLimitationFactory, - $metadataPool, - $tableMaintainer, - $priceTableResolver, - $dimensionFactory, - $resourceModelPool + $metadataPool ); } diff --git a/app/code/Magento/Review/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Review/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..89882707f5ebd --- /dev/null +++ b/app/code/Magento/Review/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,31 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuUserContentReviews"> + <data key="pageTitle">Reviews</data> + <data key="title">Reviews</data> + <data key="dataUiId">magento-review-catalog-reviews-ratings-reviews-all</data> + </entity> + <entity name="AdminMenuReportsReviewsByCustomers"> + <data key="pageTitle">Customer Reviews Report</data> + <data key="title">By Customers</data> + <data key="dataUiId">magento-review-report-review-customer</data> + </entity> + <entity name="AdminMenuReportsReviewsByProducts"> + <data key="pageTitle">Product Reviews Report</data> + <data key="title">By Products</data> + <data key="dataUiId">magento-review-report-review-product</data> + </entity> + <entity name="AdminMenuAttributesRating"> + <data key="pageTitle">Ratings</data> + <data key="title">Rating</data> + <data key="dataUiId">magento-review-catalog-reviews-ratings-ratings</data> + </entity> +</entities> diff --git a/app/code/Magento/Review/Test/Mftf/Test/AdminMarketingReviewsNavigateMenuTest.xml b/app/code/Magento/Review/Test/Mftf/Test/AdminMarketingReviewsNavigateMenuTest.xml new file mode 100644 index 0000000000000..fade220d22100 --- /dev/null +++ b/app/code/Magento/Review/Test/Mftf/Test/AdminMarketingReviewsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminMarketingReviewsNavigateMenuTest"> + <annotations> + <features value="Review"/> + <stories value="Menu Navigation"/> + <title value="Admin marketing reviews navigate menu test"/> + <description value="Admin should be able to navigate to Marketing > Reviews"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14196"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsViewsPage"> + <argument name="menuUiId" value="{{AdminMenuMarketing.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuUserContentReviews.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuUserContentReviews.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Review/Test/Mftf/Test/AdminReportsByCustomersNavigateMenuTest.xml b/app/code/Magento/Review/Test/Mftf/Test/AdminReportsByCustomersNavigateMenuTest.xml new file mode 100644 index 0000000000000..58492424e76f7 --- /dev/null +++ b/app/code/Magento/Review/Test/Mftf/Test/AdminReportsByCustomersNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsByCustomersNavigateMenuTest"> + <annotations> + <features value="Review"/> + <stories value="Menu Navigation"/> + <title value="Admin reports by customers navigate menu test"/> + <description value="Admin should be able to navigate to Reports > By Customers"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14197"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsByCustomersPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsReviewsByCustomers.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsReviewsByCustomers.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Review/Test/Mftf/Test/AdminReportsByProductsNavigateMenuTest.xml b/app/code/Magento/Review/Test/Mftf/Test/AdminReportsByProductsNavigateMenuTest.xml new file mode 100644 index 0000000000000..e848aa4f22023 --- /dev/null +++ b/app/code/Magento/Review/Test/Mftf/Test/AdminReportsByProductsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminReportsByProductsNavigateMenuTest"> + <annotations> + <features value="Review"/> + <stories value="Menu Navigation"/> + <title value="Admin reports by products navigate menu test"/> + <description value="Admin should be able to navigate to Reports > By Products"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14198"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsByProductsPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsReviewsByProducts.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuReportsReviewsByProducts.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Review/Test/Mftf/Test/AdminStoresRatingNavigateMenuTest.xml b/app/code/Magento/Review/Test/Mftf/Test/AdminStoresRatingNavigateMenuTest.xml new file mode 100644 index 0000000000000..511ed5439dc3d --- /dev/null +++ b/app/code/Magento/Review/Test/Mftf/Test/AdminStoresRatingNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminStoresRatingNavigateMenuTest"> + <annotations> + <features value="Review"/> + <stories value="Menu Navigation"/> + <title value="Admin stores rating navigate menu test"/> + <description value="Admin should be able to navigate to Stores > Rating"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14199"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToStoresRatingPage"> + <argument name="menuUiId" value="{{AdminMenuStores.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuAttributesRating.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuAttributesRating.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Review/view/adminhtml/layout/rating_block.xml b/app/code/Magento/Review/view/adminhtml/layout/rating_block.xml index b439bcb1c2710..414cec14c3bec 100644 --- a/app/code/Magento/Review/view/adminhtml/layout/rating_block.xml +++ b/app/code/Magento/Review/view/adminhtml/layout/rating_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.rating.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">ratingsGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\Review\Model\ResourceModel\Rating\Grid\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Review\Model\ResourceModel\Rating\Grid\Collection</argument> <argument name="default_sort" xsi:type="string">rating_code</argument> <argument name="default_dir" xsi:type="string">ASC</argument> <argument name="save_parameters_in_session" xsi:type="string">1</argument> diff --git a/app/code/Magento/Rule/view/adminhtml/web/rules.js b/app/code/Magento/Rule/view/adminhtml/web/rules.js index 202337c39da35..95175d272f9af 100644 --- a/app/code/Magento/Rule/view/adminhtml/web/rules.js +++ b/app/code/Magento/Rule/view/adminhtml/web/rules.js @@ -13,6 +13,7 @@ define([ 'mage/translate', 'prototype' ], function (jQuery) { + 'use strict'; var VarienRulesForm = new Class.create(); @@ -125,7 +126,7 @@ define([ var values = this.updateElement.value.split(','), s = ''; - for (i = 0; i < values.length; i++) { + for (var i = 0; i < values.length; i++) { s = values[i].strip(); if (s != '') { @@ -254,7 +255,7 @@ define([ if (elem && elem.options) { var selectedOptions = []; - for (i = 0; i < elem.options.length; i++) { + for (var i = 0; i < elem.options.length; i++) { if (elem.options[i].selected) { selectedOptions.push(elem.options[i].text); } @@ -299,14 +300,14 @@ define([ }, changeVisibilityForValueRuleParam: function(elem) { - let parsedElementId = elem.id.split('__'); - if (parsedElementId[2] != 'operator') { + var parsedElementId = elem.id.split('__'); + if (parsedElementId[2] !== 'operator') { return false; } - let valueElement = jQuery('#' + parsedElementId[0] + '__' + parsedElementId[1] + '__value'); + var valueElement = jQuery('#' + parsedElementId[0] + '__' + parsedElementId[1] + '__value'); - if(elem.value == '<=>') { + if(elem.value === '<=>') { valueElement.closest('.rule-param').hide(); } else { valueElement.closest('.rule-param').show(); diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid.php index 4bd2227d4bb1e..9a271f741edda 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid.php @@ -5,12 +5,17 @@ */ namespace Magento\Sales\Block\Adminhtml\Order\Create\Search; +use Magento\Sales\Block\Adminhtml\Order\Create\Search\Grid\DataProvider\ProductCollection + as ProductCollectionDataProvider; +use Magento\Framework\App\ObjectManager; + /** * Adminhtml sales order create search products block * * @api * @author Magento Core Team <core@magentocommerce.com> * @since 100.0.2 + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Grid extends \Magento\Backend\Block\Widget\Grid\Extended { @@ -42,6 +47,11 @@ class Grid extends \Magento\Backend\Block\Widget\Grid\Extended */ protected $_productFactory; + /** + * @var ProductCollectionDataProvider $productCollectionProvider + */ + private $productCollectionProvider; + /** * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Backend\Helper\Data $backendHelper @@ -50,6 +60,7 @@ class Grid extends \Magento\Backend\Block\Widget\Grid\Extended * @param \Magento\Backend\Model\Session\Quote $sessionQuote * @param \Magento\Sales\Model\Config $salesConfig * @param array $data + * @param ProductCollectionDataProvider|null $productCollectionProvider */ public function __construct( \Magento\Backend\Block\Template\Context $context, @@ -58,12 +69,15 @@ public function __construct( \Magento\Catalog\Model\Config $catalogConfig, \Magento\Backend\Model\Session\Quote $sessionQuote, \Magento\Sales\Model\Config $salesConfig, - array $data = [] + array $data = [], + ProductCollectionDataProvider $productCollectionProvider = null ) { $this->_productFactory = $productFactory; $this->_catalogConfig = $catalogConfig; $this->_sessionQuote = $sessionQuote; $this->_salesConfig = $salesConfig; + $this->productCollectionProvider = $productCollectionProvider + ?: ObjectManager::getInstance()->get(ProductCollectionDataProvider::class); parent::__construct($context, $backendHelper, $data); } @@ -140,20 +154,18 @@ protected function _addColumnFilterToCollection($column) */ protected function _prepareCollection() { + $attributes = $this->_catalogConfig->getProductAttributes(); + $store = $this->getStore(); + /* @var $collection \Magento\Catalog\Model\ResourceModel\Product\Collection */ - $collection = $this->_productFactory->create()->getCollection(); - $collection->setStore( - $this->getStore() - )->addAttributeToSelect( + $collection = $this->productCollectionProvider->getCollectionForStore($store); + $collection->addAttributeToSelect( $attributes - )->addAttributeToSelect( - 'sku' - )->addStoreFilter()->addAttributeToFilter( + ); + $collection->addAttributeToFilter( 'type_id', $this->_salesConfig->getAvailableProductTypes() - )->addAttributeToSelect( - 'gift_message_available' ); $this->setCollection($collection); diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid/DataProvider/ProductCollection.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid/DataProvider/ProductCollection.php new file mode 100644 index 0000000000000..733791a2f9549 --- /dev/null +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid/DataProvider/ProductCollection.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Block\Adminhtml\Order\Create\Search\Grid\DataProvider; + +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory; +use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\Store\Model\Store; + +/** + * Prepares product collection for the grid + */ +class ProductCollection +{ + /** + * @var ProductCollectionFactory + */ + private $collectionFactory; + + /** + * @param ProductCollectionFactory $collectionFactory + */ + public function __construct( + ProductCollectionFactory $collectionFactory + ) { + $this->collectionFactory = $collectionFactory; + } + + /** + * Provide products collection filtered with store + * + * @param Store $store + * @return Collection + */ + public function getCollectionForStore(Store $store):Collection + { + /** @var Collection $collection */ + $collection = $this->collectionFactory->create(); + + $collection->setStore($store); + $collection->addAttributeToSelect( + 'gift_message_available' + ); + $collection->addAttributeToSelect( + 'sku' + ); + $collection->addStoreFilter(); + + return $collection; + } +} diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Invoice/View.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Invoice/View.php index fd6e5f403f2de..074aa99a5e791 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Invoice/View.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Invoice/View.php @@ -113,8 +113,8 @@ protected function _construct() $orderPayment->canRefund() && !$this->getInvoice()->getIsUsedForRefund() ) { $this->buttonList->add( - 'capture', - [ // capture? + 'credit-memo', + [ 'label' => __('Credit Memo'), 'class' => 'credit-memo', 'onclick' => 'setLocation(\'' . $this->getCreditMemoUrl() . '\')' diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Sender/EmailSender.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Sender/EmailSender.php index ecd5670a319e7..3d2c13cbaaaa9 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo/Sender/EmailSender.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Sender/EmailSender.php @@ -89,6 +89,7 @@ public function __construct( * @param bool $forceSyncMode * * @return bool + * @throws \Exception */ public function send( \Magento\Sales\Api\Data\OrderInterface $order, @@ -96,7 +97,7 @@ public function send( \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null, $forceSyncMode = false ) { - $creditmemo->setSendEmail(true); + $creditmemo->setSendEmail($this->identityContainer->isEnabled()); if (!$this->globalConfig->getValue('sales_email/general/async_sending') || $forceSyncMode) { $transport = [ @@ -145,6 +146,7 @@ public function send( * @param \Magento\Sales\Api\Data\OrderInterface $order * * @return string + * @throws \Exception */ private function getPaymentHtml(\Magento\Sales\Api\Data\OrderInterface $order) { diff --git a/app/code/Magento/Sales/Model/Order/Email/Sender/CreditmemoSender.php b/app/code/Magento/Sales/Model/Order/Email/Sender/CreditmemoSender.php index 8004483583114..126fe4f93f1e0 100644 --- a/app/code/Magento/Sales/Model/Order/Email/Sender/CreditmemoSender.php +++ b/app/code/Magento/Sales/Model/Order/Email/Sender/CreditmemoSender.php @@ -57,10 +57,10 @@ class CreditmemoSender extends Sender * @param CreditmemoIdentity $identityContainer * @param Order\Email\SenderBuilderFactory $senderBuilderFactory * @param \Psr\Log\LoggerInterface $logger + * @param Renderer $addressRenderer * @param PaymentHelper $paymentHelper * @param CreditmemoResource $creditmemoResource * @param \Magento\Framework\App\Config\ScopeConfigInterface $globalConfig - * @param Renderer $addressRenderer * @param ManagerInterface $eventManager */ public function __construct( @@ -96,10 +96,11 @@ public function __construct( * @param Creditmemo $creditmemo * @param bool $forceSyncMode * @return bool + * @throws \Exception */ public function send(Creditmemo $creditmemo, $forceSyncMode = false) { - $creditmemo->setSendEmail(true); + $creditmemo->setSendEmail($this->identityContainer->isEnabled()); if (!$this->globalConfig->getValue('sales_email/general/async_sending') || $forceSyncMode) { $order = $creditmemo->getOrder(); @@ -146,6 +147,7 @@ public function send(Creditmemo $creditmemo, $forceSyncMode = false) * * @param Order $order * @return string + * @throws \Exception */ protected function getPaymentHtml(Order $order) { diff --git a/app/code/Magento/Sales/Model/Order/Email/Sender/InvoiceSender.php b/app/code/Magento/Sales/Model/Order/Email/Sender/InvoiceSender.php index 994fd79945cfd..ba3895cfa1524 100644 --- a/app/code/Magento/Sales/Model/Order/Email/Sender/InvoiceSender.php +++ b/app/code/Magento/Sales/Model/Order/Email/Sender/InvoiceSender.php @@ -57,10 +57,10 @@ class InvoiceSender extends Sender * @param InvoiceIdentity $identityContainer * @param Order\Email\SenderBuilderFactory $senderBuilderFactory * @param \Psr\Log\LoggerInterface $logger + * @param Renderer $addressRenderer * @param PaymentHelper $paymentHelper * @param InvoiceResource $invoiceResource * @param \Magento\Framework\App\Config\ScopeConfigInterface $globalConfig - * @param Renderer $addressRenderer * @param ManagerInterface $eventManager */ public function __construct( @@ -96,10 +96,11 @@ public function __construct( * @param Invoice $invoice * @param bool $forceSyncMode * @return bool + * @throws \Exception */ public function send(Invoice $invoice, $forceSyncMode = false) { - $invoice->setSendEmail(true); + $invoice->setSendEmail($this->identityContainer->isEnabled()); if (!$this->globalConfig->getValue('sales_email/general/async_sending') || $forceSyncMode) { $order = $invoice->getOrder(); @@ -146,6 +147,7 @@ public function send(Invoice $invoice, $forceSyncMode = false) * * @param Order $order * @return string + * @throws \Exception */ protected function getPaymentHtml(Order $order) { diff --git a/app/code/Magento/Sales/Model/Order/Email/Sender/ShipmentSender.php b/app/code/Magento/Sales/Model/Order/Email/Sender/ShipmentSender.php index 6729c746f5565..10e5e37a49394 100644 --- a/app/code/Magento/Sales/Model/Order/Email/Sender/ShipmentSender.php +++ b/app/code/Magento/Sales/Model/Order/Email/Sender/ShipmentSender.php @@ -57,10 +57,10 @@ class ShipmentSender extends Sender * @param ShipmentIdentity $identityContainer * @param Order\Email\SenderBuilderFactory $senderBuilderFactory * @param \Psr\Log\LoggerInterface $logger + * @param Renderer $addressRenderer * @param PaymentHelper $paymentHelper * @param ShipmentResource $shipmentResource * @param \Magento\Framework\App\Config\ScopeConfigInterface $globalConfig - * @param Renderer $addressRenderer * @param ManagerInterface $eventManager */ public function __construct( @@ -96,10 +96,11 @@ public function __construct( * @param Shipment $shipment * @param bool $forceSyncMode * @return bool + * @throws \Exception */ public function send(Shipment $shipment, $forceSyncMode = false) { - $shipment->setSendEmail(true); + $shipment->setSendEmail($this->identityContainer->isEnabled()); if (!$this->globalConfig->getValue('sales_email/general/async_sending') || $forceSyncMode) { $order = $shipment->getOrder(); @@ -146,6 +147,7 @@ public function send(Shipment $shipment, $forceSyncMode = false) * * @param Order $order * @return string + * @throws \Exception */ protected function getPaymentHtml(Order $order) { diff --git a/app/code/Magento/Sales/Model/Order/Invoice/Sender/EmailSender.php b/app/code/Magento/Sales/Model/Order/Invoice/Sender/EmailSender.php index aa0687bee504f..5ae3306ddf75b 100644 --- a/app/code/Magento/Sales/Model/Order/Invoice/Sender/EmailSender.php +++ b/app/code/Magento/Sales/Model/Order/Invoice/Sender/EmailSender.php @@ -89,6 +89,7 @@ public function __construct( * @param bool $forceSyncMode * * @return bool + * @throws \Exception */ public function send( \Magento\Sales\Api\Data\OrderInterface $order, @@ -96,7 +97,7 @@ public function send( \Magento\Sales\Api\Data\InvoiceCommentCreationInterface $comment = null, $forceSyncMode = false ) { - $invoice->setSendEmail(true); + $invoice->setSendEmail($this->identityContainer->isEnabled()); if (!$this->globalConfig->getValue('sales_email/general/async_sending') || $forceSyncMode) { $transport = [ @@ -145,6 +146,7 @@ public function send( * @param \Magento\Sales\Api\Data\OrderInterface $order * * @return string + * @throws \Exception */ private function getPaymentHtml(\Magento\Sales\Api\Data\OrderInterface $order) { diff --git a/app/code/Magento/Sales/Model/Order/Payment.php b/app/code/Magento/Sales/Model/Order/Payment.php index fc39755c94ee0..5d1d3f0d040a7 100644 --- a/app/code/Magento/Sales/Model/Order/Payment.php +++ b/app/code/Magento/Sales/Model/Order/Payment.php @@ -300,7 +300,7 @@ public function canCapture() } /** - * Check refund availability + * Check refund availability. * * @return bool */ @@ -310,7 +310,7 @@ public function canRefund() } /** - * Check partial refund availability for invoice + * Check partial refund availability for invoice. * * @return bool */ @@ -320,7 +320,7 @@ public function canRefundPartialPerInvoice() } /** - * Check partial capture availability + * Check partial capture availability. * * @return bool */ @@ -546,9 +546,7 @@ public function cancelInvoice($invoice) } /** - * Create new invoice with maximum qty for invoice for each item - * - * Register this invoice and capture + * Create new invoice with maximum qty for invoice for each item register this invoice and capture * * @return Invoice */ @@ -686,6 +684,7 @@ public function refund($creditmemo) $gateway->refund($this, $baseAmountToRefund); $creditmemo->setTransactionId($this->getLastTransId()); + // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Magento\Framework\Exception\LocalizedException $e) { if (!$captureTxn) { throw new \Magento\Framework\Exception\LocalizedException( @@ -732,10 +731,14 @@ public function refund($creditmemo) $message = $message = $this->prependMessage($message); $message = $this->_appendTransactionToMessage($transaction, $message); $orderState = $this->getOrderStateResolver()->getStateForOrder($this->getOrder()); + $statuses = $this->getOrder()->getConfig()->getStateStatuses($orderState, false); + $status = in_array($this->getOrder()->getStatus(), $statuses, true) + ? $this->getOrder()->getStatus() + : $this->getOrder()->getConfig()->getStateDefaultStatus($orderState); $this->getOrder() ->addStatusHistoryComment( $message, - $this->getOrder()->getConfig()->getStateDefaultStatus($orderState) + $status )->setIsCustomerNotified($creditmemo->getOrder()->getCustomerNoteNotify()); $this->_eventManager->dispatch( 'sales_order_payment_refund', @@ -1203,7 +1206,7 @@ public function addTransaction($type, $salesDocument = null, $failSafe = false) } /** - * Add message to the specified transaction. + * Add transaction comments to order. * * @param Transaction|null $transaction * @param string $message diff --git a/app/code/Magento/Sales/Model/Order/Shipment/Sender/EmailSender.php b/app/code/Magento/Sales/Model/Order/Shipment/Sender/EmailSender.php index 0a393548069f5..3657f84d4445d 100644 --- a/app/code/Magento/Sales/Model/Order/Shipment/Sender/EmailSender.php +++ b/app/code/Magento/Sales/Model/Order/Shipment/Sender/EmailSender.php @@ -89,6 +89,7 @@ public function __construct( * @param bool $forceSyncMode * * @return bool + * @throws \Exception */ public function send( \Magento\Sales\Api\Data\OrderInterface $order, @@ -96,7 +97,7 @@ public function send( \Magento\Sales\Api\Data\ShipmentCommentCreationInterface $comment = null, $forceSyncMode = false ) { - $shipment->setSendEmail(true); + $shipment->setSendEmail($this->identityContainer->isEnabled()); if (!$this->globalConfig->getValue('sales_email/general/async_sending') || $forceSyncMode) { $transport = [ @@ -145,6 +146,7 @@ public function send( * @param \Magento\Sales\Api\Data\OrderInterface $order * * @return string + * @throws \Exception */ private function getPaymentHtml(\Magento\Sales\Api\Data\OrderInterface $order) { diff --git a/app/code/Magento/Sales/Model/OrderRepository.php b/app/code/Magento/Sales/Model/OrderRepository.php index 9a1392fbe9065..79548cb190754 100644 --- a/app/code/Magento/Sales/Model/OrderRepository.php +++ b/app/code/Magento/Sales/Model/OrderRepository.php @@ -6,7 +6,9 @@ namespace Magento\Sales\Model; +use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Sales\Api\Data\OrderExtensionFactory; @@ -16,7 +18,6 @@ use Magento\Sales\Api\Data\ShippingAssignmentInterface; use Magento\Sales\Model\Order\ShippingAssignmentBuilder; use Magento\Sales\Model\ResourceModel\Metadata; -use Magento\Framework\App\ObjectManager; use Magento\Tax\Api\OrderTaxManagementInterface; use Magento\Payment\Api\Data\PaymentAdditionalInfoInterface; use Magento\Payment\Api\Data\PaymentAdditionalInfoInterfaceFactory; @@ -74,6 +75,11 @@ class OrderRepository implements \Magento\Sales\Api\OrderRepositoryInterface */ private $serializer; + /** + * @var JoinProcessorInterface + */ + private $extensionAttributesJoinProcessor; + /** * Constructor * @@ -84,6 +90,7 @@ class OrderRepository implements \Magento\Sales\Api\OrderRepositoryInterface * @param OrderTaxManagementInterface|null $orderTaxManagement * @param PaymentAdditionalInfoInterfaceFactory|null $paymentAdditionalInfoFactory * @param JsonSerializer|null $serializer + * @param JoinProcessorInterface $extensionAttributesJoinProcessor */ public function __construct( Metadata $metadata, @@ -92,7 +99,8 @@ public function __construct( \Magento\Sales\Api\Data\OrderExtensionFactory $orderExtensionFactory = null, OrderTaxManagementInterface $orderTaxManagement = null, PaymentAdditionalInfoInterfaceFactory $paymentAdditionalInfoFactory = null, - JsonSerializer $serializer = null + JsonSerializer $serializer = null, + JoinProcessorInterface $extensionAttributesJoinProcessor = null ) { $this->metadata = $metadata; $this->searchResultFactory = $searchResultFactory; @@ -106,6 +114,8 @@ public function __construct( ->get(PaymentAdditionalInfoInterfaceFactory::class); $this->serializer = $serializer ?: ObjectManager::getInstance() ->get(JsonSerializer::class); + $this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor + ?: ObjectManager::getInstance()->get(JoinProcessorInterface::class); } /** @@ -198,6 +208,7 @@ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCr { /** @var \Magento\Sales\Api\Data\OrderSearchResultInterface $searchResult */ $searchResult = $this->searchResultFactory->create(); + $this->extensionAttributesJoinProcessor->process($searchResult); $this->collectionProcessor->process($searchCriteria, $searchResult); $searchResult->setSearchCriteria($searchCriteria); foreach ($searchResult->getItems() as $order) { diff --git a/app/code/Magento/Sales/Model/RefundOrder.php b/app/code/Magento/Sales/Model/RefundOrder.php index d79f5ecf857cb..07555cba1b7f7 100644 --- a/app/code/Magento/Sales/Model/RefundOrder.php +++ b/app/code/Magento/Sales/Model/RefundOrder.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Sales\Model; use Magento\Framework\App\ResourceConnection; @@ -151,10 +152,13 @@ public function execute( $creditmemo->setState(\Magento\Sales\Model\Order\Creditmemo::STATE_REFUNDED); $order->setCustomerNoteNotify($notify); $order = $this->refundAdapter->refund($creditmemo, $order); - $order->setState( - $this->orderStateResolver->getStateForOrder($order, []) - ); - $order->setStatus($this->config->getStateDefaultStatus($order->getState())); + $orderState = $this->orderStateResolver->getStateForOrder($order, []); + $order->setState($orderState); + $statuses = $this->config->getStateStatuses($orderState, false); + $status = in_array($order->getStatus(), $statuses, true) + ? $order->getStatus() + : $this->config->getStateDefaultStatus($orderState); + $order->setStatus($status); $order = $this->orderRepository->save($order); $creditmemo = $this->creditmemoRepository->save($creditmemo); diff --git a/app/code/Magento/Sales/Model/Service/InvoiceService.php b/app/code/Magento/Sales/Model/Service/InvoiceService.php index 02242e92c8bf5..18efeba726c1b 100644 --- a/app/code/Magento/Sales/Model/Service/InvoiceService.php +++ b/app/code/Magento/Sales/Model/Service/InvoiceService.php @@ -149,6 +149,7 @@ public function setVoid($id) */ public function prepareInvoice(Order $order, array $qtys = []) { + $isQtysEmpty = empty($qtys); $invoice = $this->orderConverter->toInvoice($order); $totalQty = 0; $qtys = $this->prepareItemsQty($order, $qtys); @@ -161,7 +162,7 @@ public function prepareInvoice(Order $order, array $qtys = []) $qty = (double) $qtys[$orderItem->getId()]; } elseif ($orderItem->isDummy()) { $qty = $orderItem->getQtyOrdered() ? $orderItem->getQtyOrdered() : 1; - } elseif (empty($qtys)) { + } elseif ($isQtysEmpty) { $qty = $orderItem->getQtyToInvoice(); } else { $qty = 0; diff --git a/app/code/Magento/Sales/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Sales/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..4f6faccbb26d4 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,41 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuSalesCreditMemos"> + <data key="pageTitle">Credit Memos</data> + <data key="title">Credit Memos</data> + <data key="dataUiId">magento-sales-sales-creditmemo</data> + </entity> + <entity name="AdminMenuSalesInvoices"> + <data key="pageTitle">Invoices</data> + <data key="title">Invoices</data> + <data key="dataUiId">magento-sales-sales-invoice</data> + </entity> + <entity name="AdminMenuSalesOrders"> + <data key="pageTitle">Orders</data> + <data key="title">Orders</data> + <data key="dataUiId">magento-sales-sales-order</data> + </entity> + <entity name="AdminMenuSalesShipments"> + <data key="pageTitle">Shipments</data> + <data key="title">Shipments</data> + <data key="dataUiId">magento-sales-sales-shipment</data> + </entity> + <entity name="AdminMenuSalesTransactions"> + <data key="pageTitle">Transactions</data> + <data key="title">Transactions</data> + <data key="dataUiId">magento-sales-sales-transactions</data> + </entity> + <entity name="AdminMenuStoresSettingsOrderStatus"> + <data key="pageTitle">Order Status</data> + <data key="title">Order Status</data> + <data key="dataUiId">magento-sales-system-order-statuses</data> + </entity> +</entities> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml index 1a12a68a6874a..1c3cf9cc2b350 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml @@ -9,7 +9,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderFormPaymentSection"> - <element name="header" type="text" selector="#shipping-methods span.title"/> + <element name="header" type="text" selector="#order-methods span.title"/> <element name="getShippingMethods" type="text" selector="#order-shipping_method a.action-default" timeout="30"/> <element name="flatRateOption" type="radio" selector="#s_method_flatrate_flatrate" timeout="30"/> <element name="shippingError" type="text" selector="#order[has_shipping]-error"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminSalesCreditMemosNavigateMenuTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSalesCreditMemosNavigateMenuTest.xml new file mode 100644 index 0000000000000..af7cc1822d215 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSalesCreditMemosNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminSalesCreditMemosNavigateMenuTest"> + <annotations> + <features value="Sales"/> + <stories value="Menu Navigation"/> + <title value="Admin sales credit memos navigate menu test"/> + <description value="Admin should be able to navigate to Sales > Credit Memos"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14140"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToSalesCreditMemosPage"> + <argument name="menuUiId" value="{{AdminMenuSales.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSalesCreditMemos.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSalesCreditMemos.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminSalesInvoicesNavigateMenuTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSalesInvoicesNavigateMenuTest.xml new file mode 100644 index 0000000000000..5a38a66d1f4b2 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSalesInvoicesNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminSalesInvoicesNavigateMenuTest"> + <annotations> + <features value="Sales"/> + <stories value="Menu Navigation"/> + <title value="Admin sales invoices navigate menu test"/> + <description value="Admin should be able to navigate to Sales > Invoices"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14138"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToSalesInvoicesPage"> + <argument name="menuUiId" value="{{AdminMenuSales.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSalesInvoices.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSalesInvoices.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminSalesOrdersNavigateMenuTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSalesOrdersNavigateMenuTest.xml new file mode 100644 index 0000000000000..8099254923a2c --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSalesOrdersNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminSalesOrdersNavigateMenuTest"> + <annotations> + <features value="Sales"/> + <stories value="Menu Navigation"/> + <title value="Admin sales orders navigate menu test"/> + <description value="Admin should be able to navigate to Sales > Orders"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14137"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToSalesOrderPage"> + <argument name="menuUiId" value="{{AdminMenuSales.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSalesOrders.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSalesOrders.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminSalesShipmentsNavigateMenuTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSalesShipmentsNavigateMenuTest.xml new file mode 100644 index 0000000000000..5717c6c90fc17 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSalesShipmentsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminSalesShipmentsNavigateMenuTest"> + <annotations> + <features value="Sales"/> + <stories value="Menu Navigation"/> + <title value="Admin sales shipments navigate menu test"/> + <description value="Admin should be able to navigate to Sales > Shipments"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14139"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToSalesShipmentsPage"> + <argument name="menuUiId" value="{{AdminMenuSales.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSalesShipments.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSalesShipments.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminSalesTransactionsNavigateMenuTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSalesTransactionsNavigateMenuTest.xml new file mode 100644 index 0000000000000..68933be92efe6 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSalesTransactionsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminSalesTransactionsNavigateMenuTest"> + <annotations> + <features value="Sales"/> + <stories value="Menu Navigation"/> + <title value="Admin sales transactions navigate menu test"/> + <description value="Admin should be able to navigate to Sales > Transactions"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14141"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToSalesTransactionsPage"> + <argument name="menuUiId" value="{{AdminMenuSales.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSalesTransactions.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSalesTransactions.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminStoresOrderStatusNavigateMenuTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminStoresOrderStatusNavigateMenuTest.xml new file mode 100644 index 0000000000000..d55cde1449033 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminStoresOrderStatusNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminStoresOrderStatusNavigateMenuTest"> + <annotations> + <features value="Sales"/> + <stories value="Menu Navigation"/> + <title value="Admin stores order status navigate menu test"/> + <description value="Admin should be able to navigate to Stores > Order Status"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14142"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToStoresOrderStatusPage"> + <argument name="menuUiId" value="{{AdminMenuStores.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuStoresSettingsOrderStatus.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuStoresSettingsOrderStatus.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php index 9fd2a8b0d929f..467476c9bb406 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php @@ -1,5 +1,4 @@ <?php - /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. @@ -249,7 +248,7 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending $this->creditmemoMock->expects($this->once()) ->method('setSendEmail') - ->with(true); + ->with($emailSendingResult); if (!$configValue || $forceSyncMode) { $transport = [ @@ -279,7 +278,7 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending ->method('setTemplateVars') ->with($transport->getData()); - $this->identityContainerMock->expects($this->once()) + $this->identityContainerMock->expects($this->exactly(2)) ->method('isEnabled') ->willReturn($emailSendingResult); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php index 31bf846689230..1f074d7262f4d 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php @@ -7,6 +7,9 @@ use Magento\Sales\Model\Order\Email\Sender\CreditmemoSender; +/** + * Test for Magento\Sales\Model\Order\Email\Sender\CreditmemoSender class. + */ class CreditmemoSenderTest extends AbstractSenderTest { /** @@ -90,7 +93,7 @@ public function testSend($configValue, $forceSyncMode, $customerNoteNotify, $ema $this->creditmemoMock->expects($this->once()) ->method('setSendEmail') - ->with(true); + ->with($emailSendingResult); $this->globalConfig->expects($this->once()) ->method('getValue') @@ -130,7 +133,7 @@ public function testSend($configValue, $forceSyncMode, $customerNoteNotify, $ema ] ); - $this->identityContainerMock->expects($this->once()) + $this->identityContainerMock->expects($this->exactly(2)) ->method('isEnabled') ->willReturn($emailSendingResult); @@ -197,6 +200,8 @@ public function sendDataProvider() * @param bool $isVirtualOrder * @param int $formatCallCount * @param string|null $expectedShippingAddress + * + * @return void * @dataProvider sendVirtualOrderDataProvider */ public function testSendVirtualOrder($isVirtualOrder, $formatCallCount, $expectedShippingAddress) @@ -207,7 +212,7 @@ public function testSendVirtualOrder($isVirtualOrder, $formatCallCount, $expecte $this->creditmemoMock->expects($this->once()) ->method('setSendEmail') - ->with(true); + ->with(false); $this->globalConfig->expects($this->once()) ->method('getValue') @@ -242,7 +247,7 @@ public function testSendVirtualOrder($isVirtualOrder, $formatCallCount, $expecte ] ); - $this->identityContainerMock->expects($this->once()) + $this->identityContainerMock->expects($this->exactly(2)) ->method('isEnabled') ->willReturn(false); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php index 9c54c716e4207..d1aa5af53da4d 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php @@ -7,6 +7,9 @@ use Magento\Sales\Model\Order\Email\Sender\InvoiceSender; +/** + * Test for Magento\Sales\Model\Order\Email\Sender\InvoiceSender class. + */ class InvoiceSenderTest extends AbstractSenderTest { /** @@ -90,7 +93,7 @@ public function testSend($configValue, $forceSyncMode, $customerNoteNotify, $ema $this->invoiceMock->expects($this->once()) ->method('setSendEmail') - ->with(true); + ->with($emailSendingResult); $this->globalConfig->expects($this->once()) ->method('getValue') @@ -136,7 +139,7 @@ public function testSend($configValue, $forceSyncMode, $customerNoteNotify, $ema ] ); - $this->identityContainerMock->expects($this->once()) + $this->identityContainerMock->expects($this->exactly(2)) ->method('isEnabled') ->willReturn($emailSendingResult); @@ -212,7 +215,7 @@ public function testSendVirtualOrder($isVirtualOrder, $formatCallCount, $expecte $this->invoiceMock->expects($this->once()) ->method('setSendEmail') - ->with(true); + ->with(false); $this->globalConfig->expects($this->once()) ->method('getValue') @@ -247,7 +250,7 @@ public function testSendVirtualOrder($isVirtualOrder, $formatCallCount, $expecte ] ); - $this->identityContainerMock->expects($this->once()) + $this->identityContainerMock->expects($this->exactly(2)) ->method('isEnabled') ->willReturn(false); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php index b1b18af63b590..2d7b42bccae5a 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php @@ -7,6 +7,9 @@ use Magento\Sales\Model\Order\Email\Sender\ShipmentSender; +/** + * Test for Magento\Sales\Model\Order\Email\Sender\ShipmentSender class. + */ class ShipmentSenderTest extends AbstractSenderTest { /** @@ -90,7 +93,7 @@ public function testSend($configValue, $forceSyncMode, $customerNoteNotify, $ema $this->shipmentMock->expects($this->once()) ->method('setSendEmail') - ->with(true); + ->with($emailSendingResult); $this->globalConfig->expects($this->once()) ->method('getValue') @@ -136,7 +139,7 @@ public function testSend($configValue, $forceSyncMode, $customerNoteNotify, $ema ] ); - $this->identityContainerMock->expects($this->once()) + $this->identityContainerMock->expects($this->exactly(2)) ->method('isEnabled') ->willReturn($emailSendingResult); @@ -212,7 +215,7 @@ public function testSendVirtualOrder($isVirtualOrder, $formatCallCount, $expecte $this->shipmentMock->expects($this->once()) ->method('setSendEmail') - ->with(true); + ->with(false); $this->globalConfig->expects($this->once()) ->method('getValue') @@ -247,7 +250,7 @@ public function testSendVirtualOrder($isVirtualOrder, $formatCallCount, $expecte ] ); - $this->identityContainerMock->expects($this->once()) + $this->identityContainerMock->expects($this->exactly(2)) ->method('isEnabled') ->willReturn(false); $this->shipmentResourceMock->expects($this->once()) diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php index 8a4e2920ba207..dcf689cf7d53b 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php @@ -247,7 +247,7 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending $this->invoiceMock->expects($this->once()) ->method('setSendEmail') - ->with(true); + ->with($emailSendingResult); if (!$configValue || $forceSyncMode) { $transport = [ @@ -277,7 +277,7 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending ->method('setTemplateVars') ->with($transport->getData()); - $this->identityContainerMock->expects($this->once()) + $this->identityContainerMock->expects($this->exactly(2)) ->method('isEnabled') ->willReturn($emailSendingResult); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/PaymentTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/PaymentTest.php index 30b584b8c4ebf..9d0f10a30e6ef 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/PaymentTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/PaymentTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Sales\Test\Unit\Model\Order; use Magento\Framework\Model\Context; @@ -1526,7 +1527,7 @@ public function testRefund() $this->orderStateResolver->expects($this->once())->method('getStateForOrder') ->with($this->order) ->willReturn(Order::STATE_CLOSED); - $this->mockGetDefaultStatus(Order::STATE_CLOSED, $status); + $this->mockGetDefaultStatus(Order::STATE_CLOSED, $status, ['first, second']); $this->assertOrderUpdated(Order::STATE_PROCESSING, $status, $message); static::assertSame($this->payment, $this->payment->refund($this->creditMemoMock)); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php index 94347e8b32d54..391e99ba6f835 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php @@ -249,7 +249,7 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending $this->shipmentMock->expects($this->once()) ->method('setSendEmail') - ->with(true); + ->with($emailSendingResult); if (!$configValue || $forceSyncMode) { $transport = [ @@ -279,7 +279,7 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending ->method('setTemplateVars') ->with($transport->getData()); - $this->identityContainerMock->expects($this->once()) + $this->identityContainerMock->expects($this->exactly(2)) ->method('isEnabled') ->willReturn($emailSendingResult); diff --git a/app/code/Magento/Sales/Test/Unit/Model/RefundOrderTest.php b/app/code/Magento/Sales/Test/Unit/Model/RefundOrderTest.php index c95b56d81d6f4..1ffeaa053cc2e 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/RefundOrderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/RefundOrderTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Sales\Test\Unit\Model; use Magento\Framework\App\ResourceConnection; @@ -245,9 +246,9 @@ public function testOrderCreditmemo($orderId, $notify, $appendComment) ->method('setState') ->with(Order::STATE_CLOSED) ->willReturnSelf(); - $this->orderMock->expects($this->once()) - ->method('getState') - ->willReturn(Order::STATE_CLOSED); + $this->configMock->expects($this->once()) + ->method('getStateStatuses') + ->willReturn(['first, second']); $this->configMock->expects($this->once()) ->method('getStateDefaultStatus') ->with(Order::STATE_CLOSED) diff --git a/app/code/Magento/Sales/etc/adminhtml/system.xml b/app/code/Magento/Sales/etc/adminhtml/system.xml index 2dc467d6ca247..e437918b683b2 100644 --- a/app/code/Magento/Sales/etc/adminhtml/system.xml +++ b/app/code/Magento/Sales/etc/adminhtml/system.xml @@ -27,18 +27,23 @@ <label>Checkout Totals Sort Order</label> <field id="discount" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> <label>Discount</label> + <validate>required-number validate-number</validate> </field> <field id="grand_total" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> <label>Grand Total</label> + <validate>required-number validate-number</validate> </field> <field id="shipping" translate="label" type="text" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> <label>Shipping</label> + <validate>required-number validate-number</validate> </field> <field id="subtotal" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> <label>Subtotal</label> + <validate>required-number validate-number</validate> </field> <field id="tax" translate="label" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> <label>Tax</label> + <validate>required-number validate-number</validate> </field> </group> <group id="reorder" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_customer_block.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_customer_block.xml index 0f5a3559f3008..fe2cb7e01b727 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_customer_block.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_customer_block.xml @@ -13,7 +13,7 @@ <argument name="id" xsi:type="string">sales_order_create_customer_grid</argument> <argument name="use_ajax" xsi:type="string">1</argument> <argument name="default_sort" xsi:type="string">entity_id</argument> - <argument name="dataSource" xsi:type="object">Magento\Sales\Model\ResourceModel\Order\Customer\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Sales\Model\ResourceModel\Order\Customer\Collection</argument> <argument name="pager_visibility" xsi:type="string">1</argument> <argument name="grid_url" xsi:type="url" path="*/*/loadBlock"> <param name="block">customer_grid</param> diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_creditmemo_grid_block.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_creditmemo_grid_block.xml index 7f14ff3728a47..318416a777f3f 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_creditmemo_grid_block.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_creditmemo_grid_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="sales.order_creditmemo.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">order_creditmemos</argument> - <argument name="dataSource" xsi:type="object">Magento\Sales\Model\ResourceModel\Order\Creditmemo\Order\Grid\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Sales\Model\ResourceModel\Order\Creditmemo\Order\Grid\Collection</argument> <argument name="use_ajax" xsi:type="boolean">true</argument> <argument name="default_sort" xsi:type="string">created_at</argument> <argument name="default_dir" xsi:type="string">DESC</argument> diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_invoice_grid_block.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_invoice_grid_block.xml index 941696f0ce898..d69ed42677109 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_invoice_grid_block.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_invoice_grid_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="sales.order_invoice.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">order_invoices</argument> - <argument name="dataSource" xsi:type="object">Magento\Sales\Model\ResourceModel\Order\Invoice\Orders\Grid\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Sales\Model\ResourceModel\Order\Invoice\Orders\Grid\Collection</argument> <argument name="use_ajax" xsi:type="boolean">true</argument> <argument name="default_sort" xsi:type="string">created_at</argument> <argument name="default_dir" xsi:type="string">DESC</argument> diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_shipment_grid_block.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_shipment_grid_block.xml index 0180efd29d2fc..21b5d3b06c167 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_shipment_grid_block.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_shipment_grid_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="sales.order_shipment.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">order_shipments</argument> - <argument name="dataSource" xsi:type="object">Magento\Sales\Model\ResourceModel\Order\Shipment\Order\Grid\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Sales\Model\ResourceModel\Order\Shipment\Order\Grid\Collection</argument> <argument name="use_ajax" xsi:type="boolean">true</argument> <argument name="default_sort" xsi:type="string">created_at</argument> <argument name="default_dir" xsi:type="string">DESC</argument> diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_status_index.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_status_index.xml index 87d7644a4b00f..6854d3bb7bc38 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_status_index.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_status_index.xml @@ -12,7 +12,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="sales_order_status.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">sales_order_status_grid</argument> - <argument name="dataSource" xsi:type="object">Magento\Sales\Model\ResourceModel\Status\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Sales\Model\ResourceModel\Status\Collection</argument> <argument name="default_sort" xsi:type="string">state</argument> <argument name="default_dir" xsi:type="string">desc</argument> <argument name="pager_visibility" xsi:type="string">1</argument> diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_transactions_grid_block.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_transactions_grid_block.xml index c2f5532857202..adb722f6d0f54 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_transactions_grid_block.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_transactions_grid_block.xml @@ -13,7 +13,7 @@ <referenceBlock name="sales.transactions.grid"> <arguments> <argument name="id" xsi:type="string">order_transactions</argument> - <argument name="dataSource" xsi:type="object"> + <argument name="dataSource" xsi:type="object" shared="false"> <updater>Magento\Sales\Model\Grid\CollectionUpdater</updater> </argument> <argument name="use_ajax" xsi:type="boolean">true</argument> diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_transaction_child_block.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_transaction_child_block.xml index aa8672a68bc6c..5bdad85ebc160 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_transaction_child_block.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_transaction_child_block.xml @@ -11,7 +11,7 @@ <referenceBlock name="sales.transactions.grid"> <arguments> <argument name="id" xsi:type="string">transactionChildGrid</argument> - <argument name="dataSource" xsi:type="object"> + <argument name="dataSource" xsi:type="object" shared="false"> <updater>Magento\Sales\Model\Grid\Child\CollectionUpdater</updater> </argument> <argument name="use_ajax" xsi:type="string">false</argument> diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_transactions_grid_block.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_transactions_grid_block.xml index 9f3b7f23ba20f..37b319b94352d 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_transactions_grid_block.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_transactions_grid_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="sales.transactions.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">sales_transactions_grid</argument> - <argument name="dataSource" xsi:type="object">Magento\Sales\Model\ResourceModel\Transaction\Grid\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Sales\Model\ResourceModel\Transaction\Grid\Collection</argument> <argument name="use_ajax" xsi:type="string">true</argument> <argument name="default_sort" xsi:type="string">created_at</argument> <argument name="default_dir" xsi:type="string">DESC</argument> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/create/data.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/create/data.phtml index 170fea937348d..fdbaae2347398 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/create/data.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/create/data.phtml @@ -47,15 +47,17 @@ </div> </section> - <section id="shipping-methods" class="admin__page-section order-methods"> - <div id="order-shipping_method" class="admin__page-section-item order-shipping-method"> - <?= $block->getChildHtml('shipping_method') ?> + <section id="order-methods" class="admin__page-section order-methods"> + <div class="admin__page-section-title"> + <span class="title"><?= /* @escapeNotVerified */ __('Payment & Shipping Information') ?></span> </div> - </section> - - <section id="payment-methods" class="admin__page-section payment-methods"> - <div id="order-billing_method" class="admin__page-section-item order-billing-method"> - <?= $block->getChildHtml('billing_method') ?> + <div class="admin__page-section-content"> + <div id="order-billing_method" class="admin__page-section-item order-billing-method"> + <?= $block->getChildHtml('billing_method') ?> + </div> + <div id="order-shipping_method" class="admin__page-section-item order-shipping-method"> + <?= $block->getChildHtml('shipping_method') ?> + </div> </div> </section> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml index b0a88b8fa37dc..d1a90783c68c7 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml @@ -89,11 +89,8 @@ endif; ?> <?= $block->getForm()->toHtml() ?> <div class="admin__field admin__field-option order-save-in-address-book"> - <input name="<?= $block->getForm()->getHtmlNamePrefix() ?>[save_in_address_book]" type="checkbox" - id="<?= $block->getForm()->getHtmlIdPrefix() ?>save_in_address_book" - value="1" - <?php if (!$block->getDontSaveInAddressBook() && $block->getAddress()->getSaveInAddressBook()): ?> checked="checked"<?php endif; ?> - class="admin__control-checkbox"/> + <input name="<?= $block->getForm()->getHtmlNamePrefix() ?>[save_in_address_book]" type="checkbox" id="<?= $block->getForm()->getHtmlIdPrefix() ?>save_in_address_book" value="1" + <?php if (!$block->getDontSaveInAddressBook()): ?> checked="checked"<?php endif; ?> class="admin__control-checkbox"/> <label for="<?= $block->getForm()->getHtmlIdPrefix() ?>save_in_address_book" class="admin__field-label"><?= /* @escapeNotVerified */ __('Save in address book') ?></label> </div> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/create/totals/tax.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/create/totals/tax.phtml index 92139896273da..643146f7bb5cb 100755 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/create/totals/tax.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/create/totals/tax.phtml @@ -33,7 +33,6 @@ $taxAmount = $block->getTotal()->getValue(); <?php $percent = $info['percent']; ?> <?php $amount = $info['amount']; ?> <?php $rates = $info['rates']; ?> - <?php $isFirst = 1; ?> <?php foreach ($rates as $rate): ?> <tr class="summary-details-<?= /* @escapeNotVerified */ $taxIter ?> summary-details<?php if ($isTop): echo ' summary-details-first'; endif; ?>" style="display:none;"> @@ -44,13 +43,10 @@ $taxAmount = $block->getTotal()->getValue(); <?php endif; ?> <br /> </td> - <?php if ($isFirst): ?> - <td style="<?= /* @escapeNotVerified */ $block->getTotal()->getStyle() ?>" class="admin__total-amount" rowspan="<?= count($rates) ?>"> - <?= /* @escapeNotVerified */ $block->formatPrice($amount) ?> - </td> - <?php endif; ?> + <td style="<?= /* @escapeNotVerified */ $block->getTotal()->getStyle() ?>" class="admin__total-amount"> + <?= /* @escapeNotVerified */ $block->formatPrice(($amount*(float)$rate['percent'])/$percent) ?> + </td> </tr> - <?php $isFirst = 0; ?> <?php $isTop = 0; ?> <?php endforeach; ?> <?php endforeach; ?> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/creditmemo/create/items.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/creditmemo/create/items.phtml index fcf4ccad7060b..ba4af32ff69b2 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/creditmemo/create/items.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/creditmemo/create/items.phtml @@ -136,67 +136,76 @@ </section> <script> -require(['jquery', 'prototype'], function(jQuery){ +require(['jquery'], function(jQuery){ //<![CDATA[ -var submitButtons = $$('.submit-button'); -var updateButtons = $$('.update-button'); -var fields = $$('.qty-input'); +var submitButtons = jQuery('.submit-button'); +var updateButtons = jQuery('.update-button'); +var fields = jQuery('.qty-input'); -updateButtons.each(function (elem) {elem.disabled=true;elem.addClassName('disabled');}); +function enableButtons(buttons) { + buttons.removeClass('disabled').prop('disabled', false); +} -for(var i=0;i<fields.length;i++){ - fields[i].observe('change', checkButtonsRelation) - fields[i].baseValue = fields[i].value; +function disableButtons(buttons) { + buttons.addClass('disabled').prop('disabled', true); } +disableButtons(updateButtons); + +fields.on('change', checkButtonsRelation); +fields.each(function (i, elem) { + elem.baseValue = elem.value; +}); + function checkButtonsRelation() { var hasChanges = false; - fields.each(function (elem) { + fields.each(function (i, elem) { if (elem.baseValue != elem.value) { hasChanges = true; } }.bind(this)); if (hasChanges) { - submitButtons.each(function (elem) {elem.disabled=true;elem.addClassName('disabled');}); - updateButtons.each(function (elem) {elem.disabled=false;elem.removeClassName('disabled');}); + disableButtons(submitButtons); + enableButtons(updateButtons); } else { - submitButtons.each(function (elem) {elem.disabled=false;elem.removeClassName('disabled');}); - updateButtons.each(function (elem) {elem.disabled=true;elem.addClassName('disabled');}); + enableButtons(submitButtons); + disableButtons(updateButtons); } } submitCreditMemo = function() { - if ($('creditmemo_do_offline')) $('creditmemo_do_offline').value=0; + var creditMemoOffline = jQuery('#creditmemo_do_offline'); + if (creditMemoOffline.length) { + creditMemoOffline.prop('value', 0); + } // Temporary solution will be replaced after refactoring order functionality jQuery('#edit_form').triggerHandler('save'); -} +}; submitCreditMemoOffline = function() { - if ($('creditmemo_do_offline')) $('creditmemo_do_offline').value=1; + var creditMemoOffline = jQuery('#creditmemo_do_offline'); + if (creditMemoOffline.length) { + creditMemoOffline.prop('value', 1); + } // Temporary solution will be replaced after refactoring order functionality jQuery('#edit_form').triggerHandler('save'); -} - -var sendEmailCheckbox = $('send_email'); +}; -if (sendEmailCheckbox) { - var notifyCustomerCheckbox = $('notify_customer'); - var creditmemoCommentText = $('creditmemo_comment_text'); - Event.observe(sendEmailCheckbox, 'change', bindSendEmail); +var sendEmailCheckbox = jQuery('#send_email'); +if (sendEmailCheckbox.length) { + var notifyCustomerCheckbox = jQuery('#notify_customer'); + sendEmailCheckbox.on('change', bindSendEmail); bindSendEmail(); } -function bindSendEmail() -{ - if (sendEmailCheckbox.checked == true) { - notifyCustomerCheckbox.disabled = false; - //creditmemoCommentText.disabled = false; +function bindSendEmail() { + if (sendEmailCheckbox.prop('checked') == true) { + notifyCustomerCheckbox.prop('disabled', false); } else { - notifyCustomerCheckbox.disabled = true; - //creditmemoCommentText.disabled = true; + notifyCustomerCheckbox.prop('disabled', true); } } diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/invoice/create/items.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/invoice/create/items.phtml index 4a77c3b166de9..872be35cff206 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/invoice/create/items.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/invoice/create/items.phtml @@ -134,56 +134,61 @@ </section> <script> -require(['jquery', 'prototype'], function(jQuery){ +require(['jquery'], function(jQuery){ //<![CDATA[ -var submitButtons = $$('.submit-button'); -var updateButtons = $$('.update-button'); +var submitButtons = jQuery('.submit-button'); +var updateButtons = jQuery('.update-button'); var enableSubmitButtons = <?= (int) !$block->getDisableSubmitButton() ?>; -var fields = $$('.qty-input'); +var fields = jQuery('.qty-input'); -updateButtons.each(function (elem) {elem.disabled=true;elem.addClassName('disabled');}); +function enableButtons(buttons) { + buttons.removeClass('disabled').prop('disabled', false); +} -for(var i=0;i<fields.length;i++){ - jQuery(fields[i]).on('keyup', checkButtonsRelation); - fields[i].baseValue = fields[i].value; +function disableButtons(buttons) { + buttons.addClass('disabled').prop('disabled', true); } +disableButtons(updateButtons); + +fields.on('keyup', checkButtonsRelation); +fields.each(function (i, elem) { + elem.baseValue = elem.value; +}); + function checkButtonsRelation() { var hasChanges = false; - fields.each(function (elem) { + fields.each(function (i, elem) { if (elem.baseValue != elem.value) { hasChanges = true; } }.bind(this)); if (hasChanges) { - submitButtons.each(function (elem) {elem.disabled=true;elem.addClassName('disabled');}); - updateButtons.each(function (elem) {elem.disabled=false;elem.removeClassName('disabled');}); + disableButtons(submitButtons); + enableButtons(updateButtons); } else { if (enableSubmitButtons) { - submitButtons.each(function (elem) {elem.disabled=false;elem.removeClassName('disabled');}); + enableButtons(submitButtons); } - updateButtons.each(function (elem) {elem.disabled=true;elem.addClassName('disabled');}); + disableButtons(updateButtons); } } -var sendEmailCheckbox = $('send_email'); -if (sendEmailCheckbox) { - var notifyCustomerCheckbox = $('notify_customer'); - var invoiceCommentText = $('invoice_comment_text'); - Event.observe(sendEmailCheckbox, 'change', bindSendEmail); +var sendEmailCheckbox = jQuery('#send_email'); +if (sendEmailCheckbox.length) { + var notifyCustomerCheckbox = jQuery('#notify_customer'); + sendEmailCheckbox.on('change', bindSendEmail); bindSendEmail(); } function bindSendEmail() { - if (sendEmailCheckbox.checked == true) { - notifyCustomerCheckbox.disabled = false; - //invoiceCommentText.disabled = false; + if (sendEmailCheckbox.prop('checked') == true) { + notifyCustomerCheckbox.prop('disabled', false); } else { - notifyCustomerCheckbox.disabled = true; - //invoiceCommentText.disabled = true; + notifyCustomerCheckbox.prop('disabled', true); } } diff --git a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_creditmemo_grid.xml b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_creditmemo_grid.xml index f36a7d2821f7a..e0b7dae8fdb1a 100644 --- a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_creditmemo_grid.xml +++ b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_creditmemo_grid.xml @@ -92,7 +92,7 @@ <column name="order_increment_id"> <settings> <filter>text</filter> - <label translate="true">Order</label> + <label translate="true">Order #</label> </settings> </column> <column name="order_created_at" class="Magento\Ui\Component\Listing\Columns\Date" component="Magento_Ui/js/grid/columns/date"> diff --git a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_shipment_grid.xml b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_shipment_grid.xml index e0495e62d5ce1..9e02c31a20635 100644 --- a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_shipment_grid.xml +++ b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_shipment_grid.xml @@ -100,7 +100,7 @@ <column name="order_increment_id"> <settings> <filter>text</filter> - <label translate="true">Order</label> + <label translate="true">Order #</label> </settings> </column> <column name="order_created_at" class="Magento\Ui\Component\Listing\Columns\Date" component="Magento_Ui/js/grid/columns/date"> diff --git a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_creditmemo_grid.xml b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_creditmemo_grid.xml index 10b7b1c028c66..cf536c27a0ac3 100644 --- a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_creditmemo_grid.xml +++ b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_creditmemo_grid.xml @@ -105,7 +105,7 @@ <column name="order_increment_id"> <settings> <filter>text</filter> - <label translate="true">Order</label> + <label translate="true">Order #</label> </settings> </column> <column name="order_created_at" class="Magento\Ui\Component\Listing\Columns\Date" component="Magento_Ui/js/grid/columns/date"> diff --git a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_shipment_grid.xml b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_shipment_grid.xml index 6db77a79b8c14..5f8ebde290664 100644 --- a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_shipment_grid.xml +++ b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_shipment_grid.xml @@ -106,7 +106,7 @@ <column name="order_increment_id"> <settings> <filter>textRange</filter> - <label translate="true">Order</label> + <label translate="true">Order #</label> </settings> </column> <column name="order_created_at" class="Magento\Ui\Component\Listing\Columns\Date" component="Magento_Ui/js/grid/columns/date"> diff --git a/app/code/Magento/Sales/view/frontend/templates/email/items/creditmemo/default.phtml b/app/code/Magento/Sales/view/frontend/templates/email/items/creditmemo/default.phtml index 1fca65932b0b0..20c2c1869fedb 100644 --- a/app/code/Magento/Sales/view/frontend/templates/email/items/creditmemo/default.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/email/items/creditmemo/default.phtml @@ -31,6 +31,6 @@ </td> <td class="item-qty"><?= /* @escapeNotVerified */ $_item->getQty() * 1 ?></td> <td class="item-price"> - <?= /* @escapeNotVerified */ $block->getItemPrice($_item->getOrderItem()) ?> + <?= /* @escapeNotVerified */ $block->getItemPrice($_item) ?> </td> </tr> diff --git a/app/code/Magento/SalesGraphQl/etc/schema.graphqls b/app/code/Magento/SalesGraphQl/etc/schema.graphqls index 44f106532858f..06146f805c644 100644 --- a/app/code/Magento/SalesGraphQl/etc/schema.graphqls +++ b/app/code/Magento/SalesGraphQl/etc/schema.graphqls @@ -2,7 +2,7 @@ # See COPYING.txt for license details. type Query { - customerOrders: CustomerOrders @resolver(class: "Magento\\SalesGraphQl\\Model\\Resolver\\Orders") @doc(description: "List of customer orders") + customerOrders: CustomerOrders @resolver(class: "Magento\\SalesGraphQl\\Model\\Resolver\\Orders") @doc(description: "List of customer orders") @cache(cacheable: false) } type CustomerOrder @doc(description: "Order mapping fields") { diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminOpenNewCartPriceRuleFormPageActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminOpenNewCartPriceRuleFormPageActionGroup.xml new file mode 100644 index 0000000000000..8cb958c9d9304 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminOpenNewCartPriceRuleFormPageActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenNewCartPriceRuleFormPageActionGroup"> + <amOnPage url="{{PriceRuleNewPage.url}}" stepKey="openNewCartPriceRulePage" /> + <waitForPageLoad stepKey="waitForPageLoad" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AssertCustomerGroupNotOnCartPriceRuleFormActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AssertCustomerGroupNotOnCartPriceRuleFormActionGroup.xml new file mode 100644 index 0000000000000..98a094aea77ac --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AssertCustomerGroupNotOnCartPriceRuleFormActionGroup.xml @@ -0,0 +1,20 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCustomerGroupNotOnCartPriceRuleFormActionGroup"> + <arguments> + <argument name="customerGroup" type="entity" /> + </arguments> + <grabMultiple selector="{{AdminCartPriceRulesFormSection.customerGroupsOptions}}" stepKey="customerGroups" /> + <assertNotContains stepKey="assertCustomerGroupNotInOptions"> + <actualResult type="variable">customerGroups</actualResult> + <expectedResult type="string">{{customerGroup.code}}</expectedResult> + </assertNotContains> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..5a42980df1bbf --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,16 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuMarketingPromotionsCartPriceRules"> + <data key="pageTitle">Cart Price Rules</data> + <data key="title">Cart Price Rules</data> + <data key="dataUiId">magento-salesrule-promo-quote</data> + </entity> +</entities> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml index c8da82407457d..8af28a7fcb33e 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml @@ -18,6 +18,7 @@ <element name="ruleName" type="input" selector="input[name='name']"/> <element name="websites" type="multiselect" selector="select[name='website_ids']"/> <element name="customerGroups" type="multiselect" selector="select[name='customer_group_ids']"/> + <element name="customerGroupsOptions" type="multiselect" selector="select[name='customer_group_ids'] option"/> <element name="coupon" type="select" selector="select[name='coupon_type']"/> <element name="couponCode" type="input" selector="input[name='coupon_code']"/> <element name="useAutoGeneration" type="checkbox" selector="input[name='use_auto_generation']"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminMarketingCartPriceRulesNavigateMenuTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminMarketingCartPriceRulesNavigateMenuTest.xml new file mode 100644 index 0000000000000..f281b0abf87a0 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminMarketingCartPriceRulesNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminMarketingCartPriceRulesNavigateMenuTest"> + <annotations> + <features value="SalesRule"/> + <stories value="Menu Navigation"/> + <title value="Admin marketing cart price rules navigate menu test"/> + <description value="Admin should be able to navigate to Marketing > Cart Price Rules"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14143"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToMarketingCartPriceRulesPage"> + <argument name="menuUiId" value="{{AdminMenuMarketing.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuMarketingPromotionsCartPriceRules.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuMarketingPromotionsCartPriceRules.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/DeleteCustomerGroupTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/DeleteCustomerGroupTest.xml new file mode 100644 index 0000000000000..77ee270b3bcaa --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/DeleteCustomerGroupTest.xml @@ -0,0 +1,17 @@ +<?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="DeleteCustomerGroupTest"> + <actionGroup ref="AdminOpenNewCartPriceRuleFormPageActionGroup" stepKey="openNewCartPriceRuleForm" /> + <actionGroup ref="AssertCustomerGroupNotOnCartPriceRuleFormActionGroup" stepKey="assertCustomerGroupNotOnCartPriceRuleForm"> + <argument name="customerGroup" value="$$customerGroup$$" /> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/SalesRule/view/adminhtml/layout/sales_rule_promo_quote_index.xml b/app/code/Magento/SalesRule/view/adminhtml/layout/sales_rule_promo_quote_index.xml index 21ce8bfc3eaac..7796f280efcbc 100644 --- a/app/code/Magento/SalesRule/view/adminhtml/layout/sales_rule_promo_quote_index.xml +++ b/app/code/Magento/SalesRule/view/adminhtml/layout/sales_rule_promo_quote_index.xml @@ -12,7 +12,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.block.promo.quote.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">promo_quote_grid</argument> - <argument name="dataSource" xsi:type="object">Magento\SalesRule\Model\ResourceModel\Rule\Quote\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\SalesRule\Model\ResourceModel\Rule\Quote\Collection</argument> <argument name="default_sort" xsi:type="string">sort_order</argument> <argument name="default_dir" xsi:type="string">ASC</argument> <argument name="save_parameters_in_session" xsi:type="string">1</argument> diff --git a/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.xml b/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.xml index 81b025c9554e2..0bb929d889351 100644 --- a/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.xml +++ b/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.xml @@ -11,10 +11,15 @@ <section name="StorefrontQuickSearchResultsSection"> <element name="searchTextBox" type="text" selector="#search"/> <element name="searchTextBoxButton" type="button" selector="button[class='action search']"/> + <element name="allResults" type="block" selector=".column.main"/> <element name="productLink" type="select" selector="a[class='product-item-link']"/> + <element name="productByIndex" type="button" selector=".product-items li:nth-child({{var}}) .product-item-info" parameterized="true"/> + <element name="productByName" type="button" selector="//div[contains(@class, 'product-item-info') and .//*[contains(., '{{var}}')]]" parameterized="true"/> + <element name="addToCartBtn" type="button" selector="//button[contains(@class, 'tocart')]"/> + <element name="messageSection" type="text" selector="div .message"/> + <element name="productSpecialPrice" type="text" selector="//a[contains(text(), '{{productName}}')]/ancestor::div//span[contains(@data-price-type, 'finalPrice')]/span[contains(@class, 'price')]" parameterized="true"/> <element name="asLowAsLabel" type="text" selector=".minimal-price-link > span"/> <element name="textArea" type="text" selector="li[class='item']"/> <element name="regularPrice" type="text" selector="//span[@class='price-wrapper ']/span[@class='price']"/> - <element name="messageSection" type="text" selector="div .message"/> </section> </sections> diff --git a/app/code/Magento/Search/view/adminhtml/layout/search_term_block.xml b/app/code/Magento/Search/view/adminhtml/layout/search_term_block.xml index cc2e1509e4412..9dc60bde11315 100644 --- a/app/code/Magento/Search/view/adminhtml/layout/search_term_block.xml +++ b/app/code/Magento/Search/view/adminhtml/layout/search_term_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.report.search.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">searchReportGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\Search\Model\ResourceModel\Query\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Search\Model\ResourceModel\Query\Collection</argument> <argument name="default_sort" xsi:type="string">query_id</argument> <argument name="default_dir" xsi:type="string">DESC</argument> </arguments> diff --git a/app/code/Magento/Search/view/adminhtml/layout/search_term_grid_block.xml b/app/code/Magento/Search/view/adminhtml/layout/search_term_grid_block.xml index 7330ac1f92712..38c6fa52455b9 100644 --- a/app/code/Magento/Search/view/adminhtml/layout/search_term_grid_block.xml +++ b/app/code/Magento/Search/view/adminhtml/layout/search_term_grid_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.catalog.search.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">search_term_grid</argument> - <argument name="dataSource" xsi:type="object">Magento\Search\Model\ResourceModel\Query\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Search\Model\ResourceModel\Query\Collection</argument> <argument name="default_sort" xsi:type="string">name</argument> <argument name="default_dir" xsi:type="string">ASC</argument> <argument name="save_parameters_in_session" xsi:type="string">1</argument> diff --git a/app/code/Magento/Search/view/adminhtml/layout/search_term_report_block.xml b/app/code/Magento/Search/view/adminhtml/layout/search_term_report_block.xml index b6b1455cf27ca..f4c5d1ab85a0a 100644 --- a/app/code/Magento/Search/view/adminhtml/layout/search_term_report_block.xml +++ b/app/code/Magento/Search/view/adminhtml/layout/search_term_report_block.xml @@ -12,7 +12,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.report.search.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">searchReportGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\Search\Model\ResourceModel\Query\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Search\Model\ResourceModel\Query\Collection</argument> <argument name="default_sort" xsi:type="string">query_id</argument> <argument name="default_dir" xsi:type="string">DESC</argument> </arguments> diff --git a/app/code/Magento/Search/view/frontend/templates/form.mini.phtml b/app/code/Magento/Search/view/frontend/templates/form.mini.phtml index 2ea87be13d5e3..44c8db3f1a663 100644 --- a/app/code/Magento/Search/view/frontend/templates/form.mini.phtml +++ b/app/code/Magento/Search/view/frontend/templates/form.mini.phtml @@ -42,8 +42,10 @@ $helper = $this->helper(\Magento\Search\Helper\Data::class); </div> <div class="actions"> <button type="submit" - title="<?= $block->escapeHtml(__('Search')) ?>" - class="action search"> + title="<?= $block->escapeHtml(__('Search')) ?>" + class="action search" + aria-label="Search" + > <span><?= /* @escapeNotVerified */ __('Search') ?></span> </button> </div> diff --git a/app/code/Magento/Search/view/frontend/web/js/form-mini.js b/app/code/Magento/Search/view/frontend/web/js/form-mini.js index 15bcf2e73393e..5331ba3b447ac 100644 --- a/app/code/Magento/Search/view/frontend/web/js/form-mini.js +++ b/app/code/Magento/Search/view/frontend/web/js/form-mini.js @@ -72,7 +72,6 @@ define([ }.bind(this), exit: function () { this.isExpandable = false; - this.element.removeAttr('aria-expanded'); }.bind(this) }); diff --git a/app/code/Magento/Security/Test/Mftf/Test/AdminUserLockWhenCreatingNewUserTest.xml b/app/code/Magento/Security/Test/Mftf/Test/AdminUserLockWhenCreatingNewUserTest.xml new file mode 100644 index 0000000000000..4ceffd676313d --- /dev/null +++ b/app/code/Magento/Security/Test/Mftf/Test/AdminUserLockWhenCreatingNewUserTest.xml @@ -0,0 +1,79 @@ +<?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="AdminUserLockWhenCreatingNewUserTest"> + <annotations> + <features value="Security"/> + <stories value="Runs Lock admin user when creating new user test."/> + <title value="Lock admin user when creating new user"/> + <description value="Runs Lock admin user when creating new user test."/> + <testCaseId value="MC-14383" /> + <severity value="CRITICAL"/> + <group value="security"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Log in to Admin Panel --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <!-- Unlock Admin user --> + <magentoCLI command="admin:user:unlock {{DefaultAdminUser.username}}" stepKey="unlockAdminUser"/> + </after> + + <!-- Open Admin New User Page --> + <actionGroup ref="AdminOpenNewUserPageActionGroup" stepKey="openNewUserPage" /> + + <!-- Perform add new admin user 6 specified number of times. + "The password entered for the current user is invalid. Verify the password and try again." appears after each attempt.--> + <actionGroup ref="AdminFillNewUserFormRequiredFieldsActionGroup" stepKey="failedSaveUserFirstAttempt"> + <argument name="user" value="NewAdminUserWrongCurrentPassword" /> + </actionGroup> + <actionGroup ref="AdminClickSaveButtonOnUserFormActionGroup" stepKey="clickSaveFirstAttempt" /> + <actionGroup ref="AssertAdminUserSaveMessageActionGroup" stepKey="seeInvalidPasswordError"> + <argument name="message" value="The password entered for the current user is invalid. Verify the password and try again." /> + <argument name="messageType" value="error" /> + </actionGroup> + + <actionGroup ref="AdminFillNewUserFormRequiredFieldsActionGroup" stepKey="failedSaveUserSecondAttempt"> + <argument name="user" value="NewAdminUserWrongCurrentPassword" /> + </actionGroup> + <actionGroup ref="AdminClickSaveButtonOnUserFormActionGroup" stepKey="clickSaveSecondAttempt" /> + + <actionGroup ref="AdminFillNewUserFormRequiredFieldsActionGroup" stepKey="failedSaveUserThirdAttempt"> + <argument name="user" value="NewAdminUserWrongCurrentPassword" /> + </actionGroup> + <actionGroup ref="AdminClickSaveButtonOnUserFormActionGroup" stepKey="clickSaveThirdAttempt" /> + + <actionGroup ref="AdminFillNewUserFormRequiredFieldsActionGroup" stepKey="failedSaveUserFourthAttempt"> + <argument name="user" value="NewAdminUserWrongCurrentPassword" /> + </actionGroup> + <actionGroup ref="AdminClickSaveButtonOnUserFormActionGroup" stepKey="clickSaveFourthAttempt" /> + + <actionGroup ref="AdminFillNewUserFormRequiredFieldsActionGroup" stepKey="failedSaveUserFifthAttempt"> + <argument name="user" value="NewAdminUserWrongCurrentPassword" /> + </actionGroup> + <actionGroup ref="AdminClickSaveButtonOnUserFormActionGroup" stepKey="clickSaveFifthAttempt" /> + + <actionGroup ref="AdminFillNewUserFormRequiredFieldsActionGroup" stepKey="failedSaveUserSixthAttempt"> + <argument name="user" value="NewAdminUserWrongCurrentPassword" /> + </actionGroup> + <actionGroup ref="AdminClickSaveButtonOnUserFormActionGroup" stepKey="clickSaveSixthAttempt" /> + + <!-- Check Error that account has been locked --> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="seeLockUserErrorMessage"> + <argument name="message" value="Your account is temporarily disabled. Please try again later." /> + </actionGroup> + + <!-- Try to login as admin and check error --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsLockedAdmin"/> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="seeLoginUserErrorMessage" /> + </test> +</tests> diff --git a/app/code/Magento/Sitemap/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Sitemap/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..c8fd6a751cb22 --- /dev/null +++ b/app/code/Magento/Sitemap/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,16 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuSEOAndSearchSiteMap"> + <data key="pageTitle">Site Map</data> + <data key="title">Site Map</data> + <data key="dataUiId">magento-sitemap-catalog-sitemap</data> + </entity> +</entities> diff --git a/app/code/Magento/Sitemap/Test/Mftf/Test/AdminMarketingSiteMapNavigateMenuTest.xml b/app/code/Magento/Sitemap/Test/Mftf/Test/AdminMarketingSiteMapNavigateMenuTest.xml new file mode 100644 index 0000000000000..54543fab8649d --- /dev/null +++ b/app/code/Magento/Sitemap/Test/Mftf/Test/AdminMarketingSiteMapNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminMarketingSiteMapNavigateMenuTest"> + <annotations> + <features value="Sitemap"/> + <stories value="Menu Navigation"/> + <title value="Admin marketing site map navigate menu test"/> + <description value="Admin should be able to navigate to Marketing > Site Map"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14204"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToMarketingSiteMapPage"> + <argument name="menuUiId" value="{{AdminMenuMarketing.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSEOAndSearchSiteMap.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSEOAndSearchSiteMap.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Sitemap/view/adminhtml/layout/adminhtml_sitemap_index_grid_block.xml b/app/code/Magento/Sitemap/view/adminhtml/layout/adminhtml_sitemap_index_grid_block.xml index cdaa6575d559c..632768e4a5f08 100644 --- a/app/code/Magento/Sitemap/view/adminhtml/layout/adminhtml_sitemap_index_grid_block.xml +++ b/app/code/Magento/Sitemap/view/adminhtml/layout/adminhtml_sitemap_index_grid_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.sitemap.container.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">sitemapGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\Sitemap\Model\ResourceModel\Sitemap\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Sitemap\Model\ResourceModel\Sitemap\Collection</argument> <argument name="default_sort" xsi:type="string">sitemap_id</argument> </arguments> <block class="Magento\Backend\Block\Widget\Grid\ColumnSet" name="adminhtml.sitemap.container.grid.columnSet" as="grid.columnSet"> diff --git a/app/code/Magento/Store/Model/Group.php b/app/code/Magento/Store/Model/Group.php index 19f104c9f3790..4cd7bc93d3334 100644 --- a/app/code/Magento/Store/Model/Group.php +++ b/app/code/Magento/Store/Model/Group.php @@ -101,7 +101,7 @@ class Group extends \Magento\Framework\Model\AbstractExtensibleModel implements private $eventManager; /** - * @var \Magento\MessageQueue\Api\PoisonPillPutInterface + * @var \Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface */ private $pillPut; @@ -117,7 +117,7 @@ class Group extends \Magento\Framework\Model\AbstractExtensibleModel implements * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection * @param array $data * @param \Magento\Framework\Event\ManagerInterface|null $eventManager - * @param \Magento\MessageQueue\Api\PoisonPillPutInterface|null $pillPut + * @param \Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface|null $pillPut * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -132,7 +132,7 @@ public function __construct( \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], \Magento\Framework\Event\ManagerInterface $eventManager = null, - \Magento\MessageQueue\Api\PoisonPillPutInterface $pillPut = null + \Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface $pillPut = null ) { $this->_configDataResource = $configDataResource; $this->_storeListFactory = $storeListFactory; @@ -140,7 +140,7 @@ public function __construct( $this->eventManager = $eventManager ?: \Magento\Framework\App\ObjectManager::getInstance() ->get(\Magento\Framework\Event\ManagerInterface::class); $this->pillPut = $pillPut ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\MessageQueue\Api\PoisonPillPutInterface::class); + ->get(\Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface::class); parent::__construct( $context, $registry, diff --git a/app/code/Magento/Store/Model/Store.php b/app/code/Magento/Store/Model/Store.php index b2a515b198b11..f62762986cb32 100644 --- a/app/code/Magento/Store/Model/Store.php +++ b/app/code/Magento/Store/Model/Store.php @@ -327,7 +327,7 @@ class Store extends AbstractExtensibleModel implements private $eventManager; /** - * @var \Magento\MessageQueue\Api\PoisonPillPutInterface + * @var \Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface */ private $pillPut; @@ -357,7 +357,7 @@ class Store extends AbstractExtensibleModel implements * @param bool $isCustomEntryPoint * @param array $data optional generic object data * @param \Magento\Framework\Event\ManagerInterface|null $eventManager - * @param \Magento\MessageQueue\Api\PoisonPillPutInterface|null $pillPut + * @param \Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface|null $pillPut * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -387,7 +387,7 @@ public function __construct( $isCustomEntryPoint = false, array $data = [], \Magento\Framework\Event\ManagerInterface $eventManager = null, - \Magento\MessageQueue\Api\PoisonPillPutInterface $pillPut = null + \Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface $pillPut = null ) { $this->_coreFileStorageDatabase = $coreFileStorageDatabase; $this->_config = $config; @@ -409,7 +409,7 @@ public function __construct( $this->eventManager = $eventManager ?: \Magento\Framework\App\ObjectManager::getInstance() ->get(\Magento\Framework\Event\ManagerInterface::class); $this->pillPut = $pillPut ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\MessageQueue\Api\PoisonPillPutInterface::class); + ->get(\Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface::class); parent::__construct( $context, $registry, @@ -423,9 +423,14 @@ public function __construct( /** * @inheritdoc + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $properties = parent::__sleep(); $properties = array_diff($properties, ['_coreFileStorageDatabase', '_config']); return $properties; @@ -435,9 +440,14 @@ public function __sleep() * Init not serializable fields * * @return void + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + parent::__wakeup(); $this->_coreFileStorageDatabase = ObjectManager::getInstance() ->get(\Magento\MediaStorage\Helper\File\Storage\Database::class); @@ -716,7 +726,8 @@ protected function _updatePathUseRewrites($url) if ($this->_isCustomEntryPoint()) { $indexFileName = 'index.php'; } else { - $indexFileName = basename($_SERVER['SCRIPT_FILENAME']); + $scriptFilename = $this->_request->getServer('SCRIPT_FILENAME'); + $indexFileName = basename($scriptFilename); } $url .= $indexFileName . '/'; } diff --git a/app/code/Magento/Store/Model/Website.php b/app/code/Magento/Store/Model/Website.php index 383b36fd63228..42c89bbe4210d 100644 --- a/app/code/Magento/Store/Model/Website.php +++ b/app/code/Magento/Store/Model/Website.php @@ -160,7 +160,7 @@ class Website extends \Magento\Framework\Model\AbstractExtensibleModel implement protected $_currencyFactory; /** - * @var \Magento\MessageQueue\Api\PoisonPillPutInterface + * @var \Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface */ private $pillPut; @@ -179,7 +179,7 @@ class Website extends \Magento\Framework\Model\AbstractExtensibleModel implement * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data - * @param \Magento\MessageQueue\Api\PoisonPillPutInterface|null $pillPut + * @param \Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface|null $pillPut * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -197,7 +197,7 @@ public function __construct( \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], - \Magento\MessageQueue\Api\PoisonPillPutInterface $pillPut = null + \Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface $pillPut = null ) { parent::__construct( $context, @@ -216,7 +216,7 @@ public function __construct( $this->_storeManager = $storeManager; $this->_currencyFactory = $currencyFactory; $this->pillPut = $pillPut ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\MessageQueue\Api\PoisonPillPutInterface::class); + ->get(\Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface::class); } /** diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml index 7f1a63d3db6f2..870b92d17e679 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml @@ -42,4 +42,30 @@ <waitForElementVisible selector="{{AdminStoresGridSection.storeGrpFilterTextField}}" stepKey="waitForStoreGridReload"/> <see userInput="You saved the store." stepKey="seeSavedMessage"/> </actionGroup> + <actionGroup name="AssertStoreGroupInGrid"> + <arguments> + <argument name="storeGroupName" type="string"/> + </arguments> + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="amOnAdminSystemStorePage"/> + <waitForPageLoad stepKey="waitForAdminSystemStorePageLoad"/> + <click selector="{{AdminStoresGridSection.resetButton}}" stepKey="resetSearchFilter"/> + <fillField userInput="{{storeGroupName}}" selector="{{AdminStoresGridSection.storeGrpFilterTextField}}" stepKey="fillSearchStoreGroupField"/> + <click selector="{{AdminStoresGridSection.searchButton}}" stepKey="clickSearchButton"/> + <waitForPageLoad stepKey="waitForStoreToLoad"/> + <see selector="{{AdminStoresGridSection.nthRow('1')}}" userInput="{{storeGroupName}}" stepKey="seeAssertStoreGroupInGridMessage"/> + </actionGroup> + <actionGroup name="AssertStoreGroupForm"> + <arguments> + <argument name="website" type="string"/> + <argument name="storeGroupName" type="string"/> + <argument name="storeGroupCode" type="string"/> + <argument name="rootCategory" type="string"/> + </arguments> + <click selector="{{AdminStoresGridSection.storeGrpNameInFirstRow}}" stepKey="clickEditExistingStoreRow"/> + <waitForPageLoad stepKey="waitTillAdminSystemStoreGroupPage"/> + <seeInField selector="{{AdminNewStoreGroupSection.storeGrpWebsiteDropdown}}" userInput="{{website}}" stepKey="seeAssertWebsite"/> + <seeInField selector="{{AdminNewStoreGroupSection.storeGrpNameTextField}}" userInput="{{storeGroupName}}" stepKey="seeAssertStoreGroupName"/> + <seeInField selector="{{AdminNewStoreGroupSection.storeGrpCodeTextField}}" userInput="{{storeGroupCode}}" stepKey="seeAssertStoreGroupCode"/> + <seeInField selector="{{AdminNewStoreGroupSection.storeRootCategoryDropdown}}" userInput="{{rootCategory}}" stepKey="seeAssertRootCategory"/> + </actionGroup> </actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreViewActionGroup.xml index 9b942109785d4..fda92810a13be 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreViewActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreViewActionGroup.xml @@ -8,6 +8,7 @@ <!-- Test XML Example --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminCreateStoreViewActionGroup"> <arguments> <argument name="StoreGroup" defaultValue="_defaultStoreGroup"/> @@ -25,20 +26,22 @@ <waitForElementVisible selector="{{AdminConfirmationModalSection.ok}}" stepKey="waitForModal" /> <see selector="{{AdminConfirmationModalSection.title}}" userInput="Warning message" stepKey="seeWarningAboutTakingALongTimeToComplete" /> <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmModal" /> - <waitForPageLoad stepKey="waitForStorePageLoad" /> - <waitForElementNotVisible selector="{{AdminNewStoreViewActionsSection.loadingMask}}" stepKey="waitForElementVisible"/> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitForPageReload"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You saved the store view." stepKey="seeSavedMessage" /> </actionGroup> + + <actionGroup name="AdminCreateStoreViewWithoutCheckActionGroup" extends="AdminCreateStoreViewActionGroup"> + <remove keyForRemoval="waitForPageReload"/> + <remove keyForRemoval="seeSavedMessage"/> + </actionGroup> + <!--Save the Store view--> <actionGroup name="AdminCreateStoreViewActionSaveGroup"> <waitForLoadingMaskToDisappear stepKey="waitForGridLoad"/> <waitForElementVisible selector="{{AdminStoresGridSection.websiteFilterTextField}}" stepKey="waitForStoreGridToReload2"/> <see userInput="You saved the store view." stepKey="seeSavedMessage" /> </actionGroup> - <!--Save the same Store view code for code validation--> - <actionGroup name="AdminCreateStoreViewCodeUniquenessActionGroup"> - <waitForLoadingMaskToDisappear stepKey="waitForForm"/> - <see userInput="Store with the same code already exists." stepKey="seeMessage" /> - </actionGroup> + <actionGroup name="navigateToAdminContentManagementPage"> <amOnPage url="{{AdminContentManagementPage.url}}" stepKey="navigateToConfigurationPage"/> <waitForPageLoad stepKey="waitForPageLoad1"/> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchStoreViewActionGroup.xml index ac8e9d717fdca..47236376f0209 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchStoreViewActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchStoreViewActionGroup.xml @@ -18,6 +18,7 @@ <waitForElementVisible selector="{{AdminConfirmationModalSection.ok}}" stepKey="waitingForInformationModal"/> <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmStoreSwitch"/> <waitForPageLoad stepKey="waitForStoreViewSwitched"/> + <scrollToTopOfPage stepKey="scrollToStoreSwitcher"/> <see userInput="{{storeView}}" selector="{{AdminMainActionsSection.storeSwitcher}}" stepKey="seeNewStoreViewName"/> </actionGroup> <actionGroup name="AdminSwitchToAllStoreViewActionGroup" extends="AdminSwitchStoreViewActionGroup"> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreConfigurationBackendActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreConfigurationBackendActionGroup.xml new file mode 100644 index 0000000000000..38030f59a7a33 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreConfigurationBackendActionGroup.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<!-- Test XML Example --> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStoreConfigurationBackendActionGroup"> + <arguments> + <argument name="website" type="string"/> + <argument name="customStore" type="string"/> + <argument name="storeView1" type="string"/> + <argument name="storeView2" type="string"/> + </arguments> + <amOnPage url="{{AdminConfigPage.url}}" stepKey="goToConfigStoreConfigurationPage"/> + <waitForPageLoad stepKey="waitForSystemStoreConfigurationPageLoad"/> + <click selector="{{AdminConfigSection.defaultConfigButton}}" stepKey="clickDefaultConfigButton"/> + <see selector="{{AdminConfigSection.defaultConfigDropdown}}" userInput="{{website}}" stepKey="seeAssertWebsiteInDefaultConfigDropdown"/> + <see selector="{{AdminConfigSection.defaultConfigDropdown}}" userInput="{{customStore}}" stepKey="seeAssertSecondStoreInDefaultConfigDropdown"/> + <see selector="{{AdminConfigSection.defaultConfigDropdown}}" userInput="{{storeView1}}" stepKey="seeAssertFirstStoreViewInDefaultConfigDropdown"/> + <see selector="{{AdminConfigSection.defaultConfigDropdown}}" userInput="{{storeView2}}" stepKey="seeAssertSecondStoreViewInDefaultConfigDropdown"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreFrontendActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreFrontendActionGroup.xml new file mode 100644 index 0000000000000..afd64e33e8b36 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreFrontendActionGroup.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<!-- Test XML Example --> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStoreFrontendActionGroup"> + <arguments> + <argument name="customStore" type="string"/> + <argument name="storeView1" type="string"/> + <argument name="storeView2" type="string"/> + </arguments> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStorefrontPage"/> + <waitForPageLoad stepKey="waitForStorefrontHomePageLoad"/> + <click selector="{{StorefrontFooterSection.switchStoreButton}}" stepKey="clickSwitchStoreButton"/> + <waitForElementVisible selector="{{StorefrontFooterSection.storeLink(customStore)}}" stepKey="waitForStoreLinkToVosible"/> + <click selector="{{StorefrontFooterSection.storeLink(customStore)}}" stepKey="clickStoreLinkCustomStore"/> + <waitForPageLoad stepKey="waitForCustomStoreToLoad"/> + <see selector="{{StorefrontHeaderSection.storeViewName}}" userInput="{{storeView1}}" stepKey="seeAssertFirstStoreViewOnStorefront"/> + <click selector="{{StorefrontHeaderSection.storeViewName}}" stepKey="clickStoreViewName"/> + <see selector="{{StorefrontHeaderSection.storeViewDropdown}}" userInput="{{storeView2}}" stepKey="seeAssertSecondStoreViewOnStorefront"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreGroupFormActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreGroupFormActionGroup.xml new file mode 100644 index 0000000000000..a8b8b7bb6d07f --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreGroupFormActionGroup.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<!-- Admin creates new Store group --> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStoreGroupFormActionGroup"> + <arguments> + <argument name="website" type="string"/> + <argument name="storeGroupName" type="string"/> + <argument name="storeGroupCode" type="string"/> + <argument name="rootCategory" type="string"/> + </arguments> + <click selector="{{AdminStoresGridSection.storeGrpNameInFirstRow}}" stepKey="clickEditExistingStoreRow"/> + <waitForPageLoad stepKey="waitTillAdminSystemStoreGroupPage"/> + <seeInField selector="{{AdminNewStoreGroupSection.storeGrpWebsiteDropdown}}" userInput="{{website}}" stepKey="seeAssertWebsite"/> + <seeInField selector="{{AdminNewStoreGroupSection.storeGrpNameTextField}}" userInput="{{storeGroupName}}" stepKey="seeAssertStoreGroupName"/> + <seeInField selector="{{AdminNewStoreGroupSection.storeGrpCodeTextField}}" userInput="{{storeGroupCode}}" stepKey="seeAssertStoreGroupCode"/> + <seeInField selector="{{AdminNewStoreGroupSection.storeRootCategoryDropdown}}" userInput="{{rootCategory}}" stepKey="seeAssertRootCategory"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreGroupInGridActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreGroupInGridActionGroup.xml new file mode 100644 index 0000000000000..fb36cbd0d1812 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreGroupInGridActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<!-- Admin creates new Store group --> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStoreGroupInGridActionGroup"> + <arguments> + <argument name="storeGroupName" type="string"/> + </arguments> + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="amOnAdminSystemStorePage"/> + <waitForPageLoad stepKey="waitForAdminSystemStorePageLoad"/> + <click selector="{{AdminStoresGridSection.resetButton}}" stepKey="resetSearchFilter"/> + <fillField userInput="{{storeGroupName}}" selector="{{AdminStoresGridSection.storeGrpFilterTextField}}" stepKey="fillSearchStoreGroupField"/> + <click selector="{{AdminStoresGridSection.searchButton}}" stepKey="clickSearchButton"/> + <waitForPageLoad stepKey="waitForStoreToLoad"/> + <see selector="{{AdminStoresGridSection.firstRow}}" userInput="{{storeGroupName}}" stepKey="seeAssertStoreGroupInGridMessage"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreViewFormActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreViewFormActionGroup.xml new file mode 100644 index 0000000000000..92ebbc3950131 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreViewFormActionGroup.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<!-- Test XML Example --> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStoreViewFormActionGroup"> + <arguments> + <argument name="storeDropdown" type="string"/> + <argument name="storeViewName" type="string"/> + <argument name="storeViewCode" type="string"/> + <argument name="status" type="string"/> + </arguments> + <click selector="{{AdminStoresGridSection.storeNameInFirstRow}}" stepKey="clickStoreViewFirstRowInGrid"/> + <waitForPageLoad stepKey="waitForAdminSystemStoreViewPageLoad"/> + <seeInField selector="{{AdminNewStoreSection.storeGrpDropdown}}" userInput="{{storeDropdown}}" stepKey="seeAssertStore"/> + <seeInField selector="{{AdminNewStoreSection.storeNameTextField}}" userInput="{{storeViewName}}" stepKey="seeAssertStoreViewName"/> + <seeInField selector="{{AdminNewStoreSection.storeCodeTextField}}" userInput="{{storeViewCode}}" stepKey="seeAssertStoreViewCode"/> + <seeInField selector="{{AdminNewStoreSection.statusDropdown}}" userInput="{{status}}" stepKey="seeAssertStatus"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreViewInGridActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreViewInGridActionGroup.xml new file mode 100644 index 0000000000000..fad8eec9990cc --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreViewInGridActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<!-- Test XML Example --> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStoreViewInGridActionGroup"> + <arguments> + <argument name="storeViewName" type="string"/> + </arguments> + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="amOnAdminSystemStorePage"/> + <waitForPageLoad stepKey="waitForAdminSystemStorePageLoad"/> + <click selector="{{AdminStoresGridSection.resetButton}}" stepKey="resetSearchFilter"/> + <fillField userInput="{{storeViewName}}" selector="{{AdminStoresGridSection.storeFilterTextField}}" stepKey="fillSearchStoreViewField"/> + <click selector="{{AdminStoresGridSection.searchButton}}" stepKey="clickSearchButton"/> + <waitForPageLoad stepKey="waitForStoreToLoad"/> + <see selector="{{AdminStoresGridSection.firstRow}}" userInput="{{storeViewName}}" stepKey="seeAssertStoreViewInGridMessage"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStorefrontStoreCodeInUrlActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStorefrontStoreCodeInUrlActionGroup.xml new file mode 100644 index 0000000000000..3daa1c25a1737 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStorefrontStoreCodeInUrlActionGroup.xml @@ -0,0 +1,16 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontStoreCodeInUrlActionGroup"> + <arguments> + <argument name="storeCode" type="string" defaultValue="{{_defaultStore.code}}" /> + </arguments> + <seeInCurrentUrl url="{{StorefrontHomePage.url}}{{storeCode}}" stepKey="seeStoreCodeInURL"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/ChangeStoreInStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/ChangeStoreInStoreViewActionGroup.xml new file mode 100644 index 0000000000000..15784235d5918 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/ChangeStoreInStoreViewActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<!-- Test XML Example --> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="ChangeStoreInStoreViewActionGroup"> + <arguments> + <argument name="storeDropdown" type="string"/> + </arguments> + <click selector="{{AdminStoresGridSection.storeNameInFirstRow}}" stepKey="clickStoreViewFirstRowInGrid"/> + <waitForPageLoad stepKey="waitForAdminSystemStoreViewPageLoad"/> + <selectOption selector="{{AdminNewStoreSection.storeGrpDropdown}}" userInput="{{storeDropdown}}" stepKey="selectStoreGrpDropdown"/> + <click selector="{{AdminNewStoreViewActionsSection.saveButton}}" stepKey="clickSaveButton"/> + <waitForElementVisible selector="{{AdminConfirmationModalSection.ok}}" stepKey="waitForModal"/> + <see selector="{{AdminConfirmationModalSection.title}}" userInput="Warning message" stepKey="seeWarningAboutTakingALongTimeToComplete"/> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmModal"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/EditCustomStoreGroupAcceptWarningMessageActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/EditCustomStoreGroupAcceptWarningMessageActionGroup.xml new file mode 100644 index 0000000000000..8889795c8acbf --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/EditCustomStoreGroupAcceptWarningMessageActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<!-- Admin creates new Store group --> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="EditCustomStoreGroupAcceptWarningMessageActionGroup" extends="CreateCustomStore"> + <arguments> + <argument name="website" type="string"/> + <argument name="store" type="string"/> + <argument name="rootCategory" type="string"/> + </arguments> + <remove keyForRemoval="selectCreateStore"/> + <click selector="{{AdminStoresGridSection.storeGrpNameInFirstRow}}" stepKey="clickFirstRow" before="selectMainWebsite"/> + <waitForPageLoad stepKey="waitForWarningMessageToAppear" before="seeAssertAlertWarningMessage" /> + <click selector="{{AdminStoreGroupActionsSection.okButton}}" stepKey="seeAssertAlertWarningMessage" before="waitForStoreGridReload"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/Data/StoreConfigData.xml b/app/code/Magento/Store/Test/Mftf/Data/StoreConfigData.xml new file mode 100644 index 0000000000000..4333446925474 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Data/StoreConfigData.xml @@ -0,0 +1,24 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="StorefrontDisableAddStoreCodeToUrls"> + <!-- Magento default value --> + <data key="path">web/url/use_store</data> + <data key="scope_id">0</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> + <entity name="StorefrontEnableAddStoreCodeToUrls"> + <data key="path">web/url/use_store</data> + <data key="scope_id">0</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> +</entities> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminStoreGroupActionsSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminStoreGroupActionsSection.xml index f79ea080ed1ca..9ad0f40cc4d01 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminStoreGroupActionsSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminStoreGroupActionsSection.xml @@ -8,5 +8,6 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminStoreGroupActionsSection"> <element name="saveButton" type="button" selector="#save" timeout="60" /> + <element name="okButton" type="button" selector="//footer[@class='modal-footer']//button[@class='action-primary action-accept']/span" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml index d7006fd01b2ff..e8333bd307530 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml @@ -21,8 +21,10 @@ <element name="storeGrpNameInFirstRow" type="text" selector=".col-group_title>a"/> <element name="storeNameInFirstRow" type="text" selector=".col-store_title>a"/> <element name="firstRow" type="textarea" selector="(//*[@id='storeGrid_table']/tbody/tr)[1]"/> + <element name="nthRow" type="textarea" selector="(//*[@id='storeGrid_table']/tbody/tr)[{{rownum}}]" parameterized="true"/> <element name="successMessage" type="text" selector="//div[@class='message message-success success']/div"/> <element name="emptyText" type="text" selector="//tr[@class='data-grid-tr-no-data even']/td[@class='empty-text']"/> <element name="websiteName" type="text" selector="//td[@class='a-left col-website_title ']/a[contains(.,'{{websiteName}}')]" parameterized="true"/> + <element name="gridCell" type="text" selector="//table[@class='data-grid']//tr[{{row}}]//td[count(//table[@class='data-grid']//tr//th[contains(., '{{column}}')]/preceding-sibling::th) +1 ]" parameterized="true"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupWithCustomWebsiteAndDefaultCategoryTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupWithCustomWebsiteAndDefaultCategoryTest.xml new file mode 100644 index 0000000000000..8e8f31eaca865 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupWithCustomWebsiteAndDefaultCategoryTest.xml @@ -0,0 +1,58 @@ +<?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="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateStoreGroupWithCustomWebsiteAndDefaultCategoryTest"> + <annotations> + <stories value="Create Store Group"/> + <title value="Create Store Group with Custom Website and Default Category"/> + <description value="Test log in to Stores and Create Store Group with Custom Website and Default Category Test"/> + <testCaseId value="MC-14300"/> + <severity value="CRITICAL"/> + <group value="store"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create website--> + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createWebsite"> + <argument name="newWebsiteName" value="{{customWebsite.name}}"/> + <argument name="websiteCode" value="{{customWebsite.code}}"/> + </actionGroup> + </before> + <after> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> + <argument name="websiteName" value="{{customWebsite.name}}"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Create custom store group with custom website and default category and verify AssertStoreGroupSuccessSaveMessage--> + <actionGroup ref="CreateCustomStore" stepKey="createCustomStoreGroup"> + <argument name="website" value="{{customWebsite.name}}"/> + <argument name="store" value="{{customStoreGroup.name}}"/> + <argument name="rootCategory" value="Default Category"/> + </actionGroup> + + <!--Search created store group(from above step) in grid and verify AssertStoreGroupInGrid message--> + <actionGroup ref="AssertStoreGroupInGrid" stepKey="seeCreatedStoreGroupInGrid"> + <argument name="storeGroupName" value="{{customStoreGroup.name}}"/> + </actionGroup> + + <!--Go to store group form page and verify AssertStoreGroupForm--> + <actionGroup ref="AssertStoreGroupForm" stepKey="seeCreatedStoreGroupForm"> + <argument name="website" value="{{customWebsite.name}}"/> + <argument name="storeGroupName" value="{{customStoreGroup.name}}"/> + <argument name="storeGroupCode" value="{{customStoreGroup.code}}"/> + <argument name="rootCategory" value="Default Category"/> + </actionGroup> + <!--Also verify absence of delete button on store group form page(AssertStoreGroupNoDeleteButton)--> + <dontSee selector="{{AdminStoresMainActionsSection.deleteButton}}" stepKey="AssertStoreGroupNoDeleteButton"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupWithCustomWebsiteAndRootCategoryTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupWithCustomWebsiteAndRootCategoryTest.xml new file mode 100644 index 0000000000000..18f9822145dec --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupWithCustomWebsiteAndRootCategoryTest.xml @@ -0,0 +1,64 @@ +<?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="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateStoreGroupWithCustomWebsiteAndRootCategoryTest"> + <annotations> + <stories value="Create Store Group"/> + <title value="Create Store Group with Custom Website and Root Category"/> + <description value="Test log in to Stores and Create Store Group with Custom Website and Root Category Test"/> + <testCaseId value="MC-14299"/> + <severity value="CRITICAL"/> + <group value="store"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create root category--> + <createData entity="NewRootCategory" stepKey="rootCategory"/> + <createData entity="SimpleRootSubCategory" stepKey="category"> + <requiredEntity createDataKey="rootCategory"/> + </createData> + <!--Create website--> + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createWebsite"> + <argument name="newWebsiteName" value="{{customWebsite.name}}"/> + <argument name="websiteCode" value="{{customWebsite.code}}"/> + </actionGroup> + </before> + <after> + <!--Delete website--> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> + <argument name="websiteName" value="{{customWebsite.name}}"/> + </actionGroup> + <!--Delete root category--> + <deleteData stepKey="deleteRootCategory" createDataKey="rootCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Create custom store group with custom website and root category and verify AssertStoreGroupSuccessSaveMessage--> + <actionGroup ref="CreateCustomStore" stepKey="createCustomStoreGroup"> + <argument name="website" value="{{customWebsite.name}}"/> + <argument name="store" value="{{customStoreGroup.name}}"/> + <argument name="rootCategory" value="$$rootCategory.name$$"/> + </actionGroup> + + <!--Search created store group(from above step) in grid and verify AssertStoreGroupInGrid--> + <actionGroup ref="AssertStoreGroupInGrid" stepKey="seeCreatedStoreGroupInGrid"> + <argument name="storeGroupName" value="{{customStoreGroup.name}}"/> + </actionGroup> + + <!--Go to store group form page and verify AssertStoreGroupForm and AssertStoreGroupOnStoreViewForm--> + <actionGroup ref="AssertStoreGroupForm" stepKey="seeCreatedStoreGroupInForm"> + <argument name="website" value="{{customWebsite.name}}"/> + <argument name="storeGroupName" value="{{customStoreGroup.name}}"/> + <argument name="storeGroupCode" value="{{customStoreGroup.code}}"/> + <argument name="rootCategory" value="$$rootCategory.name$$"/> + </actionGroup> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupWithDefaultWebsiteAndDefaultCategoryTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupWithDefaultWebsiteAndDefaultCategoryTest.xml new file mode 100644 index 0000000000000..ddc5d061c1db2 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupWithDefaultWebsiteAndDefaultCategoryTest.xml @@ -0,0 +1,51 @@ +<?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="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateStoreGroupWithDefaultWebsiteAndDefaultCategoryTest"> + <annotations> + <stories value="Create Store Group"/> + <title value="Create Store Group with Default Website and Default Category"/> + <description value="Test log in to Stores and Create Store Group with Default Website and Default Category Test"/> + <testCaseId value="MC-14298"/> + <severity value="CRITICAL"/> + <group value="store"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteStoreGroup"> + <argument name="storeGroupName" value="SecondStoreGroupUnique.name"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Create custom store group with default website and default category and verify AssertStoreGroupSuccessSaveMessage--> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createNewCustomStoreGroup"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + <argument name="storeGroupName" value="{{SecondStoreGroupUnique.name}}"/> + <argument name="storeGroupCode" value="{{SecondStoreGroupUnique.code}}"/> + </actionGroup> + + <!--Search created store group(from above step) in grid and verify AssertStoreGroupInGrid--> + <actionGroup ref="AssertStoreGroupInGrid" stepKey="seeCreatedStoreGroupInGrid"> + <argument name="storeGroupName" value="{{SecondStoreGroupUnique.name}}"/> + </actionGroup> + + <!--Go to store group form page and verify AssertStoreGroupForm and AssertStoreGroupOnStoreViewForm--> + <actionGroup ref="AssertStoreGroupForm" stepKey="seeCreatedStoreGroupForm"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + <argument name="storeGroupName" value="{{SecondStoreGroupUnique.name}}"/> + <argument name="storeGroupCode" value="{{SecondStoreGroupUnique.code}}"/> + <argument name="rootCategory" value="Default Category"/> + </actionGroup> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreViewTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreViewTest.xml index af8aceed07f0f..e20eb70ae6f45 100644 --- a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreViewTest.xml +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreViewTest.xml @@ -11,32 +11,36 @@ <annotations> <features value="Store"/> <stories value="Create a store view in admin"/> - <title value="Admin should be able to create a store view"/> - <description value="Admin should be able to create a store view"/> + <title value="Admin shouldn't be able to create a Store View with the same code"/> + <description value="Admin shouldn't be able to create a Store View with the same code"/> <group value="storeView"/> <severity value="AVERAGE"/> - <testCaseId value="MAGETWO-95111"/> + <useCaseId value="MAGETWO-95111"/> + <testCaseId value="MC-15422"/> </annotations> + <before> - <actionGroup ref="LoginActionGroup" stepKey="login"/> + <actionGroup ref="LoginActionGroup" stepKey="loginAsAdmin"/> <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView" /> - <!--<createData stepKey="b2" entity="customStoreGroup"/>--> </before> - <!--Save store view on Store Grid--> - <actionGroup ref="AdminCreateStoreViewActionSaveGroup" stepKey="createStoreViewSave" /> - <!--Confirm new store view created on Store Grid--> - <fillField selector="{{AdminStoresGridSection.storeFilterTextField}}" userInput="{{customStore.name}}" stepKey="fillStoreViewFilter"/> - <click selector="{{AdminStoresGridSection.searchButton}}" stepKey="clickSearch" /> - <waitForPageLoad stepKey="waitForPageLoad"/> - <see selector="{{AdminStoresGridSection.storeNameInFirstRow}}" userInput="{{customStore.name}}" stepKey="seeNewStoreView" /> - <!--Creating the same store view to validate the code uniqueness on store form--> - <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView2" /> - <actionGroup ref="AdminCreateStoreViewCodeUniquenessActionGroup" stepKey="createStoreViewCode" /> + <after> <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView"> <argument name="customStore" value="customStore"/> </actionGroup> + <click selector="{{AdminStoresGridSection.resetButton}}" stepKey="resetSearchFilter"/> <actionGroup ref="logout" stepKey="logout"/> </after> + + <!--Filter grid and see created store view--> + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="navigateToStoresIndex"/> + <click selector="{{AdminStoresGridSection.resetButton}}" stepKey="resetSearchFilter"/> + <fillField selector="{{AdminStoresGridSection.storeFilterTextField}}" userInput="{{customStore.name}}" stepKey="fillStoreViewFilterField"/> + <click selector="{{AdminStoresGridSection.searchButton}}" stepKey="clickSearch"/> + <see selector="{{AdminStoresGridSection.gridCell('1', 'Store View')}}" userInput="{{customStore.name}}" stepKey="seeNewStoreView"/> + <!--Try to create store view with the same code--> + <actionGroup ref="AdminCreateStoreViewWithoutCheckActionGroup" stepKey="createSameStoreView"/> + <dontSeeElement selector="{{AdminMessagesSection.success}}" stepKey="dontSeeSuccessMessage"/> + <see selector="{{AdminMessagesSection.error}}" userInput="Store with the same code already exists." stepKey="seeErrorMessage"/> </test> </tests> diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminMoveStoreToOtherGroupSameWebsiteTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminMoveStoreToOtherGroupSameWebsiteTest.xml new file mode 100644 index 0000000000000..b86b99936dbe2 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminMoveStoreToOtherGroupSameWebsiteTest.xml @@ -0,0 +1,95 @@ +<?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="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminMoveStoreToOtherGroupSameWebsiteTest"> + <annotations> + <stories value="Move Store"/> + <title value="Move Store To Other Group Same Website and Verify Backend and Frontend"/> + <description value="Test log in to Stores and Move Store To Other Group Same Website Test"/> + <testCaseId value="MC-14294"/> + <severity value="CRITICAL"/> + <group value="store"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <!-- Create first store --> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createFirstStore"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + <argument name="storeGroupName" value="{{customStore.name}}"/> + <argument name="storeGroupCode" value="{{customStore.code}}"/> + </actionGroup> + <!-- Create first store view --> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createFirstStoreView"> + <argument name="StoreGroup" value="customStore"/> + <argument name="customStore" value="storeViewData1"/> + </actionGroup> + <!-- Create second store --> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createSecondStore"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + <argument name="storeGroupName" value="{{customStoreGroup.name}}"/> + <argument name="storeGroupCode" value="{{customStoreGroup.code}}"/> + </actionGroup> + <!-- Create second store view --> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createSecondStoreView"> + <argument name="StoreGroup" value="customStoreGroup"/> + <argument name="customStore" value="storeViewData2"/> + </actionGroup> + </before> + <after> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteFirstStore"> + <argument name="storeGroupName" value="customStore.name"/> + </actionGroup> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteSecondStore"> + <argument name="storeGroupName" value="customStoreGroup.name"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Search created second store view in grid--> + <actionGroup ref="AssertStoreViewInGridActionGroup" stepKey="searchCreatedStoreViewInGrid"> + <argument name="storeViewName" value="{{storeViewData2.name}}"/> + </actionGroup> + <!--Move created store view to other store keeping website same--> + <actionGroup ref="ChangeStoreInStoreViewActionGroup" stepKey="moveStoreView"> + <argument name="storeDropdown" value="{{customStore.name}}"/> + </actionGroup> + <!--Save the above store view and verify AssertStoreViewSuccessSaveMessage--> + <actionGroup ref="AdminCreateStoreViewActionSaveGroup" stepKey="verifyAssertStoreViewSuccessSaveMessage"/> + + <!--Search moved store view(from above step) in grid and verify AssertStoreInGrid--> + <actionGroup ref="AssertStoreViewInGridActionGroup" stepKey="searchMovedStoreViewInGrid"> + <argument name="storeViewName" value="{{storeViewData2.name}}"/> + </actionGroup> + + <!--Go to store view form page and verify AssertStoreForm--> + <actionGroup ref="AssertStoreViewFormActionGroup" stepKey="verifyStoreViewForm"> + <argument name="storeDropdown" value="{{customStore.name}}"/> + <argument name="storeViewName" value="{{storeViewData2.name}}"/> + <argument name="storeViewCode" value="{{storeViewData2.code}}"/> + <argument name="status" value="Enabled"/> + </actionGroup> + + <!--Go to store configuration page and verify AssertStoreBackend--> + <actionGroup ref="AssertStoreConfigurationBackendActionGroup" stepKey="verifyValuesOnStoreBackend"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + <argument name="customStore" value="{{customStore.name}}"/> + <argument name="storeView1" value="{{storeViewData1.name}}"/> + <argument name="storeView2" value="{{storeViewData2.name}}"/> + </actionGroup> + + <!--Go to storefront and verify AssertStoreFrontend--> + <actionGroup ref="AssertStoreFrontendActionGroup" stepKey="verifyValuesOnStoreFrontend"> + <argument name="customStore" value="{{customStore.name}}"/> + <argument name="storeView1" value="{{storeViewData1.name}}"/> + <argument name="storeView2" value="{{storeViewData2.name}}"/> + </actionGroup> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminUpdateStoreGroupAcceptAlertAndVerifyStoreViewFormTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminUpdateStoreGroupAcceptAlertAndVerifyStoreViewFormTest.xml new file mode 100644 index 0000000000000..9c84388d86f99 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminUpdateStoreGroupAcceptAlertAndVerifyStoreViewFormTest.xml @@ -0,0 +1,76 @@ +<?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="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminUpdateStoreGroupAcceptAlertAndVerifyStoreViewFormTest"> + <annotations> + <stories value="Update Store Group"/> + <title value="Update Store Group, Accept Alert and Verify Store View Form"/> + <description value="Test log in to Stores and Update Store Group Test"/> + <testCaseId value="MC-14296"/> + <severity value="CRITICAL"/> + <group value="store"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create root category--> + <createData entity="NewRootCategory" stepKey="rootCategory"/> + <createData entity="SimpleRootSubCategory" stepKey="category"> + <requiredEntity createDataKey="rootCategory"/> + </createData> + <!--Create website--> + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createWebsite"> + <argument name="newWebsiteName" value="{{customWebsite.name}}"/> + <argument name="websiteCode" value="{{customWebsite.code}}"/> + </actionGroup> + <!--Create custom store group--> + <actionGroup ref="CreateCustomStore" stepKey="createCustomStoreGroup"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + <argument name="store" value="{{staticStoreGroup.name}}"/> + <argument name="rootCategory" value="Default Category"/> + </actionGroup> + </before> + <after> + <!--Delete website--> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> + <argument name="websiteName" value="{{customWebsite.name}}"/> + </actionGroup> + <!--Delete root category--> + <deleteData stepKey="deleteRootCategory" createDataKey="rootCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open created Store group in grid--> + <actionGroup ref="AssertStoreGroupInGridActionGroup" stepKey="openCreatedStoreGroupInGrid"> + <argument name="storeGroupName" value="{{staticStoreGroup.name}}"/> + </actionGroup> + <click selector="{{AdminStoresGridSection.firstRow}}" stepKey="clickFirstRow"/> + <waitForPageLoad stepKey="AdminSystemStoreGroupPageToOpen"/> + <!--Update created Store group as per requirement and accept alert message--> + <actionGroup ref="EditCustomStoreGroupAcceptWarningMessageActionGroup" stepKey="updateCustomStoreGroup"> + <argument name="website" value="{{customWebsite.name}}"/> + <argument name="store" value="{{customStoreGroup.name}}"/> + <argument name="rootCategory" value="$$rootCategory.name$$"/> + </actionGroup> + + <!--Search updated store group(from above step) in grid and verify AssertStoreGroupInGrid--> + <actionGroup ref="AssertStoreGroupInGridActionGroup" stepKey="seeUpdatedStoreGroupInGrid"> + <argument name="storeGroupName" value="{{customStoreGroup.name}}"/> + </actionGroup> + + <!--Verify updated website name and updated websitecode on website form (AssertStoreGroupForm and AssertStoreGroupOnStoreViewForm)--> + <actionGroup ref="AssertStoreGroupFormActionGroup" stepKey="seeUpdatedStoreGroupForm"> + <argument name="website" value="{{customWebsite.name}}"/> + <argument name="storeGroupName" value="{{customStoreGroup.name}}"/> + <argument name="storeGroupCode" value="{{customStoreGroup.code}}"/> + <argument name="rootCategory" value="$$rootCategory.name$$"/> + </actionGroup> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminUpdateStoreGroupAndVerifyStoreViewFormTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminUpdateStoreGroupAndVerifyStoreViewFormTest.xml new file mode 100644 index 0000000000000..3d85a34901434 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminUpdateStoreGroupAndVerifyStoreViewFormTest.xml @@ -0,0 +1,66 @@ +<?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="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminUpdateStoreGroupAndVerifyStoreViewFormTest"> + <annotations> + <stories value="Update Store Group"/> + <title value="Update Store Group and Verify Store View Form"/> + <description value="Test log in to Stores and Update Store Group Test"/> + <testCaseId value="MC-14295"/> + <severity value="CRITICAL"/> + <group value="store"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create custom store group--> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createNewCustomStoreGroup"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + <argument name="storeGroupName" value="{{SecondStoreGroupUnique.name}}"/> + <argument name="storeGroupCode" value="{{SecondStoreGroupUnique.code}}"/> + </actionGroup> + </before> + <after> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteStoreGroup"> + <argument name="storeGroupName" value="customStoreGroup.name"/> + </actionGroup> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteUpdatedStoreGroup"> + <argument name="storeGroupName" value="SecondStoreGroupUnique.name"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open created Store group in grid--> + <actionGroup ref="AssertStoreGroupInGridActionGroup" stepKey="openCreatedStoreGroupInGrid"> + <argument name="storeGroupName" value="{{SecondStoreGroupUnique.name}}"/> + </actionGroup> + <click selector="{{AdminStoresGridSection.firstRow}}" stepKey="clickFirstRow"/> + <waitForPageLoad stepKey="AdminSystemStoreGroupPageToOpen"/> + <!--Update created Store group as per requirement--> + <actionGroup ref="CreateCustomStore" stepKey="createNewCustomStoreGroup"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + <argument name="store" value="{{customStoreGroup.name}}"/> + <argument name="rootCategory" value="Default Category"/> + </actionGroup> + + <!--Search updated store group(from above step) in grid and verify AssertStoreGroupInGrid--> + <actionGroup ref="AssertStoreGroupInGridActionGroup" stepKey="seeUpdatedStoreGroupInGrid"> + <argument name="storeGroupName" value="{{customStoreGroup.name}}"/> + </actionGroup> + + <!--Verify updated website name and updated websitecode on website form (AssertStoreGroupForm and AssertStoreGroupOnStoreViewForm)--> + <actionGroup ref="AssertStoreGroupFormActionGroup" stepKey="seeUpdatedStoreGroupForm"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + <argument name="storeGroupName" value="{{customStoreGroup.name}}"/> + <argument name="storeGroupCode" value="{{customStoreGroup.code}}"/> + <argument name="rootCategory" value="Default Category"/> + </actionGroup> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminUpdateStoreViewTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminUpdateStoreViewTest.xml new file mode 100644 index 0000000000000..054ee789fbdc5 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminUpdateStoreViewTest.xml @@ -0,0 +1,81 @@ +<?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="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminUpdateStoreViewTest"> + <annotations> + <stories value="Update Store View"/> + <title value="Update Store View and Verify Backend and Frontend"/> + <description value="Test log in to Stores and Update Store View Test"/> + <testCaseId value="MC-14316"/> + <severity value="CRITICAL"/> + <group value="store"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create custom store view--> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createNewStoreView"> + <argument name="StoreGroup" value="_defaultStoreGroup"/> + <argument name="customStore" value="storeViewData"/> + </actionGroup> + </before> + <after> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView"> + <argument name="customStore" value="storeViewData"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteUpdatedStoreView"> + <argument name="customStore" value="SecondStoreUnique"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Search created store view in grid--> + <actionGroup ref="AssertStoreViewInGridActionGroup" stepKey="searchCreatedStoreViewInGrid"> + <argument name="storeViewName" value="{{storeViewData.name}}"/> + </actionGroup> + <click selector="{{AdminStoresGridSection.storeNameInFirstRow}}" stepKey="clickStoreViewFirstRowInGrid"/> + <waitForPageLoad stepKey="waitForAdminSystemStoreViewPageLoad"/> + <!--Update created store view as per requirements--> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="updateStoreView"> + <argument name="StoreGroup" value="_defaultStoreGroup"/> + <argument name="customStore" value="SecondStoreUnique"/> + </actionGroup> + <!--Save the updated Store view and verify AssertStoreViewSuccessSaveMessage--> + <actionGroup ref="AdminCreateStoreViewActionSaveGroup" stepKey="verifyAssertStoreViewSuccessSaveMessage"> + </actionGroup> + + <!--Search updated store view in grid and verify AssertStoreViewInGridMessage--> + <actionGroup ref="AssertStoreViewInGridActionGroup" stepKey="verifyUpdatedStoreViewInGrid"> + <argument name="storeViewName" value="{{SecondStoreUnique.name}}"/> + </actionGroup> + + <!--Go to store view form page and verify AssertStoreForm--> + <actionGroup ref="AssertStoreViewFormActionGroup" stepKey="verifyStoreViewForm"> + <argument name="storeDropdown" value="{{_defaultStoreGroup.name}}"/> + <argument name="storeViewName" value="{{SecondStoreUnique.name}}"/> + <argument name="storeViewCode" value="{{SecondStoreUnique.code}}"/> + <argument name="status" value="Enabled"/> + </actionGroup> + + <!--Go to store configuration page and verify AssertStoreBackend--> + <amOnPage url="{{AdminConfigPage.url}}" stepKey="goToConfigStoreConfigurationPage"/> + <waitForPageLoad stepKey="waitForSystemStoreConfigurationPageLoad" /> + <click selector="{{AdminConfigSection.defaultConfigButton}}" stepKey="clickDefaultConfigButton"/> + <see selector="{{AdminConfigSection.defaultConfigDropdown}}" userInput="{{storeViewData.name}}" stepKey="seeAssertStoreViewInDefaultConfigDropdown"/> + <see selector="{{AdminConfigSection.defaultConfigDropdown}}" userInput="{{SecondStoreUnique.name}}" stepKey="seeAssertUpdateStoreViewInDefaultConfigDropdown"/> + + <!--Go to storefront and verify AssertStoreFrontend--> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStorefrontPage"/> + <waitForPageLoad stepKey="waitForStorefrontHomePageLoad"/> + <click selector="{{StorefrontHeaderSection.storeViewSwitcher}}" stepKey="selectStoreSwitcher"/> + <waitForPageLoad stepKey="waitForFirstStoreView"/> + <see selector="{{StorefrontHeaderSection.storeViewDropdown}}" userInput="{{storeViewData.name}}" stepKey="seeAssertStoreViewOnStorefront"/> + <see selector="{{StorefrontHeaderSection.storeViewDropdown}}" userInput="{{SecondStoreUnique.name}}" stepKey="seeAssertUpdatedStoreViewOnStorefront"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/Test/StorefrontAddStoreCodeInUrlTest.xml b/app/code/Magento/Store/Test/Mftf/Test/StorefrontAddStoreCodeInUrlTest.xml new file mode 100644 index 0000000000000..95f5a9cd2d669 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Test/StorefrontAddStoreCodeInUrlTest.xml @@ -0,0 +1,30 @@ +<?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="StorefrontAddStoreCodeInUrlTest"> + <annotations> + <features value="Backend"/> + <title value="Store code should be added to storefront URL if the corresponding configuration is enabled"/> + <description value="Store code should be added to storefront URL if the corresponding configuration is enabled"/> + <testCaseId value="MC-15944" /> + <group value="store"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <magentoCLI command="config:set {{StorefrontEnableAddStoreCodeToUrls.path}} {{StorefrontEnableAddStoreCodeToUrls.value}}" stepKey="addStoreCodeToUrlEnable"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontDisableAddStoreCodeToUrls.path}} {{StorefrontDisableAddStoreCodeToUrls.value}}" stepKey="addStoreCodeToUrlDisable"/> + </after> + + <actionGroup ref="StorefrontClickOnHeaderLogoActionGroup" stepKey="clickOnStorefrontHeaderLogo"/> + <actionGroup ref="AssertStorefrontStoreCodeInUrlActionGroup" stepKey="seeStoreCodeInUrl"/> + </test> +</tests> diff --git a/app/code/Magento/Store/Test/Unit/Model/StoreTest.php b/app/code/Magento/Store/Test/Unit/Model/StoreTest.php index f4a5010e51b88..a83ca833a15e0 100644 --- a/app/code/Magento/Store/Test/Unit/Model/StoreTest.php +++ b/app/code/Magento/Store/Test/Unit/Model/StoreTest.php @@ -160,7 +160,7 @@ public function testGetWebsite() /** @var \Magento\Store\Model\Store $model */ $model = $this->objectManagerHelper->getObject( \Magento\Store\Model\Store::class, - ['websiteRepository' => $websiteRepository,] + ['websiteRepository' => $websiteRepository] ); $model->setWebsiteId($websiteId); @@ -181,7 +181,7 @@ public function testGetWebsiteIfWebsiteIsNotExist() /** @var \Magento\Store\Model\Store $model */ $model = $this->objectManagerHelper->getObject( \Magento\Store\Model\Store::class, - ['websiteRepository' => $websiteRepository,] + ['websiteRepository' => $websiteRepository] ); $model->setWebsiteId(null); @@ -207,7 +207,7 @@ public function testGetGroup() /** @var \Magento\Store\Model\Store $model */ $model = $this->objectManagerHelper->getObject( \Magento\Store\Model\Store::class, - ['groupRepository' => $groupRepository,] + ['groupRepository' => $groupRepository] ); $model->setGroupId($groupId); @@ -228,7 +228,7 @@ public function testGetGroupIfGroupIsNotExist() /** @var \Magento\Store\Model\Store $model */ $model = $this->objectManagerHelper->getObject( \Magento\Store\Model\Store::class, - ['groupRepository' => $groupRepository,] + ['groupRepository' => $groupRepository] ); $model->setGroupId(null); @@ -377,30 +377,31 @@ public function testGetBaseUrlEntryPoint() $configMock = $this->getMockForAbstractClass(\Magento\Framework\App\Config\ReinitableConfigInterface::class); $configMock->expects($this->atLeastOnce()) ->method('getValue') - ->will($this->returnCallback( - function ($path, $scope, $scopeCode) use ($expectedPath) { - return $expectedPath == $path ? 'http://domain.com/' . $path . '/' : null; - } - )); + ->willReturnCallback(function ($path, $scope, $scopeCode) use ($expectedPath) { + return $expectedPath == $path ? 'http://domain.com/' . $path . '/' : null; + }); + $this->requestMock->expects($this->once()) + ->method('getServer') + ->with('SCRIPT_FILENAME') + ->willReturn('test_script.php'); + /** @var \Magento\Store\Model\Store $model */ $model = $this->objectManagerHelper->getObject( \Magento\Store\Model\Store::class, [ 'config' => $configMock, 'isCustomEntryPoint' => false, + 'request' => $this->requestMock ] ); $model->setCode('scopeCode'); $this->setUrlModifier($model); - $server = $_SERVER; - $_SERVER['SCRIPT_FILENAME'] = 'test_script.php'; $this->assertEquals( $expectedBaseUrl, $model->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_LINK, false) ); - $_SERVER = $server; } /** @@ -592,7 +593,7 @@ public function testGetAllowedCurrencies() /** @var \Magento\Store\Model\Store $model */ $model = $this->objectManagerHelper->getObject( \Magento\Store\Model\Store::class, - ['config' => $configMock, 'currencyInstalled' => $currencyPath,] + ['config' => $configMock, 'currencyInstalled' => $currencyPath] ); $this->assertEquals($expectedResult, $model->getAllowedCurrencies()); diff --git a/app/code/Magento/Store/composer.json b/app/code/Magento/Store/composer.json index da408f105ccb6..ebaa32b95f48b 100644 --- a/app/code/Magento/Store/composer.json +++ b/app/code/Magento/Store/composer.json @@ -7,7 +7,6 @@ "require": { "php": "~7.1.3||~7.2.0", "magento/framework": "*", - "magento/module-message-queue": "*", "magento/module-catalog": "*", "magento/module-config": "*", "magento/module-directory": "*", diff --git a/app/code/Magento/StoreGraphQl/Controller/HttpHeaderProcessor/StoreProcessor.php b/app/code/Magento/StoreGraphQl/Controller/HttpHeaderProcessor/StoreProcessor.php new file mode 100644 index 0000000000000..7999a96917cde --- /dev/null +++ b/app/code/Magento/StoreGraphQl/Controller/HttpHeaderProcessor/StoreProcessor.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\StoreGraphQl\Controller\HttpHeaderProcessor; + +use Magento\GraphQl\Controller\HttpHeaderProcessorInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Framework\App\Http\Context as HttpContext; +use Magento\Store\Api\StoreCookieManagerInterface; + +/** + * Process the "Store" header entry + */ +class StoreProcessor implements HttpHeaderProcessorInterface +{ + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var HttpContext + */ + private $httpContext; + + /** + * @var StoreCookieManagerInterface + */ + private $storeCookieManager; + + /** + * @param StoreManagerInterface $storeManager + * @param HttpContext $httpContext + * @param StoreCookieManagerInterface $storeCookieManager + */ + public function __construct( + StoreManagerInterface $storeManager, + HttpContext $httpContext, + StoreCookieManagerInterface $storeCookieManager + ) { + $this->storeManager = $storeManager; + $this->httpContext = $httpContext; + $this->storeCookieManager = $storeCookieManager; + } + + /** + * Handle the value of the store and set the scope + * + * @see \Magento\Store\App\Action\Plugin\Context::beforeDispatch + * + * @param string $headerValue + * @return void + */ + public function processHeaderValue(string $headerValue) : void + { + if (!empty($headerValue)) { + $storeCode = ltrim(rtrim($headerValue)); + $this->storeManager->setCurrentStore($storeCode); + $this->updateContext($storeCode); + } elseif (!$this->isAlreadySet()) { + $storeCode = $this->storeCookieManager->getStoreCodeFromCookie() + ?: $this->storeManager->getDefaultStoreView()->getCode(); + $this->storeManager->setCurrentStore($storeCode); + $this->updateContext($storeCode); + } + } + + /** + * Update context accordingly to the store code found. + * + * @param string $storeCode + * @return void + */ + private function updateContext(string $storeCode) : void + { + $this->httpContext->setValue( + StoreManagerInterface::CONTEXT_STORE, + $storeCode, + $this->storeManager->getDefaultStoreView()->getCode() + ); + } + + /** + * Check if there is a need to find the current store. + * + * @return bool + */ + private function isAlreadySet(): bool + { + $storeKey = StoreManagerInterface::CONTEXT_STORE; + + return $this->httpContext->getValue($storeKey) !== null; + } +} diff --git a/app/code/Magento/StoreGraphQl/Controller/HttpRequestValidator/StoreValidator.php b/app/code/Magento/StoreGraphQl/Controller/HttpRequestValidator/StoreValidator.php new file mode 100644 index 0000000000000..144905d728141 --- /dev/null +++ b/app/code/Magento/StoreGraphQl/Controller/HttpRequestValidator/StoreValidator.php @@ -0,0 +1,57 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\StoreGraphQl\Controller\HttpRequestValidator; + +use Magento\Framework\App\HttpRequestInterface; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\GraphQl\Controller\HttpRequestValidatorInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Validate the "Store" header entry + */ +class StoreValidator implements HttpRequestValidatorInterface +{ + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param StoreManagerInterface $storeManager + */ + public function __construct( + StoreManagerInterface $storeManager + ) { + $this->storeManager = $storeManager; + } + + /** + * Validate the header 'Store' value. + * + * @param HttpRequestInterface $request + * @return void + * @throws GraphQlInputException + */ + public function validate(HttpRequestInterface $request): void + { + $headerValue = $request->getHeader('Store'); + if (!empty($headerValue)) { + $storeCode = ltrim(rtrim($headerValue)); + $stores = $this->storeManager->getStores(false, true); + if (!isset($stores[$storeCode])) { + if (strtolower($storeCode) !== 'default') { + $this->storeManager->setCurrentStore(null); + throw new GraphQlInputException( + __("Requested store is not found") + ); + } + } + } + } +} diff --git a/app/code/Magento/StoreGraphQl/composer.json b/app/code/Magento/StoreGraphQl/composer.json index d53ba9fbb0023..aa36a4891343d 100644 --- a/app/code/Magento/StoreGraphQl/composer.json +++ b/app/code/Magento/StoreGraphQl/composer.json @@ -5,9 +5,7 @@ "require": { "php": "~7.1.3||~7.2.0", "magento/framework": "*", - "magento/module-store": "*" - }, - "suggest": { + "magento/module-store": "*", "magento/module-graph-ql": "*" }, "license": [ diff --git a/app/code/Magento/StoreGraphQl/etc/graphql/di.xml b/app/code/Magento/StoreGraphQl/etc/graphql/di.xml new file mode 100644 index 0000000000000..c2191164287f1 --- /dev/null +++ b/app/code/Magento/StoreGraphQl/etc/graphql/di.xml @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\GraphQl\Controller\HttpRequestProcessor"> + <arguments> + <argument name="graphQlHeaders" xsi:type="array"> + <item name="Store" xsi:type="object">Magento\StoreGraphQl\Controller\HttpHeaderProcessor\StoreProcessor</item> + </argument> + <argument name="requestValidators" xsi:type="array"> + <item name="storeValidator" xsi:type="object">Magento\StoreGraphQl\Controller\HttpRequestValidator\StoreValidator</item> + </argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/StoreGraphQl/etc/module.xml b/app/code/Magento/StoreGraphQl/etc/module.xml index f53379ac3bbf2..bbec6a85a1a13 100644 --- a/app/code/Magento/StoreGraphQl/etc/module.xml +++ b/app/code/Magento/StoreGraphQl/etc/module.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="Magento_StoreGraphQl"> <sequence> + <module name="Magento_Store"/> <module name="Magento_GraphQl"/> </sequence> </module> diff --git a/app/code/Magento/StoreGraphQl/etc/schema.graphqls b/app/code/Magento/StoreGraphQl/etc/schema.graphqls index d9f7eaaaa294c..376635e5c8f75 100644 --- a/app/code/Magento/StoreGraphQl/etc/schema.graphqls +++ b/app/code/Magento/StoreGraphQl/etc/schema.graphqls @@ -1,7 +1,7 @@ # Copyright © Magento, Inc. All rights reserved. # See COPYING.txt for license details. type Query { - storeConfig : StoreConfig @resolver(class: "Magento\\StoreGraphQl\\Model\\Resolver\\StoreConfigResolver") @doc(description: "The store config query") + storeConfig : StoreConfig @resolver(class: "Magento\\StoreGraphQl\\Model\\Resolver\\StoreConfigResolver") @doc(description: "The store config query") @cache(cacheable: false) } type Website @doc(description: "The type contains information about a website") { diff --git a/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml index e40a04080285a..4290ddbbd8dd4 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -9,7 +9,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductInfoMainSection"> - <element name="swatchOptionByLabel" type="button" selector="div.swatch-option[option-label={{opt}}]" parameterized="true"/> + <element name="swatchOptionByLabel" type="button" selector="div.swatch-option[option-label='{{opt}}']" parameterized="true"/> <element name="nthSwatchOption" type="button" selector="div.swatch-option:nth-of-type({{var}})" parameterized="true"/> <element name="selectedSwatchValue" type="text" selector="//div[contains(@class, 'swatch-attribute') and contains(., '{{attr}}')]//span[contains(@class, 'swatch-attribute-selected-option')]" parameterized="true"/> <element name="swatchAttributeOptions" type="text" selector="div.swatch-attribute-options"/> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml index e4c96ab3a2ba7..5347a1a1f870f 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml @@ -79,16 +79,25 @@ <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> <waitForPageLoad time="30" stepKey="waitForProductGrid"/> <actionGroup ref="goToCreateProductPage" stepKey="goToCreateConfigurableProduct"> - <argument name="product" value="BaseConfigurableProduct"/> + <argument name="product" value="ApiConfigurableProduct"/> + </actionGroup> + <!-- Add image to configurable product --> + <actionGroup ref="addProductImage" stepKey="addFirstImageForProductConfigurable"> + <argument name="image" value="MagentoLogo"/> + </actionGroup> + <!-- Add image to configurable product --> + <actionGroup ref="addProductImage" stepKey="addSecondImageForProductConfigurable"> + <argument name="image" value="TestImageNew"/> </actionGroup> <actionGroup ref="fillMainProductForm" stepKey="fillProductForm"> - <argument name="product" value="BaseConfigurableProduct"/> + <argument name="product" value="ApiConfigurableProduct"/> </actionGroup> <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createCategory.name$$]" stepKey="fillCategory"/> <!-- Create configurations based off the visual swatch we created earlier --> - <actionGroup ref="createConfigurationsForAttribute" stepKey="createConfigurations"> + <actionGroup ref="createConfigurationsForAttributeWithImages" stepKey="createConfigurations"> <argument name="attributeCode" value="{{ProductAttributeFrontendLabel.label}}"/> + <argument name="image" value="TestImageAdobe"/> </actionGroup> <!-- Go to the category page --> @@ -111,7 +120,33 @@ <!-- Click a swatch and expect to see the configurable product, not see the simple product --> <click selector="{{StorefrontCategorySidebarSection.attributeNthOption(ProductAttributeFrontendLabel.label, '1')}}" stepKey="filterBySwatch1"/> - <see selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" userInput="{{BaseConfigurableProduct.name}}" stepKey="seeConfigurableProduct"/> + <see selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" userInput="{{ApiConfigurableProduct.name}}" stepKey="seeConfigurableProduct"/> <dontSee selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" userInput="$$createSimpleProduct.name$$" stepKey="dontSeeSimpleProduct"/> + + <!-- Assert configurable product in storefront product page --> + <actionGroup ref="AssertProductInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> + <argument name="product" value="ApiConfigurableProduct"/> + </actionGroup> + + <!-- Assert configurable product image in storefront product page --> + <actionGroup ref="assertProductImageStorefrontProductPage" stepKey="assertProductImageStorefrontProductPage"> + <argument name="product" value="ApiConfigurableProduct"/> + <argument name="image" value="MagentoLogo"/> + </actionGroup> + + <!-- Assert configurable product image in storefront product page --> + <actionGroup ref="assertProductImageStorefrontProductPage" stepKey="assertProductSecondImageStorefrontProductPage"> + <argument name="product" value="ApiConfigurableProduct"/> + <argument name="image" value="TestImageNew"/> + </actionGroup> + + <!-- Click a swatch and expect to see the image from the swatch from the configurable product --> + <click selector="{{StorefrontProductInfoMainSection.swatchOptionByLabel('adobe-thumb')}}" stepKey="clickSwatchOption"/> + + <!-- Assert swatch option image for configurable product image in storefront product page --> + <actionGroup ref="assertProductImageStorefrontProductPage" stepKey="assertSwatchImageStorefrontProductPage"> + <argument name="product" value="ApiConfigurableProduct"/> + <argument name="image" value="TestImageAdobe"/> + </actionGroup> </test> </tests> diff --git a/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php b/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php index bff489ee50c2f..77b3cfa3a08bb 100644 --- a/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php +++ b/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php @@ -348,7 +348,7 @@ public function mapItems( $useBaseCurrency ) { $items = $shippingAssignment->getItems(); - if (!count($items)) { + if (empty($items)) { return []; } @@ -478,7 +478,7 @@ protected function prepareQuoteDetails(ShippingAssignmentInterface $shippingAssi { $items = $shippingAssignment->getItems(); $address = $shippingAssignment->getShipping()->getAddress(); - if (!count($items)) { + if (empty($items)) { return $this->quoteDetailsDataObjectFactory->create(); } @@ -688,6 +688,9 @@ public function updateItemTaxInfo($quoteItem, $itemTaxDetails, $baseItemTaxDetai { //The price should be base price $quoteItem->setPrice($baseItemTaxDetails->getPrice()); + if ($quoteItem->getCustomPrice() && $this->taxHelper->applyTaxOnCustomPrice()) { + $quoteItem->setCustomPrice($baseItemTaxDetails->getPrice()); + } $quoteItem->setConvertedPrice($itemTaxDetails->getPrice()); $quoteItem->setPriceInclTax($itemTaxDetails->getPriceInclTax()); $quoteItem->setRowTotal($itemTaxDetails->getRowTotal()); diff --git a/app/code/Magento/Tax/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Tax/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..2bed9b0d07918 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,26 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuStoresTaxesTaxRules"> + <data key="pageTitle">Tax Rules</data> + <data key="title">Tax Rules</data> + <data key="dataUiId">magento-tax-sales-tax-rules</data> + </entity> + <entity name="AdminMenuStoresTaxZonesAndRates"> + <data key="pageTitle">Tax Zones and Rates</data> + <data key="title">Tax Zones and Rates</data> + <data key="dataUiId">magento-tax-sales-tax-rates</data> + </entity> + <entity name="AdminMenuSystemDataTransferImportAndExportTaxRates"> + <data key="pageTitle">Import and Export Tax Rates</data> + <data key="title">Import/Export Tax Rates</data> + <data key="dataUiId">magento-taximportexport-system-convert-tax</data> + </entity> +</entities> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminStoresTaxRulesNavigateMenuTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminStoresTaxRulesNavigateMenuTest.xml new file mode 100644 index 0000000000000..1277d6e5f9fd2 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminStoresTaxRulesNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminStoresTaxRulesNavigateMenuTest"> + <annotations> + <features value="Tax"/> + <stories value="Menu Navigation"/> + <title value="Admin stores tax rules navigate menu test"/> + <description value="Admin should be able to navigate to Stores > Tax Rules"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14175"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToStoresTaxRulesPage"> + <argument name="menuUiId" value="{{AdminMenuStores.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuStoresTaxesTaxRules.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuStoresTaxesTaxRules.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminStoresTaxZonesAndRatesNavigateMenuTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminStoresTaxZonesAndRatesNavigateMenuTest.xml new file mode 100644 index 0000000000000..e0a4d5d9a4016 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminStoresTaxZonesAndRatesNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminStoresTaxZonesAndRatesNavigateMenuTest"> + <annotations> + <features value="Tax"/> + <stories value="Menu Navigation"/> + <title value="Admin stores tax zones and rates navigate menu test"/> + <description value="Admin should be able to navigate to Stores > Tax Zones and Rates"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14176"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToStoresTaxZonesAndRatesPage"> + <argument name="menuUiId" value="{{AdminMenuStores.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuStoresTaxZonesAndRates.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuStoresTaxZonesAndRates.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminSystemImportExportTaxRatesNavigateMenuTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminSystemImportExportTaxRatesNavigateMenuTest.xml new file mode 100644 index 0000000000000..a84ae61d66305 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminSystemImportExportTaxRatesNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminSystemImportExportTaxRatesNavigateMenuTest"> + <annotations> + <features value="Tax"/> + <stories value="Menu Navigation"/> + <title value="Admin system import export tax rates navigate menu test"/> + <description value="Admin should be able to navigate to System > Import/Export Tax Rates"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14177"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToSystemImportExportTaxRatesPage"> + <argument name="menuUiId" value="{{AdminMenuSystem.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSystemDataTransferImportAndExportTaxRates.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSystemDataTransferImportAndExportTaxRates.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Unit/Model/Sales/Total/Quote/CommonTaxCollectorTest.php b/app/code/Magento/Tax/Test/Unit/Model/Sales/Total/Quote/CommonTaxCollectorTest.php index 9325ec10dc627..50d45ad662bd4 100644 --- a/app/code/Magento/Tax/Test/Unit/Model/Sales/Total/Quote/CommonTaxCollectorTest.php +++ b/app/code/Magento/Tax/Test/Unit/Model/Sales/Total/Quote/CommonTaxCollectorTest.php @@ -3,79 +3,106 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Tax\Test\Unit\Model\Sales\Total\Quote; -/** - * Test class for \Magento\Tax\Model\Sales\Total\Quote\Tax - */ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Tax\Helper\Data as TaxHelper; +use Magento\Tax\Api\Data\TaxDetailsItemInterface; +use Magento\Quote\Model\Quote\Item as QuoteItem; +use Magento\Store\Model\Store; +use Magento\Tax\Model\Sales\Total\Quote\CommonTaxCollector; +use Magento\Tax\Model\Config; +use Magento\Quote\Model\Quote\Address as QuoteAddress; +use Magento\Quote\Model\Quote; +use Magento\Tax\Api\Data\QuoteDetailsItemInterface; +use Magento\Tax\Api\Data\TaxClassKeyInterface; +use Magento\Tax\Model\Sales\Quote\ItemDetails; +use Magento\Tax\Model\TaxClass\Key as TaxClassKey; +use Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory; +use Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory; +use Magento\Quote\Api\Data\ShippingAssignmentInterface; +use Magento\Quote\Api\Data\ShippingInterface; +use Magento\Quote\Model\Quote\Address\Total as QuoteAddressTotal; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; /** + * Common tax collector test + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class CommonTaxCollectorTest extends \PHPUnit\Framework\TestCase +class CommonTaxCollectorTest extends TestCase { /** - * @var \Magento\Tax\Model\Sales\Total\Quote\CommonTaxCollector + * @var CommonTaxCollector */ private $commonTaxCollector; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Tax\Model\Config + * @var MockObject|Config */ private $taxConfig; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Quote\Model\Quote\Address + * @var MockObject|QuoteAddress */ private $address; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Quote\Model\Quote + * @var MockObject|Quote */ private $quote; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Store\Model\Store + * @var MockObject|Store */ private $store; /** - * @var \PHPUnit_Framework_MockObject_MockObject| + * @var MockObject */ protected $taxClassKeyDataObjectFactoryMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject| + * @var MockObject */ protected $quoteDetailsItemDataObjectFactoryMock; /** - * @var \Magento\Tax\Api\Data\QuoteDetailsItemInterface + * @var QuoteDetailsItemInterface */ protected $quoteDetailsItemDataObject; /** - * @var \Magento\Tax\Api\Data\TaxClassKeyInterface + * @var TaxClassKeyInterface */ protected $taxClassKeyDataObject; + /** + * @var TaxHelper + */ + private $taxHelper; + + /** + * {@inheritdoc} + */ protected function setUp() { $objectManager = new ObjectManager($this); - $this->taxConfig = $this->getMockBuilder(\Magento\Tax\Model\Config::class) + $this->taxConfig = $this->getMockBuilder(Config::class) ->disableOriginalConstructor() - ->setMethods(['getShippingTaxClass', 'shippingPriceIncludesTax']) + ->setMethods(['getShippingTaxClass', 'shippingPriceIncludesTax', 'discountTax']) ->getMock(); - $this->store = $this->getMockBuilder(\Magento\Store\Model\Store::class) + $this->store = $this->getMockBuilder(Store::class) ->disableOriginalConstructor() ->setMethods(['__wakeup']) ->getMock(); - $this->quote = $this->getMockBuilder(\Magento\Quote\Model\Quote::class) + $this->quote = $this->getMockBuilder(Quote::class) ->disableOriginalConstructor() ->setMethods(['__wakeup', 'getStore']) ->getMock(); @@ -84,7 +111,7 @@ protected function setUp() ->method('getStore') ->will($this->returnValue($this->store)); - $this->address = $this->getMockBuilder(\Magento\Quote\Model\Quote\Address::class) + $this->address = $this->getMockBuilder(QuoteAddress::class) ->disableOriginalConstructor() ->getMock(); @@ -92,35 +119,41 @@ protected function setUp() ->method('getQuote') ->will($this->returnValue($this->quote)); $methods = ['create']; - $this->quoteDetailsItemDataObject = $objectManager->getObject( - \Magento\Tax\Model\Sales\Quote\ItemDetails::class - ); - $this->taxClassKeyDataObject = $objectManager->getObject(\Magento\Tax\Model\TaxClass\Key::class); + $this->quoteDetailsItemDataObject = $objectManager->getObject(ItemDetails::class); + $this->taxClassKeyDataObject = $objectManager->getObject(TaxClassKey::class); $this->quoteDetailsItemDataObjectFactoryMock - = $this->createPartialMock(\Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory::class, $methods); + = $this->createPartialMock(QuoteDetailsItemInterfaceFactory::class, $methods); $this->quoteDetailsItemDataObjectFactoryMock->expects($this->any()) ->method('create') ->willReturn($this->quoteDetailsItemDataObject); $this->taxClassKeyDataObjectFactoryMock = - $this->createPartialMock(\Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory::class, $methods); + $this->createPartialMock(TaxClassKeyInterfaceFactory::class, $methods); $this->taxClassKeyDataObjectFactoryMock->expects($this->any()) ->method('create') ->willReturn($this->taxClassKeyDataObject); + $this->taxHelper = $this->getMockBuilder(TaxHelper::class) + ->disableOriginalConstructor() + ->getMock(); $this->commonTaxCollector = $objectManager->getObject( - \Magento\Tax\Model\Sales\Total\Quote\CommonTaxCollector::class, + CommonTaxCollector::class, [ 'taxConfig' => $this->taxConfig, 'quoteDetailsItemDataObjectFactory' => $this->quoteDetailsItemDataObjectFactoryMock, - 'taxClassKeyDataObjectFactory' => $this->taxClassKeyDataObjectFactoryMock + 'taxClassKeyDataObjectFactory' => $this->taxClassKeyDataObjectFactoryMock, + 'taxHelper' => $this->taxHelper, ] ); } /** + * Test for GetShippingDataObject + * * @param array $addressData * @param bool $useBaseCurrency * @param string $shippingTaxClass * @param bool $shippingPriceInclTax + * + * @return void * @dataProvider getShippingDataObjectDataProvider */ public function testGetShippingDataObject( @@ -128,8 +161,8 @@ public function testGetShippingDataObject( $useBaseCurrency, $shippingTaxClass, $shippingPriceInclTax - ) { - $shippingAssignmentMock = $this->createMock(\Magento\Quote\Api\Data\ShippingAssignmentInterface::class); + ): void { + $shippingAssignmentMock = $this->createMock(ShippingAssignmentInterface::class); $methods = [ 'getShippingDiscountAmount', 'getShippingTaxCalculationAmount', @@ -139,8 +172,10 @@ public function testGetShippingDataObject( 'getBaseShippingAmount', 'getBaseShippingDiscountAmount' ]; - $totalsMock = $this->createPartialMock(\Magento\Quote\Model\Quote\Address\Total::class, $methods); - $shippingMock = $this->createMock(\Magento\Quote\Api\Data\ShippingInterface::class); + /** @var MockObject|QuoteAddressTotal $totalsMock */ + $totalsMock = $this->createPartialMock(QuoteAddressTotal::class, $methods); + $shippingMock = $this->createMock(ShippingInterface::class); + /** @var MockObject|ShippingAssignmentInterface $shippingAssignmentMock */ $shippingAssignmentMock->expects($this->once())->method('getShipping')->willReturn($shippingMock); $shippingMock->expects($this->once())->method('getAddress')->willReturn($this->address); $baseShippingAmount = $addressData['base_shipping_amount']; @@ -184,9 +219,44 @@ public function testGetShippingDataObject( } /** + * Update item tax info + * + * @return void + */ + public function testUpdateItemTaxInfo(): void + { + /** @var MockObject|QuoteItem $quoteItem */ + $quoteItem = $this->getMockBuilder(QuoteItem::class) + ->disableOriginalConstructor() + ->setMethods(['getPrice', 'setPrice', 'getCustomPrice', 'setCustomPrice']) + ->getMock(); + $this->taxHelper->method('applyTaxOnCustomPrice')->willReturn(true); + $quoteItem->method('getCustomPrice')->willReturn(true); + /** @var MockObject|TaxDetailsItemInterface $itemTaxDetails */ + $itemTaxDetails = $this->getMockBuilder(TaxDetailsItemInterface::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var MockObject|TaxDetailsItemInterface $baseItemTaxDetails */ + $baseItemTaxDetails = $this->getMockBuilder(TaxDetailsItemInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $quoteItem->expects($this->once())->method('setCustomPrice'); + + $this->commonTaxCollector->updateItemTaxInfo( + $quoteItem, + $itemTaxDetails, + $baseItemTaxDetails, + $this->store + ); + } + + /** + * Data for testGetShippingDataObject + * * @return array */ - public function getShippingDataObjectDataProvider() + public function getShippingDataObjectDataProvider(): array { $data = [ 'free_shipping' => [ diff --git a/app/code/Magento/Tax/view/adminhtml/layout/tax_rate_block.xml b/app/code/Magento/Tax/view/adminhtml/layout/tax_rate_block.xml index 463994262b7d3..5c50b306411ed 100644 --- a/app/code/Magento/Tax/view/adminhtml/layout/tax_rate_block.xml +++ b/app/code/Magento/Tax/view/adminhtml/layout/tax_rate_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.tax.rate.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">tax_rate_grid</argument> - <argument name="dataSource" xsi:type="object">Magento\Tax\Model\TaxRateCollection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Tax\Model\TaxRateCollection</argument> <argument name="default_sort" xsi:type="string">region_name</argument> <argument name="default_dir" xsi:type="string">ASC</argument> <argument name="save_parameters_in_session" xsi:type="string">1</argument> diff --git a/app/code/Magento/Tax/view/adminhtml/layout/tax_rule_block.xml b/app/code/Magento/Tax/view/adminhtml/layout/tax_rule_block.xml index 5e42b835c035d..07afd05e63f8c 100644 --- a/app/code/Magento/Tax/view/adminhtml/layout/tax_rule_block.xml +++ b/app/code/Magento/Tax/view/adminhtml/layout/tax_rule_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.block.tax.rule.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">taxRuleGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\Tax\Model\TaxRuleCollection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Tax\Model\TaxRuleCollection</argument> <argument name="default_sort" xsi:type="string">tax_rule_id</argument> <argument name="default_dir" xsi:type="string">ASC</argument> <argument name="save_parameters_in_session" xsi:type="string">1</argument> diff --git a/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/UploadJs.php b/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/UploadJs.php index e87e70e21b9de..89636ad3de50e 100644 --- a/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/UploadJs.php +++ b/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/UploadJs.php @@ -4,19 +4,21 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Theme\Controller\Adminhtml\System\Design\Theme; +use Magento\Framework\App\Action\HttpGetActionInterface; + /** * Class UploadJs * @deprecated */ -class UploadJs extends \Magento\Theme\Controller\Adminhtml\System\Design\Theme +class UploadJs extends \Magento\Theme\Controller\Adminhtml\System\Design\Theme implements HttpGetActionInterface { /** * Upload js file * * @return void - * @throws \Magento\Framework\Exception\LocalizedException */ public function execute() { @@ -50,6 +52,7 @@ public function execute() \Magento\Framework\View\Design\Theme\Customization\File\Js::TYPE ); $result = ['error' => false, 'files' => $customization->generateFileInfo($customJsFiles)]; + // phpcs:disable Magento2.Exceptions.ThrowCatch } catch (\Magento\Framework\Exception\LocalizedException $e) { $result = ['error' => true, 'message' => $e->getMessage()]; } catch (\Exception $e) { diff --git a/app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php b/app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php index a81f29280af96..a234cd589904c 100644 --- a/app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php +++ b/app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php @@ -5,34 +5,59 @@ */ namespace Magento\Theme\Controller\Result; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Framework\App\Response\Http; + /** - * Plugin for putting messages to cookies + * Plugin for putting all js to footer. */ class JsFooterPlugin { + private const XML_PATH_DEV_MOVE_JS_TO_BOTTOM = 'dev/js/move_inline_to_bottom'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct(ScopeConfigInterface $scopeConfig) + { + $this->scopeConfig = $scopeConfig; + } + /** - * Put all javascript to footer before sending the response + * Put all javascript to footer before sending the response. * - * @param \Magento\Framework\App\Response\Http $subject + * @param Http $subject * @return void */ - public function beforeSendResponse(\Magento\Framework\App\Response\Http $subject) + public function beforeSendResponse(Http $subject) { $content = $subject->getContent(); $script = []; if (strpos($content, '</body') !== false) { - $pattern = '#<script[^>]*+(?<!text/x-magento-template.)>.*?</script>#is'; - $content = preg_replace_callback( - $pattern, - function ($matchPart) use (&$script) { - $script[] = $matchPart[0]; - return ''; - }, - $content - ); - $subject->setContent( - str_replace('</body', implode("\n", $script) . "\n</body", $content) - ); + if ($this->scopeConfig->isSetFlag( + self::XML_PATH_DEV_MOVE_JS_TO_BOTTOM, + ScopeInterface::SCOPE_STORE + ) + ) { + $pattern = '#<script[^>]*+(?<!text/x-magento-template.)>.*?</script>#is'; + $content = preg_replace_callback( + $pattern, + function ($matchPart) use (&$script) { + $script[] = $matchPart[0]; + return ''; + }, + $content + ); + $subject->setContent( + str_replace('</body', implode("\n", $script) . "\n</body", $content) + ); + } } } } diff --git a/app/code/Magento/Theme/Model/Design/Backend/File.php b/app/code/Magento/Theme/Model/Design/Backend/File.php index 511fe30f79dcd..27636b4d574cd 100644 --- a/app/code/Magento/Theme/Model/Design/Backend/File.php +++ b/app/code/Magento/Theme/Model/Design/Backend/File.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Theme\Model\Design\Backend; use Magento\Config\Model\Config\Backend\File\RequestData\RequestDataInterface; @@ -103,21 +104,20 @@ public function beforeSave() return $this; } + //phpcs:ignore Magento2.Functions.DiscouragedFunction $this->updateMediaDirectory(basename($file), $value['url']); return $this; } /** - * After Load - * - * @return File - * @throws LocalizedException + * @inheritDoc */ public function afterLoad() { $value = $this->getValue(); if ($value && !is_array($value)) { + //phpcs:ignore Magento2.Functions.DiscouragedFunction $fileName = $this->_getUploadDir() . '/' . basename($value); $fileInfo = null; if ($this->_mediaDirectory->isExist($fileName)) { @@ -128,6 +128,7 @@ public function afterLoad() 'url' => $url, 'file' => $value, 'size' => is_array($stat) ? $stat['size'] : 0, + //phpcs:ignore Magento2.Functions.DiscouragedFunction 'name' => basename($value), 'type' => $this->getMimeType($fileName), 'exists' => true, @@ -237,7 +238,7 @@ private function getMime() */ private function getRelativeMediaPath(string $path): string { - return str_replace('/pub/media/', '', $path); + return preg_replace('/\/(pub\/)?media\//', '', $path); } /** diff --git a/app/code/Magento/Theme/Model/Design/Config/FileUploader/FileProcessor.php b/app/code/Magento/Theme/Model/Design/Config/FileUploader/FileProcessor.php index ecf5bcbea6dfc..901dbf7d88f3d 100644 --- a/app/code/Magento/Theme/Model/Design/Config/FileUploader/FileProcessor.php +++ b/app/code/Magento/Theme/Model/Design/Config/FileUploader/FileProcessor.php @@ -18,6 +18,8 @@ use Magento\Store\Model\StoreManagerInterface; /** + * Design file processor. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class FileProcessor @@ -79,8 +81,8 @@ public function __construct( * Save file to temp media directory * * @param string $fileId + * * @return array - * @throws LocalizedException */ public function saveToTmp($fileId) { diff --git a/app/code/Magento/Theme/Model/ResourceModel/Design.php b/app/code/Magento/Theme/Model/ResourceModel/Design.php index c36640b98ef40..6711f4a2117be 100644 --- a/app/code/Magento/Theme/Model/ResourceModel/Design.php +++ b/app/code/Magento/Theme/Model/ResourceModel/Design.php @@ -46,7 +46,7 @@ protected function _construct() * Perform actions before object save * * @param \Magento\Framework\Model\AbstractModel $object - * @return $this + * @return void * @throws \Magento\Framework\Exception\LocalizedException */ public function _beforeSave(\Magento\Framework\Model\AbstractModel $object) @@ -152,7 +152,6 @@ protected function _checkIntersection($storeId, $dateFrom, $dateTo, $currentId) $dateConditions = []; } - $condition = ''; if (!empty($dateConditions)) { $condition = '(' . implode(') OR (', $dateConditions) . ')'; $select->where($condition); diff --git a/app/code/Magento/Theme/Model/Wysiwyg/Storage.php b/app/code/Magento/Theme/Model/Wysiwyg/Storage.php index 2c3350e695a85..0519460c02423 100644 --- a/app/code/Magento/Theme/Model/Wysiwyg/Storage.php +++ b/app/code/Magento/Theme/Model/Wysiwyg/Storage.php @@ -4,17 +4,12 @@ * See COPYING.txt for license details. */ -/** - * Theme wysiwyg storage model - */ namespace Magento\Theme\Model\Wysiwyg; use Magento\Framework\App\Filesystem\DirectoryList; /** - * Class Storage - * - * @package Magento\Theme\Model\Wysiwyg + * Theme wysiwyg storage model */ class Storage { @@ -110,7 +105,7 @@ public function __construct( * Upload file * * @param string $targetPath - * @return bool + * @return array * @throws \Magento\Framework\Exception\LocalizedException */ public function uploadFile($targetPath) @@ -271,7 +266,7 @@ public function getFilesCollection() if (self::TYPE_IMAGE == $storageType) { $requestParams['file'] = $fileName; $file['thumbnailParams'] = $requestParams; - + //phpcs:ignore Generic.PHP.NoSilencedErrors $size = @getimagesize($path); if (is_array($size)) { $file['width'] = $size[0]; diff --git a/app/code/Magento/Theme/Test/Mftf/ActionGroup/StorefrontClickOnHeaderLogoActionGroup.xml b/app/code/Magento/Theme/Test/Mftf/ActionGroup/StorefrontClickOnHeaderLogoActionGroup.xml new file mode 100644 index 0000000000000..cd4117a4cfa6e --- /dev/null +++ b/app/code/Magento/Theme/Test/Mftf/ActionGroup/StorefrontClickOnHeaderLogoActionGroup.xml @@ -0,0 +1,16 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontClickOnHeaderLogoActionGroup"> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToHomePage"/> + <waitForPageLoad stepKey="waitForHomePageLoaded"/> + <click selector="{{StorefrontHeaderSection.logoLink}}" stepKey="clickOnLogo"/> + <waitForPageLoad stepKey="waitForHomePageLoadedAfterClickOnLogo"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Theme/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Theme/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..e826651062562 --- /dev/null +++ b/app/code/Magento/Theme/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,16 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuContentDesignThemes"> + <data key="pageTitle">Themes</data> + <data key="title">Themes</data> + <data key="dataUiId">magento-theme-system-design-theme</data> + </entity> +</entities> diff --git a/app/code/Magento/Theme/Test/Mftf/Section/StorefrontHeaderSection.xml b/app/code/Magento/Theme/Test/Mftf/Section/StorefrontHeaderSection.xml index a4088c7a4a0b7..e2f0a01fc733b 100644 --- a/app/code/Magento/Theme/Test/Mftf/Section/StorefrontHeaderSection.xml +++ b/app/code/Magento/Theme/Test/Mftf/Section/StorefrontHeaderSection.xml @@ -9,5 +9,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontHeaderSection"> <element name="welcomeMessage" type="text" selector=".greet.welcome"/> + <element name="logoLink" type="button" selector=".header .logo"/> </section> </sections> diff --git a/app/code/Magento/Theme/Test/Mftf/Test/AdminContentThemesNavigateMenuTest.xml b/app/code/Magento/Theme/Test/Mftf/Test/AdminContentThemesNavigateMenuTest.xml new file mode 100644 index 0000000000000..8e7bfd71b07d3 --- /dev/null +++ b/app/code/Magento/Theme/Test/Mftf/Test/AdminContentThemesNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminContentThemesNavigateMenuTest"> + <annotations> + <features value="Theme"/> + <stories value="Menu Navigation"/> + <title value="Admin content themes navigate menu test"/> + <description value="Admin should be able to navigate to Content > Themes"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14112"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToContentThemesPage"> + <argument name="menuUiId" value="{{AdminMenuContent.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuContentDesignThemes.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuContentDesignThemes.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Theme/etc/config.xml b/app/code/Magento/Theme/etc/config.xml index b44691c0df963..37841789c0e10 100644 --- a/app/code/Magento/Theme/etc/config.xml +++ b/app/code/Magento/Theme/etc/config.xml @@ -66,6 +66,11 @@ Disallow: /*SID= <static> <sign>1</sign> </static> + <dev> + <js> + <move_inline_to_bottom>0</move_inline_to_bottom> + </js> + </dev> </dev> </default> </config> diff --git a/app/code/Magento/Theme/etc/system.xml b/app/code/Magento/Theme/etc/system.xml new file mode 100644 index 0000000000000..4abc87e845122 --- /dev/null +++ b/app/code/Magento/Theme/etc/system.xml @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> + <system> + <section id="dev" translate="label" type="text" sortOrder="920" showInDefault="1" showInWebsite="1" showInStore="1"> + <group id="js" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="move_inline_to_bottom" translate="label" type="select" sortOrder="25" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <label>Move JS code to the bottom of the page</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + </field> + </group> + </section> + </system> +</config> diff --git a/app/code/Magento/Theme/view/adminhtml/layout/adminhtml_system_design_theme_block.xml b/app/code/Magento/Theme/view/adminhtml/layout/adminhtml_system_design_theme_block.xml index bb8fbd44629cf..e19460d56a89b 100644 --- a/app/code/Magento/Theme/view/adminhtml/layout/adminhtml_system_design_theme_block.xml +++ b/app/code/Magento/Theme/view/adminhtml/layout/adminhtml_system_design_theme_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="theme.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">theme_grid</argument> - <argument name="dataSource" xsi:type="object">Magento\Theme\Model\ResourceModel\Theme\Grid\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Theme\Model\ResourceModel\Theme\Grid\Collection</argument> <argument name="save_parameters_in_session" xsi:type="string">1</argument> <argument name="use_ajax" xsi:type="string">1</argument> <argument name="grid_url" xsi:type="url" path="*/*/grid"> diff --git a/app/code/Magento/Theme/view/frontend/templates/html/header/logo.phtml b/app/code/Magento/Theme/view/frontend/templates/html/header/logo.phtml index f719f5dd78307..9c34dfea3218b 100644 --- a/app/code/Magento/Theme/view/frontend/templates/html/header/logo.phtml +++ b/app/code/Magento/Theme/view/frontend/templates/html/header/logo.phtml @@ -12,7 +12,11 @@ ?> <?php $storeName = $block->getThemeName() ? $block->getThemeName() : $block->getLogoAlt();?> <span data-action="toggle-nav" class="action nav-toggle"><span><?= /* @escapeNotVerified */ __('Toggle Nav') ?></span></span> -<a class="logo" href="<?= $block->getUrl('') ?>" title="<?= /* @escapeNotVerified */ $storeName ?>"> +<a + class="logo" + href="<?= $block->getUrl('') ?>" + title="<?= /* @escapeNotVerified */ $storeName ?>" + aria-label="store logo"> <img src="<?= /* @escapeNotVerified */ $block->getLogoSrc() ?>" title="<?= $block->escapeHtmlAttr($block->getLogoAlt()) ?>" alt="<?= $block->escapeHtmlAttr($block->getLogoAlt()) ?>" diff --git a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridPaginationActionGroup.xml b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridPaginationActionGroup.xml index 9148c22976c19..fbb543a6cab92 100644 --- a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridPaginationActionGroup.xml +++ b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridPaginationActionGroup.xml @@ -23,8 +23,23 @@ </arguments> <click selector="{{AdminDataGridPaginationSection.perPageDropdown}}" stepKey="clickPerPageDropdown"/> <click selector="{{AdminDataGridPaginationSection.perPageOption('Custom')}}" stepKey="selectCustomPerPage"/> + <waitForElementVisible selector="{{AdminDataGridPaginationSection.perPageInput}}" time="30" stepKey="waitForInputVisible"/> <fillField selector="{{AdminDataGridPaginationSection.perPageInput}}" userInput="{{perPage}}" stepKey="fillCustomPerPage"/> <click selector="{{AdminDataGridPaginationSection.perPageApplyInput}}" stepKey="applyCustomPerPage"/> <waitForLoadingMaskToDisappear stepKey="waitForGridLoad"/> + <seeInField selector="{{AdminDataGridPaginationSection.perPageDropDownValue}}" userInput="{{perPage}}" stepKey="seePerPageValueInDropDown"/> + </actionGroup> + + <actionGroup name="adminDataGridDeleteCustomPerPage"> + <arguments> + <argument name="perPage"/> + </arguments> + <click selector="{{AdminDataGridPaginationSection.perPageDropdown}}" stepKey="clickPerPageDropdown1"/> + <click selector="{{AdminDataGridPaginationSection.perPageEditCustomValue(perPage)}}" stepKey="clickToEditCustomPerPage"/> + <waitForElementVisible selector="{{AdminDataGridPaginationSection.perPageDeleteCustomValue(perPage)}}" time="30" stepKey="waitForDeleteButtonVisible"/> + <click selector="{{AdminDataGridPaginationSection.perPageDeleteCustomValue(perPage)}}" stepKey="clickToDeleteCustomPerPage"/> + <waitForLoadingMaskToDisappear stepKey="waitForGridLoad"/> + <click selector="{{AdminDataGridPaginationSection.perPageDropdown}}" stepKey="clickPerPageDropdown"/> + <dontSeeElement selector="{{AdminDataGridPaginationSection.perPageDropDownItem(perPage)}}" stepKey="dontSeeDropDownItem"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridPaginationSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridPaginationSection.xml index 0f54f51549e7a..133836761174d 100644 --- a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridPaginationSection.xml +++ b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridPaginationSection.xml @@ -11,11 +11,15 @@ <section name="AdminDataGridPaginationSection"> <element name="perPageDropdown" type="select" selector=".admin__data-grid-pager-wrap .selectmenu"/> <element name="perPageOption" type="button" selector="//div[@class='admin__data-grid-pager-wrap']//div[@class='selectmenu-items _active']//li//button[text()='{{var1}}']" parameterized="true"/> - <element name="perPageInput" type="input" selector="//div[@class='admin__data-grid-pager-wrap']//div[@class='selectmenu-items _active']//li//div[@class='selectmenu-item-edit']//input"/> - <element name="perPageApplyInput" type="button" selector="//div[@class='admin__data-grid-pager-wrap']//div[@class='selectmenu-items _active']//li//div[@class='selectmenu-item-edit']//button"/> + <element name="perPageInput" type="input" selector="//div[contains(@class, 'admin__data-grid-pager-wrap')]//div[contains(@class, 'selectmenu-items _active')]//li[contains(@class, '_edit')]//div[contains(@class, 'selectmenu-item-edit')]//input"/> + <element name="perPageApplyInput" type="button" selector="//div[contains(@class, 'admin__data-grid-pager-wrap')]//div[contains(@class, 'selectmenu-items _active')]//li[@class='_edit']//div[contains(@class, 'selectmenu-item-edit')]//button"/> + <element name="perPageDropDownItem" type="button" selector="//*[contains(@class, 'selectmenu-items _active')]//button[contains(@class, 'selectmenu-item-action') and text()='{{dropDownItem}}']" timeout="30" parameterized="true"/> + <element name="perPageEditCustomValue" type="button" selector="//div[contains(@class, 'selectmenu-items _active')]//div[contains(@class, 'selectmenu-item')]//button[text()='{{perPageCustomValue}}']/following-sibling::button[contains(@class, 'action-edit')]" parameterized="true"/> + <element name="perPageDeleteCustomValue" type="button" selector="//div[contains(@class, 'selectmenu-items _active')]//div[contains(@class, 'selectmenu-item')]//button[text()='{{perPageCustomValue}}']/parent::div/preceding-sibling::div/button[contains(@class, 'action-delete')]" parameterized="true"/> <element name="nextPage" type="button" selector="div.admin__data-grid-pager > button.action-next" timeout="30"/> <element name="previousPage" type="button" selector="div.admin__data-grid-pager > button.action-previous" timeout="30"/> <element name="currentPage" type="input" selector="div.admin__data-grid-pager > input[data-ui-id='current-page-input']"/> <element name="totalPages" type="text" selector="div.admin__data-grid-pager > label"/> + <element name="perPageDropDownValue" type="input" selector=".selectmenu-value input" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Ui/etc/db_schema.xml b/app/code/Magento/Ui/etc/db_schema.xml index e2a04b0cdc72d..13a384024f18a 100644 --- a/app/code/Magento/Ui/etc/db_schema.xml +++ b/app/code/Magento/Ui/etc/db_schema.xml @@ -18,8 +18,8 @@ comment="Mark current bookmark per user and identifier"/> <column xsi:type="varchar" name="title" nullable="true" length="255" comment="Bookmark title"/> <column xsi:type="longtext" name="config" nullable="true" comment="Bookmark config"/> - <column xsi:type="datetime" name="created_at" on_update="false" nullable="false" comment="Bookmark created at"/> - <column xsi:type="datetime" name="updated_at" on_update="false" nullable="false" comment="Bookmark updated at"/> + <column xsi:type="datetime" name="created_at" on_update="false" nullable="false" default="CURRENT_TIMESTAMP" comment="Bookmark created at"/> + <column xsi:type="datetime" name="updated_at" on_update="true" nullable="false" default="CURRENT_TIMESTAMP" comment="Bookmark updated at"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="bookmark_id"/> </constraint> diff --git a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows-grid.js b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows-grid.js index dc6f8d930a144..17b2d1db4eb1b 100644 --- a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows-grid.js +++ b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows-grid.js @@ -33,6 +33,15 @@ define([ } }, + /** + * @inheritdoc + */ + initialize: function () { + this.setToInsertData = _.debounce(this.setToInsertData, 200); + + return this._super(); + }, + /** * Calls 'initObservable' of parent * diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/ui-select.js b/app/code/Magento/Ui/view/base/web/js/form/element/ui-select.js index dba0992c5ba52..4479cff5135dc 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/ui-select.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/ui-select.js @@ -131,6 +131,7 @@ define([ return Abstract.extend({ defaults: { options: [], + total: 0, listVisible: false, value: [], filterOptions: false, @@ -153,6 +154,7 @@ define([ labelsDecoration: false, disableLabel: false, filterRateLimit: 500, + filterRateLimitMethod: 'notifyAtFixedRate', closeBtnLabel: $t('Done'), optgroupTmpl: 'ui/grid/filters/elements/ui-select-optgroup', quantityPlaceholder: $t('options'), @@ -180,6 +182,7 @@ define([ debounce: 300, missingValuePlaceholder: $t('Entity with ID: %s doesn\'t exist'), isDisplayMissingValuePlaceholder: false, + currentSearchKey: '', listens: { listVisible: 'cleanHoveredElement', filterInputValue: 'filterOptionsList', @@ -330,7 +333,10 @@ define([ ]); this.filterInputValue.extend({ - rateLimit: this.filterRateLimit + rateLimit: { + timeout: this.filterRateLimit, + method: this.filterRateLimitMethod + } }); return this; @@ -460,7 +466,7 @@ define([ } if (this.searchOptions) { - return _.debounce(this.loadOptions.bind(this, value), this.debounce)(); + return this.loadOptions(value); } this.cleanHoveredElement(); @@ -547,11 +553,21 @@ define([ _setItemsQuantity: function (data) { if (this.showFilteredQuantity) { data || parseInt(data, 10) === 0 ? - this.itemsQuantity(data + ' ' + this.quantityPlaceholder) : + this.itemsQuantity(this.getItemsPlaceholder(data)) : this.itemsQuantity(''); } }, + /** + * Return formatted items placeholder. + * + * @param {Object} data - option data + * @returns {String} + */ + getItemsPlaceholder: function (data) { + return data + ' ' + this.quantityPlaceholder; + }, + /** * Remove element from selected array */ @@ -1234,13 +1250,11 @@ define([ * @param {Number} page */ processRequest: function (searchKey, page) { - var total = 0, - existingOptions = this.options(); - this.loading(true); + this.currentSearchKey = searchKey; $.ajax({ url: this.searchUrl, - type: 'post', + type: 'get', dataType: 'json', context: this, data: { @@ -1248,27 +1262,39 @@ define([ page: page, limit: this.pageLimit }, + success: $.proxy(this.success, this), + error: $.proxy(this.error, this), + beforeSend: $.proxy(this.beforeSend, this), + complete: $.proxy(this.complete, this, searchKey, page) + }); + }, - /** @param {Object} response */ - success: function (response) { - _.each(response.options, function (opt) { - existingOptions.push(opt); - }); - total = response.total; - this.options(existingOptions); - }, - - /** set empty array if error occurs */ - error: function () { - this.options([]); - }, + /** @param {Object} response */ + success: function (response) { + var existingOptions = this.options(); - /** cache options and stop loading*/ - complete: function () { - this.setCachedSearchResults(searchKey, this.options(), page, total); - this.afterLoadOptions(searchKey, page, total); - } + _.each(response.options, function (opt) { + existingOptions.push(opt); }); + + this.total = response.total; + this.options(existingOptions); + }, + + /** add actions before ajax request */ + beforeSend: function () { + + }, + + /** set empty array if error occurs */ + error: function () { + this.options([]); + }, + + /** cache options and stop loading*/ + complete: function (searchKey, page) { + this.setCachedSearchResults(searchKey, this.options(), page, this.total); + this.afterLoadOptions(searchKey, page, this.total); }, /** @@ -1279,9 +1305,9 @@ define([ * @param {Number} total */ afterLoadOptions: function (searchKey, page, total) { - this._setItemsQuantity(total); - this.lastSearchPage = page; this.lastSearchKey = searchKey; + this.lastSearchPage = page; + this._setItemsQuantity(total); this.loading(false); } }); diff --git a/app/code/Magento/Ui/view/base/web/js/grid/paging/paging.js b/app/code/Magento/Ui/view/base/web/js/grid/paging/paging.js index abd79e797e413..8e6f1496495c7 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/paging/paging.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/paging/paging.js @@ -19,7 +19,9 @@ define([ defaults: { template: 'ui/grid/paging/paging', totalTmpl: 'ui/grid/paging-total', + totalRecords: 0, pageSize: 20, + pages: 1, current: 1, selectProvider: 'ns = ${ $.ns }, index = ids', @@ -35,7 +37,8 @@ define([ imports: { pageSize: '${ $.sizesConfig.name }:value', totalSelected: '${ $.selectProvider }:totalSelected', - totalRecords: '${ $.provider }:data.totalRecords' + totalRecords: '${ $.provider }:data.totalRecords', + filters: '${ $.provider }:params.filters' }, exports: { @@ -43,6 +46,11 @@ define([ current: '${ $.provider }:params.paging.current' }, + statefull: { + pageSize: true, + current: true + }, + listens: { 'pages': 'onPagesChange', 'pageSize': 'onPageSizeChange', @@ -173,7 +181,9 @@ define([ * @returns {Paging} Chainable. */ goFirst: function () { - this.current = 1; + if (!_.isUndefined(this.filters)) { + this.current = 1; + } return this; }, @@ -219,13 +229,11 @@ define([ /** * Calculates new page cursor based on the * previous and current page size values. - * - * @returns {Number} Updated cursor value. */ updateCursor: function () { var cursor = this.current - 1, size = this.pageSize, - oldSize = this.previousSize, + oldSize = _.isUndefined(this.previousSize) ? this.pageSize : this.previousSize, delta = cursor * (oldSize - size) / size; delta = size > oldSize ? diff --git a/app/code/Magento/Ui/view/base/web/js/grid/search/search.js b/app/code/Magento/Ui/view/base/web/js/grid/search/search.js index 999e3262dbbdd..ce53b23b79e11 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/search/search.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/search/search.js @@ -11,8 +11,9 @@ define([ 'uiLayout', 'mage/translate', 'mageUtils', - 'uiElement' -], function (_, layout, $t, utils, Element) { + 'uiElement', + 'jquery' +], function (_, layout, $t, utils, Element, $) { 'use strict'; return Element.extend({ @@ -29,11 +30,13 @@ define([ tracks: { value: true, previews: true, - inputValue: true + inputValue: true, + focused: true }, imports: { inputValue: 'value', - updatePreview: 'value' + updatePreview: 'value', + focused: false }, exports: { value: '${ $.provider }:params.search' @@ -88,6 +91,18 @@ define([ return this; }, + /** + * Click To ScrollTop. + */ + scrollTo: function ($data) { + $('html, body').animate({ + scrollTop: 0 + }, 'slow', function () { + $data.focused = false; + $data.focused = true; + }); + }, + /** * Resets input value to the last applied state. * diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/search/search.html b/app/code/Magento/Ui/view/base/web/templates/grid/search/search.html index 13b82a93eca25..fcad729a95fbb 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/search/search.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/search/search.html @@ -5,7 +5,7 @@ */ --> <div class="data-grid-search-control-wrap"> - <label class="data-grid-search-label" attr="title: $t('Search'), for: index"> + <label class="data-grid-search-label" attr="title: $t('Search'), for: index" data-bind="click: scrollTo"> <span translate="'Search'"/> </label> <input class="admin__control-text data-grid-search-control" type="text" @@ -16,6 +16,7 @@ placeholder: $t(placeholder) }, textInput: inputValue, + hasFocus: focused, keyboard: { 13: apply.bind($data, false), 27: cancel diff --git a/app/code/Magento/Ui/view/frontend/web/templates/form/element/helper/tooltip.html b/app/code/Magento/Ui/view/frontend/web/templates/form/element/helper/tooltip.html index 3d4f9eefe5afa..f8d1cbbcad5c1 100644 --- a/app/code/Magento/Ui/view/frontend/web/templates/form/element/helper/tooltip.html +++ b/app/code/Magento/Ui/view/frontend/web/templates/form/element/helper/tooltip.html @@ -13,14 +13,20 @@ data-bind="attr: {href: tooltip.link}, mageInit: {'dropdown':{'activeClass': '_active'}}"></a> <!-- /ko --> + <span id="tooltip-label" class="label"><!-- ko i18n: 'Tooltip' --><!-- /ko --></span> <!-- ko if: (!tooltip.link)--> - <span class="field-tooltip-action action-help" - tabindex="0" - data-toggle="dropdown" - data-bind="mageInit: {'dropdown':{'activeClass': '_active'}}"></span> + <span + id="tooltip" + class="field-tooltip-action action-help" + tabindex="0" + data-toggle="dropdown" + data-bind="mageInit: {'dropdown':{'activeClass': '_active', 'parent': '.field-tooltip.toggle'}}" + aria-labelledby="tooltip-label" + > + </span> <!-- /ko --> - <div class="field-tooltip-content" + <div class="field-tooltip-content" data-target="dropdown" translate="tooltip.description"> </div> </div> diff --git a/app/code/Magento/Ups/Model/Carrier.php b/app/code/Magento/Ups/Model/Carrier.php index 9cb1fe615aa42..0e2ce05f2d079 100644 --- a/app/code/Magento/Ups/Model/Carrier.php +++ b/app/code/Magento/Ups/Model/Carrier.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Ups\Model; @@ -454,7 +455,7 @@ protected function _getCgiQuotes() { $rowRequest = $this->_rawRequest; if (self::USA_COUNTRY_ID == $rowRequest->getDestCountry()) { - $destPostal = substr($rowRequest->getDestPostal(), 0, 5); + $destPostal = substr((string)$rowRequest->getDestPostal(), 0, 5); } else { $destPostal = $rowRequest->getDestPostal(); } @@ -472,7 +473,7 @@ protected function _getCgiQuotes() '47_rate_chart' => $rowRequest->getPickup(), '48_container' => $rowRequest->getContainer(), '49_residential' => $rowRequest->getDestType(), - 'weight_std' => strtolower($rowRequest->getUnitMeasure()), + 'weight_std' => strtolower((string)$rowRequest->getUnitMeasure()), ]; $params['47_rate_chart'] = $params['47_rate_chart']['label']; @@ -536,7 +537,7 @@ protected function _parseCgiResponse($response) $priceArr = []; if (strlen(trim($response)) > 0) { $rRows = explode("\n", $response); - $allowedMethods = explode(",", $this->getConfigData('allowed_methods')); + $allowedMethods = explode(",", (string)$this->getConfigData('allowed_methods')); foreach ($rRows as $rRow) { $row = explode('%', $rRow); switch (substr($row[0], -1)) { @@ -612,7 +613,7 @@ protected function _getXmlQuotes() $rowRequest = $this->_rawRequest; if (self::USA_COUNTRY_ID == $rowRequest->getDestCountry()) { - $destPostal = substr($rowRequest->getDestPostal(), 0, 5); + $destPostal = substr((string)$rowRequest->getDestPostal(), 0, 5); } else { $destPostal = $rowRequest->getDestPostal(); } @@ -832,76 +833,15 @@ protected function _parseXmlResponse($xmlResponse) $allowedCurrencies = $this->_currencyFactory->create()->getConfigAllowCurrencies(); foreach ($arr as $shipElement) { - $code = (string)$shipElement->Service->Code; - if (in_array($code, $allowedMethods)) { - //The location of tax information is in a different place - // depending on whether we are using negotiated rates or not - if ($negotiatedActive) { - $includeTaxesArr = $xml->getXpath( - "//RatingServiceSelectionResponse/RatedShipment/NegotiatedRates" - . "/NetSummaryCharges/TotalChargesWithTaxes" - ); - $includeTaxesActive = $this->getConfigFlag('include_taxes') && !empty($includeTaxesArr); - if ($includeTaxesActive) { - $cost = $shipElement->NegotiatedRates - ->NetSummaryCharges - ->TotalChargesWithTaxes - ->MonetaryValue; - - $responseCurrencyCode = $this->mapCurrencyCode( - (string)$shipElement->NegotiatedRates - ->NetSummaryCharges - ->TotalChargesWithTaxes - ->CurrencyCode - ); - } else { - $cost = $shipElement->NegotiatedRates->NetSummaryCharges->GrandTotal->MonetaryValue; - $responseCurrencyCode = $this->mapCurrencyCode( - (string)$shipElement->NegotiatedRates->NetSummaryCharges->GrandTotal->CurrencyCode - ); - } - } else { - $includeTaxesArr = $xml->getXpath( - "//RatingServiceSelectionResponse/RatedShipment/TotalChargesWithTaxes" - ); - $includeTaxesActive = $this->getConfigFlag('include_taxes') && !empty($includeTaxesArr); - if ($includeTaxesActive) { - $cost = $shipElement->TotalChargesWithTaxes->MonetaryValue; - $responseCurrencyCode = $this->mapCurrencyCode( - (string)$shipElement->TotalChargesWithTaxes->CurrencyCode - ); - } else { - $cost = $shipElement->TotalCharges->MonetaryValue; - $responseCurrencyCode = $this->mapCurrencyCode( - (string)$shipElement->TotalCharges->CurrencyCode - ); - } - } - - //convert price with Origin country currency code to base currency code - $successConversion = true; - if ($responseCurrencyCode) { - if (in_array($responseCurrencyCode, $allowedCurrencies)) { - $cost = (double)$cost * $this->_getBaseCurrencyRate($responseCurrencyCode); - } else { - $errorTitle = __( - 'We can\'t convert a rate from "%1-%2".', - $responseCurrencyCode, - $this->_request->getPackageCurrency()->getCode() - ); - $error = $this->_rateErrorFactory->create(); - $error->setCarrier('ups'); - $error->setCarrierTitle($this->getConfigData('title')); - $error->setErrorMessage($errorTitle); - $successConversion = false; - } - } - - if ($successConversion) { - $costArr[$code] = $cost; - $priceArr[$code] = $this->getMethodPrice((float)$cost, $code); - } - } + $this->processShippingRateForItem( + $shipElement, + $allowedMethods, + $allowedCurrencies, + $costArr, + $priceArr, + $negotiatedActive, + $xml + ); } } else { $arr = $xml->getXpath("//RatingServiceSelectionResponse/Response/Error/ErrorDescription/text()"); @@ -944,6 +884,99 @@ protected function _parseXmlResponse($xmlResponse) return $result; } + /** + * Processing rate for ship element + * + * @param \Magento\Framework\Simplexml\Element $shipElement + * @param array $allowedMethods + * @param array $allowedCurrencies + * @param array $costArr + * @param array $priceArr + * @param bool $negotiatedActive + * @param \Magento\Framework\Simplexml\Config $xml + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + private function processShippingRateForItem( + \Magento\Framework\Simplexml\Element $shipElement, + array $allowedMethods, + array $allowedCurrencies, + array &$costArr, + array &$priceArr, + bool $negotiatedActive, + \Magento\Framework\Simplexml\Config $xml + ): void { + $code = (string)$shipElement->Service->Code; + if (in_array($code, $allowedMethods)) { + //The location of tax information is in a different place + // depending on whether we are using negotiated rates or not + if ($negotiatedActive) { + $includeTaxesArr = $xml->getXpath( + "//RatingServiceSelectionResponse/RatedShipment/NegotiatedRates" + . "/NetSummaryCharges/TotalChargesWithTaxes" + ); + $includeTaxesActive = $this->getConfigFlag('include_taxes') && !empty($includeTaxesArr); + if ($includeTaxesActive) { + $cost = $shipElement->NegotiatedRates + ->NetSummaryCharges + ->TotalChargesWithTaxes + ->MonetaryValue; + + $responseCurrencyCode = $this->mapCurrencyCode( + (string)$shipElement->NegotiatedRates + ->NetSummaryCharges + ->TotalChargesWithTaxes + ->CurrencyCode + ); + } else { + $cost = $shipElement->NegotiatedRates->NetSummaryCharges->GrandTotal->MonetaryValue; + $responseCurrencyCode = $this->mapCurrencyCode( + (string)$shipElement->NegotiatedRates->NetSummaryCharges->GrandTotal->CurrencyCode + ); + } + } else { + $includeTaxesArr = $xml->getXpath( + "//RatingServiceSelectionResponse/RatedShipment/TotalChargesWithTaxes" + ); + $includeTaxesActive = $this->getConfigFlag('include_taxes') && !empty($includeTaxesArr); + if ($includeTaxesActive) { + $cost = $shipElement->TotalChargesWithTaxes->MonetaryValue; + $responseCurrencyCode = $this->mapCurrencyCode( + (string)$shipElement->TotalChargesWithTaxes->CurrencyCode + ); + } else { + $cost = $shipElement->TotalCharges->MonetaryValue; + $responseCurrencyCode = $this->mapCurrencyCode( + (string)$shipElement->TotalCharges->CurrencyCode + ); + } + } + + //convert price with Origin country currency code to base currency code + $successConversion = true; + if ($responseCurrencyCode) { + if (in_array($responseCurrencyCode, $allowedCurrencies)) { + $cost = (double)$cost * $this->_getBaseCurrencyRate($responseCurrencyCode); + } else { + $errorTitle = __( + 'We can\'t convert a rate from "%1-%2".', + $responseCurrencyCode, + $this->_request->getPackageCurrency()->getCode() + ); + $error = $this->_rateErrorFactory->create(); + $error->setCarrier('ups'); + $error->setCarrierTitle($this->getConfigData('title')); + $error->setErrorMessage($errorTitle); + $successConversion = false; + } + } + + if ($successConversion) { + $costArr[$code] = $cost; + $priceArr[$code] = $this->getMethodPrice((float)$cost, $code); + } + } + } + /** * Get tracking * @@ -1100,54 +1133,7 @@ protected function _parseXmlTrackingResponse($trackingValue, $xmlResponse) if ($activityTags) { $index = 1; foreach ($activityTags as $activityTag) { - $addressArr = []; - if (isset($activityTag->ActivityLocation->Address->City)) { - $addressArr[] = (string)$activityTag->ActivityLocation->Address->City; - } - if (isset($activityTag->ActivityLocation->Address->StateProvinceCode)) { - $addressArr[] = (string)$activityTag->ActivityLocation->Address->StateProvinceCode; - } - if (isset($activityTag->ActivityLocation->Address->CountryCode)) { - $addressArr[] = (string)$activityTag->ActivityLocation->Address->CountryCode; - } - $dateArr = []; - $date = (string)$activityTag->Date; - //YYYYMMDD - $dateArr[] = substr($date, 0, 4); - $dateArr[] = substr($date, 4, 2); - $dateArr[] = substr($date, -2, 2); - - $timeArr = []; - $time = (string)$activityTag->Time; - //HHMMSS - $timeArr[] = substr($time, 0, 2); - $timeArr[] = substr($time, 2, 2); - $timeArr[] = substr($time, -2, 2); - - if ($index === 1) { - $resultArr['status'] = (string)$activityTag->Status->StatusType->Description; - $resultArr['deliverydate'] = implode('-', $dateArr); - //YYYY-MM-DD - $resultArr['deliverytime'] = implode(':', $timeArr); - //HH:MM:SS - $resultArr['deliverylocation'] = (string)$activityTag->ActivityLocation->Description; - $resultArr['signedby'] = (string)$activityTag->ActivityLocation->SignedForByName; - if ($addressArr) { - $resultArr['deliveryto'] = implode(', ', $addressArr); - } - } else { - $tempArr = []; - $tempArr['activity'] = (string)$activityTag->Status->StatusType->Description; - $tempArr['deliverydate'] = implode('-', $dateArr); - //YYYY-MM-DD - $tempArr['deliverytime'] = implode(':', $timeArr); - //HH:MM:SS - if ($addressArr) { - $tempArr['deliverylocation'] = implode(', ', $addressArr); - } - $packageProgress[] = $tempArr; - } - $index++; + $this->processActivityTagInfo($activityTag, $index, $resultArr, $packageProgress); } $resultArr['progressdetail'] = $packageProgress; } @@ -1180,6 +1166,70 @@ protected function _parseXmlTrackingResponse($trackingValue, $xmlResponse) return $this->_result; } + /** + * Process tracking info from activity tag + * + * @param \Magento\Framework\Simplexml\Element $activityTag + * @param int $index + * @param array $resultArr + * @param array $packageProgress + */ + private function processActivityTagInfo( + \Magento\Framework\Simplexml\Element $activityTag, + int &$index, + array &$resultArr, + array &$packageProgress + ) { + $addressArr = []; + if (isset($activityTag->ActivityLocation->Address->City)) { + $addressArr[] = (string)$activityTag->ActivityLocation->Address->City; + } + if (isset($activityTag->ActivityLocation->Address->StateProvinceCode)) { + $addressArr[] = (string)$activityTag->ActivityLocation->Address->StateProvinceCode; + } + if (isset($activityTag->ActivityLocation->Address->CountryCode)) { + $addressArr[] = (string)$activityTag->ActivityLocation->Address->CountryCode; + } + $dateArr = []; + $date = (string)$activityTag->Date; + //YYYYMMDD + $dateArr[] = substr($date, 0, 4); + $dateArr[] = substr($date, 4, 2); + $dateArr[] = substr($date, -2, 2); + + $timeArr = []; + $time = (string)$activityTag->Time; + //HHMMSS + $timeArr[] = substr($time, 0, 2); + $timeArr[] = substr($time, 2, 2); + $timeArr[] = substr($time, -2, 2); + + if ($index === 1) { + $resultArr['status'] = (string)$activityTag->Status->StatusType->Description; + $resultArr['deliverydate'] = implode('-', $dateArr); + //YYYY-MM-DD + $resultArr['deliverytime'] = implode(':', $timeArr); + //HH:MM:SS + $resultArr['deliverylocation'] = (string)$activityTag->ActivityLocation->Description; + $resultArr['signedby'] = (string)$activityTag->ActivityLocation->SignedForByName; + if ($addressArr) { + $resultArr['deliveryto'] = implode(', ', $addressArr); + } + } else { + $tempArr = []; + $tempArr['activity'] = (string)$activityTag->Status->StatusType->Description; + $tempArr['deliverydate'] = implode('-', $dateArr); + //YYYY-MM-DD + $tempArr['deliverytime'] = implode(':', $timeArr); + //HH:MM:SS + if ($addressArr) { + $tempArr['deliverylocation'] = implode(', ', $addressArr); + } + $packageProgress[] = $tempArr; + } + $index++; + } + /** * Get tracking response * @@ -1478,6 +1528,7 @@ protected function _sendShipmentAcceptRequest(Element $shipmentConfirmResponse) $shippingLabelContent = (string)$response->ShipmentResults->PackageResults->LabelImage->GraphicImage; $trackingNumber = (string)$response->ShipmentResults->PackageResults->TrackingNumber; + // phpcs:ignore Magento2.Functions.DiscouragedFunction $result->setShippingLabelContent(base64_decode($shippingLabelContent)); $result->setTrackingNumber($trackingNumber); } diff --git a/app/code/Magento/Ups/Test/Mftf/Data/ShippingMethodsData.xml b/app/code/Magento/Ups/Test/Mftf/Data/ShippingMethodsData.xml new file mode 100644 index 0000000000000..d4156d4f3358b --- /dev/null +++ b/app/code/Magento/Ups/Test/Mftf/Data/ShippingMethodsData.xml @@ -0,0 +1,17 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="ShippingMethodsUpsTypeSetDefault" type="shipping_methods_ups_type_config"> + <requiredEntity type="ups_type_inherit">ShippingMethodsUpsTypeDefault</requiredEntity> + </entity> + <entity name="ShippingMethodsUpsTypeDefault" type="ups_type_inherit"> + <data key="inherit">true</data> + </entity> +</entities> diff --git a/app/code/Magento/Ups/Test/Mftf/Metadata/shipping_methods_ups_type_config-meta.xml b/app/code/Magento/Ups/Test/Mftf/Metadata/shipping_methods_ups_type_config-meta.xml new file mode 100644 index 0000000000000..d642b7923282e --- /dev/null +++ b/app/code/Magento/Ups/Test/Mftf/Metadata/shipping_methods_ups_type_config-meta.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="ShippingMethodsUpsTypeConfig" dataType="shipping_methods_ups_type_config" type="create" auth="adminFormKey" url="admin/system_config/save/section/carriers/" method="POST"> + <object key="groups" dataType="shipping_methods_ups_type_config"> + <object key="ups" dataType="shipping_methods_ups_type_config"> + <object key="fields" dataType="shipping_methods_ups_type_config"> + <object key="type" dataType="ups_type_inherit"> + <field key="inherit">boolean</field> + </object> + </object> + </object> + </object> + </operation> +</operations> diff --git a/app/code/Magento/Ups/Test/Mftf/Page/AdminShippingMethodsConfigPage.xml b/app/code/Magento/Ups/Test/Mftf/Page/AdminShippingMethodsConfigPage.xml new file mode 100644 index 0000000000000..ebc44aace6dfb --- /dev/null +++ b/app/code/Magento/Ups/Test/Mftf/Page/AdminShippingMethodsConfigPage.xml @@ -0,0 +1,14 @@ +<?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="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminShippingMethodsConfigPage" url="admin/system_config/edit/section/carriers/" area="admin" module="Magento_Ups"> + <section name="AdminShippingMethodsUpsSection"/> + </page> +</pages> diff --git a/app/code/Magento/Ups/Test/Mftf/Section/AdminShippingMethodsUpsSection.xml b/app/code/Magento/Ups/Test/Mftf/Section/AdminShippingMethodsUpsSection.xml new file mode 100644 index 0000000000000..4107f17dbc18c --- /dev/null +++ b/app/code/Magento/Ups/Test/Mftf/Section/AdminShippingMethodsUpsSection.xml @@ -0,0 +1,16 @@ +<?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="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminShippingMethodsUpsSection"> + <element name="carriersUpsTab" type="button" selector="#carriers_ups-head"/> + <element name="carriersUpsType" type="select" selector="#carriers_ups_type"/> + <element name="selectedUpsType" type="text" selector="#carriers_ups_type option[selected]"/> + </section> +</sections> diff --git a/app/code/Magento/Ups/Test/Mftf/Test/DefaultConfigForUPSTypeTest.xml b/app/code/Magento/Ups/Test/Mftf/Test/DefaultConfigForUPSTypeTest.xml new file mode 100644 index 0000000000000..51db704a7abc7 --- /dev/null +++ b/app/code/Magento/Ups/Test/Mftf/Test/DefaultConfigForUPSTypeTest.xml @@ -0,0 +1,45 @@ +<?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="DefaultConfigForUPSTypeTest"> + <annotations> + <title value="Default Configuration for UPS Type"/> + <description value="Default Configuration for UPS Type"/> + <features value="Ups"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-99012"/> + <useCaseId value="MAGETWO-98947"/> + <group value="ups"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <!--Collapse UPS tab and logout--> + <comment userInput="Collapse UPS tab and logout" stepKey="collapseTabAndLogout"/> + <click selector="{{AdminShippingMethodsUpsSection.carriersUpsTab}}" stepKey="collapseTab"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Set shipping methods UPS type to default --> + <comment userInput="Set shipping methods UPS type to default" stepKey="setToDefaultShippingMethodsUpsType"/> + <createData entity="ShippingMethodsUpsTypeSetDefault" stepKey="setShippingMethodsUpsTypeToDefault"/> + <!-- Navigate to Stores -> Configuration -> Sales -> Shipping Methods Page --> + <comment userInput="Navigate to Stores -> Configuration -> Sales -> Shipping Methods Page" stepKey="goToAdminShippingMethodsPage"/> + <amOnPage url="{{AdminShippingMethodsConfigPage.url}}" stepKey="navigateToAdminShippingMethodsPage"/> + <waitForPageLoad stepKey="waitPageToLoad"/> + <!-- Expand 'UPS' tab --> + <comment userInput="Expand UPS tab" stepKey="expandUpsTab"/> + <conditionalClick selector="{{AdminShippingMethodsUpsSection.carriersUpsTab}}" dependentSelector="{{AdminShippingMethodsUpsSection.carriersUpsType}}" visible="false" stepKey="expandTab"/> + <waitForElementVisible selector="{{AdminShippingMethodsUpsSection.carriersUpsType}}" stepKey="waitTabToExpand"/> + <!-- Assert that selected UPS type by default is 'United Parcel Service XML' --> + <comment userInput="Check that selected UPS type by default is 'United Parcel Service XML'" stepKey="assertDefUpsType"/> + <grabTextFrom selector="{{AdminShippingMethodsUpsSection.selectedUpsType}}" stepKey="grabSelectedOptionText"/> + <assertEquals expected='United Parcel Service XML' expectedType="string" actual="($grabSelectedOptionText)" stepKey="assertDefaultUpsType"/> + </test> +</tests> diff --git a/app/code/Magento/Ups/etc/config.xml b/app/code/Magento/Ups/etc/config.xml index 791b325c65e3f..73b10dd5ff41b 100644 --- a/app/code/Magento/Ups/etc/config.xml +++ b/app/code/Magento/Ups/etc/config.xml @@ -37,7 +37,7 @@ <negotiated_active>0</negotiated_active> <include_taxes>0</include_taxes> <mode_xml>1</mode_xml> - <type>UPS</type> + <type>UPS_XML</type> <is_account_live>0</is_account_live> <active_rma>0</active_rma> <is_online>1</is_online> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..66eb3c9ba9f46 --- /dev/null +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,16 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuMarketingSEOAndSearchURLRewrites"> + <data key="pageTitle">URL Rewrites</data> + <data key="title">URL Rewrites</data> + <data key="dataUiId">magento-urlrewrite-urlrewrite</data> + </entity> +</entities> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesInCatalogCategoriesAfterChangingUrlKeyForStoreViewAndMovingCategoryTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesInCatalogCategoriesAfterChangingUrlKeyForStoreViewAndMovingCategoryTest.xml index ee3682c8ad4d7..52dce4d67f698 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesInCatalogCategoriesAfterChangingUrlKeyForStoreViewAndMovingCategoryTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesInCatalogCategoriesAfterChangingUrlKeyForStoreViewAndMovingCategoryTest.xml @@ -40,6 +40,7 @@ <deleteData createDataKey="createSecondCategory" stepKey="deleteSecondCategory"/> <deleteData createDataKey="createFirstSimpleProduct" stepKey="deleteFirstSimpleProduct"/> <deleteData createDataKey="createSecondSimpleProduct" stepKey="deleteSecondSimpleProduct"/> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView"/> <actionGroup ref="logout" stepKey="logout"/> </after> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminMarketingUrlRewritesNavigateMenuTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminMarketingUrlRewritesNavigateMenuTest.xml new file mode 100644 index 0000000000000..443307b427b42 --- /dev/null +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminMarketingUrlRewritesNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminMarketingUrlRewritesNavigateMenuTest"> + <annotations> + <features value="UrlRewrite"/> + <stories value="Menu Navigation"/> + <title value="Admin marketing url rewrites navigate menu test"/> + <description value="Admin should be able to navigate to Marketing > URL Rewrites"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14202"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToMarketingURLRewritesPage"> + <argument name="menuUiId" value="{{AdminMenuMarketing.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuMarketingSEOAndSearchURLRewrites.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuMarketingSEOAndSearchURLRewrites.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/UrlRewrite/view/adminhtml/layout/adminhtml_url_rewrite_index.xml b/app/code/Magento/UrlRewrite/view/adminhtml/layout/adminhtml_url_rewrite_index.xml index de8575178d06d..012409a8b6aca 100644 --- a/app/code/Magento/UrlRewrite/view/adminhtml/layout/adminhtml_url_rewrite_index.xml +++ b/app/code/Magento/UrlRewrite/view/adminhtml/layout/adminhtml_url_rewrite_index.xml @@ -12,7 +12,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.block.url_rewrite.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">urlrewriteGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollection</argument> <argument name="default_sort" xsi:type="string">url_rewrite_id</argument> <!-- Add below argument to save session parameter in URL rewrite grid --> <argument name="save_parameters_in_session" xsi:type="string">1</argument> diff --git a/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls b/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls index 5aea482a0fe02..e9033880704ca 100644 --- a/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls @@ -2,7 +2,7 @@ # See COPYING.txt for license details. type Query { - urlResolver(url: String!): EntityUrl @resolver(class: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\EntityUrl") @doc(description: "The urlResolver query returns the relative URL for a specified product, category or CMS page") + urlResolver(url: String!): EntityUrl @resolver(class: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\EntityUrl") @doc(description: "The urlResolver query returns the relative URL for a specified product, category or CMS page") @cache(cacheable: false) } type EntityUrl @doc(description: "EntityUrl is an output object containing the `id`, `relative_url`, and `type` attributes") { diff --git a/app/code/Magento/User/Model/User.php b/app/code/Magento/User/Model/User.php index 2994ac351fc78..d8040b0bbaaac 100644 --- a/app/code/Magento/User/Model/User.php +++ b/app/code/Magento/User/Model/User.php @@ -212,9 +212,14 @@ protected function _construct() * Removing dependencies and leaving only entity's properties. * * @return string[] + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $properties = parent::__sleep(); return array_diff( $properties, @@ -240,9 +245,14 @@ public function __sleep() * Restoring required objects after serialization. * * @return void + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + parent::__wakeup(); $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); $this->serializer = $objectManager->get(Json::class); @@ -406,6 +416,10 @@ public function getRoles() */ public function getRole() { + if ($this->getData('extracted_role')) { + $this->_role = $this->getData('extracted_role'); + $this->unsetData('extracted_role'); + } if (null === $this->_role) { $this->_role = $this->_roleFactory->create(); $roles = $this->getRoles(); diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminClickSaveButtonOnUserFormActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminClickSaveButtonOnUserFormActionGroup.xml new file mode 100644 index 0000000000000..e1edb16aba6ea --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminClickSaveButtonOnUserFormActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminClickSaveButtonOnUserFormActionGroup"> + <click selector="{{AdminNewUserFormSection.save}}" stepKey="saveNewUser"/> + <waitForPageLoad stepKey="waitForSaveResultLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminCreateUserActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminCreateUserActionGroup.xml index 303713132d2b0..5d51dcc610f78 100644 --- a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminCreateUserActionGroup.xml +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminCreateUserActionGroup.xml @@ -39,7 +39,7 @@ <argument name="role"/> <argument name="user" defaultValue="newAdmin"/> </arguments> - <amOnPage url="{{AdminEditUserPage.url}}" stepKey="navigateToNewUser"/> + <amOnPage url="{{AdminNewUserPage.url}}" stepKey="navigateToNewUser"/> <waitForPageLoad stepKey="waitForUsersPage" /> <fillField selector="{{AdminCreateUserSection.usernameTextField}}" userInput="{{user.username}}" stepKey="enterUserName" /> <fillField selector="{{AdminCreateUserSection.firstNameTextField}}" userInput="{{user.firstName}}" stepKey="enterFirstName" /> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminFillForgotPasswordFormActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminFillForgotPasswordFormActionGroup.xml new file mode 100644 index 0000000000000..01be51e72ec6d --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminFillForgotPasswordFormActionGroup.xml @@ -0,0 +1,17 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminFillForgotPasswordFormActionGroup"> + <arguments> + <argument name="email" type="string"/> + </arguments> + + <fillField selector="{{AdminForgotPasswordFormSection.email}}" userInput="{{email}}" stepKey="fillAdminEmail"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminFillNewUserFormRequiredFieldsActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminFillNewUserFormRequiredFieldsActionGroup.xml new file mode 100644 index 0000000000000..87bf1e003931a --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminFillNewUserFormRequiredFieldsActionGroup.xml @@ -0,0 +1,31 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminFillNewUserFormRequiredFieldsActionGroup"> + <arguments> + <argument name="user" type="entity" /> + </arguments> + <fillField selector="{{AdminNewUserFormSection.username}}" userInput="{{user.username}}" stepKey="fillUser"/> + <fillField selector="{{AdminNewUserFormSection.firstname}}" userInput="{{user.firstname}}" stepKey="fillFirstName"/> + <fillField selector="{{AdminNewUserFormSection.lastname}}" userInput="{{user.lastname}}" stepKey="fillLastName"/> + <fillField selector="{{AdminNewUserFormSection.email}}" userInput="{{user.email}}" stepKey="fillEmail"/> + <fillField selector="{{AdminNewUserFormSection.password}}" userInput="{{user.password}}" stepKey="fillPassword"/> + <fillField selector="{{AdminNewUserFormSection.passwordConfirmation}}" userInput="{{user.password_confirmation}}" stepKey="fillPasswordConfirmation"/> + <fillField selector="{{AdminNewUserFormSection.currentPassword}}" userInput="{{user.current_password}}" stepKey="fillCurrentUserPassword"/> + <scrollToTopOfPage stepKey="scrollToTopOfPage"/> + <click selector="{{AdminNewUserFormSection.userRoleTab}}" stepKey="openUserRoleTab"/> + <waitForPageLoad stepKey="waitForUserRoleTabOpened" /> + <click selector="{{AdminNewUserFormSection.resetFilter}}" stepKey="resetGridFilter" /> + <waitForPageLoad stepKey="waitForFiltersReset" /> + <fillField userInput="{{user.role}}" selector="{{AdminNewUserFormSection.roleFilterField}}" stepKey="fillRoleFilterField" /> + <click selector="{{AdminNewUserFormSection.search}}" stepKey="clickSearchButton" /> + <waitForPageLoad stepKey="waitForFiltersApplied" /> + <checkOption selector="{{AdminNewUserFormSection.roleRadiobutton(user.role)}}" stepKey="assignRole"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminOpenForgotPasswordPageActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminOpenForgotPasswordPageActionGroup.xml new file mode 100644 index 0000000000000..fa17c5a7f8b76 --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminOpenForgotPasswordPageActionGroup.xml @@ -0,0 +1,16 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenForgotPasswordPageActionGroup"> + <amOnPage url="{{AdminLoginPage.url}}" stepKey="amOnAdminLoginPage"/> + <waitForPageLoad stepKey="waitForAdminLoginPage"/> + <click stepKey="clickForgotPasswordLink" selector="{{AdminLoginFormSection.forgotPasswordLink}}"/> + <waitForPageLoad stepKey="waitForAdminForgotPasswordPage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminOpenNewUserPageActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminOpenNewUserPageActionGroup.xml new file mode 100644 index 0000000000000..67aef9379faa8 --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminOpenNewUserPageActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenNewUserPageActionGroup"> + <amOnPage url="{{AdminNewUserPage.url}}" stepKey="amOnNewAdminUserPage"/> + <waitForPageLoad stepKey="waitForNewAdminUserPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminSubmitForgotPasswordFormActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminSubmitForgotPasswordFormActionGroup.xml new file mode 100644 index 0000000000000..198bc713093ea --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminSubmitForgotPasswordFormActionGroup.xml @@ -0,0 +1,13 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSubmitForgotPasswordFormActionGroup"> + <click selector="{{AdminForgotPasswordFormSection.retrievePasswordButton}}" stepKey="clickOnRetrievePasswordButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AssertAdminUserSaveMessageActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AssertAdminUserSaveMessageActionGroup.xml new file mode 100644 index 0000000000000..db4f0a89348a9 --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AssertAdminUserSaveMessageActionGroup.xml @@ -0,0 +1,18 @@ +<?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="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminUserSaveMessageActionGroup"> + <arguments> + <argument name="message" type="string" defaultValue="You saved the user." /> + <argument name="messageType" type="string" defaultValue="success" /> + </arguments> + <waitForElementVisible selector="{{AdminUserFormMessagesSection.messageByType(messageType)}}" stepKey="waitForMessage" /> + <see userInput="{{message}}" selector="{{AdminUserFormMessagesSection.messageByType(messageType)}}" stepKey="verifyMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/User/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/User/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..e8b7d2aa8e047 --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,31 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuSystemPermissionsAllUsers"> + <data key="pageTitle">Users</data> + <data key="title">All Users</data> + <data key="dataUiId">magento-user-system-acl-users</data> + </entity> + <entity name="AdminMenuSystemPermissionsLockedUsers"> + <data key="pageTitle">Locked Users</data> + <data key="title">Locked Users</data> + <data key="dataUiId">magento-user-system-acl-locks</data> + </entity> + <entity name="AdminMenuSystemOtherSettingsManageEncryptionKey"> + <data key="pageTitle">Encryption Key</data> + <data key="title">Manage Encryption Key</data> + <data key="dataUiId">magento-encryptionkey-system-crypt-key</data> + </entity> + <entity name="AdminMenuSystemPermissionsUserRoles"> + <data key="pageTitle">Roles</data> + <data key="title">User Roles</data> + <data key="dataUiId">magento-user-system-acl-roles</data> + </entity> +</entities> diff --git a/app/code/Magento/User/Test/Mftf/Data/UserData.xml b/app/code/Magento/User/Test/Mftf/Data/UserData.xml index d602f094ce4e5..e665736ae28f1 100644 --- a/app/code/Magento/User/Test/Mftf/Data/UserData.xml +++ b/app/code/Magento/User/Test/Mftf/Data/UserData.xml @@ -16,6 +16,40 @@ <data key="username" unique="suffix">username_</data> <data key="password" unique="suffix">password_</data> </entity> + <entity name="NewAdminUser" type="user"> + <data key="username" unique="suffix">admin</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="email" unique="prefix">admin@example.com</data> + <data key="password">123123q</data> + <data key="password_confirmation">123123q</data> + <data key="interface_local">en_US</data> + <data key="interface_local_label">English (United States)</data> + <data key="is_active">true</data> + <data key="is_active_label">Active</data> + <data key="current_password">{{_ENV.MAGENTO_ADMIN_PASSWORD}}</data> + <data key="role">Administrators</data> + <array key="roles"> + <item>1</item> + </array> + </entity> + <entity name="NewAdminUserWrongCurrentPassword" type="user"> + <data key="username" unique="suffix">admin</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="email" unique="prefix">admin@example.com</data> + <data key="password">123123q</data> + <data key="password_confirmation">123123q</data> + <data key="interface_local">en_US</data> + <data key="interface_local_label">English (United States)</data> + <data key="is_active">true</data> + <data key="is_active_label">Active</data> + <data key="current_password" unique="suffix">password_</data> + <data key="role">Administrators</data> + <array key="roles"> + <item>1</item> + </array> + </entity> <entity name="admin" type="user"> <data key="email">admin@magento.com</data> <data key="password">admin123</data> diff --git a/app/code/Magento/User/Test/Mftf/Page/AdminNewUserPage.xml b/app/code/Magento/User/Test/Mftf/Page/AdminNewUserPage.xml new file mode 100644 index 0000000000000..6de0945793447 --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/Page/AdminNewUserPage.xml @@ -0,0 +1,15 @@ +<?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="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminNewUserPage" url="admin/user/new" area="admin" module="Magento_User"> + <section name="AdminNewUserFormSection" /> + <section name="AdminNewUserFormMessagesSection" /> + </page> +</pages> diff --git a/app/code/Magento/User/Test/Mftf/Section/AdminNewUserFormSection.xml b/app/code/Magento/User/Test/Mftf/Section/AdminNewUserFormSection.xml new file mode 100644 index 0000000000000..9b030b216ce2c --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/Section/AdminNewUserFormSection.xml @@ -0,0 +1,30 @@ +<?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="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminNewUserFormSection"> + <element name="save" type="button" selector=".page-main-actions #save"/> + + <element name="userInfoTab" type="button" selector="#page_tabs_main_section"/> + <element name="username" type="input" selector="#page_tabs_main_section_content input[name='username']"/> + <element name="firstname" type="input" selector="#page_tabs_main_section_content input[name='firstname']"/> + <element name="lastname" type="input" selector="#page_tabs_main_section_content input[name='lastname']"/> + <element name="email" type="input" selector="#page_tabs_main_section_content input[name='email']"/> + <element name="password" type="input" selector="#page_tabs_main_section_content input[name='password']"/> + <element name="passwordConfirmation" type="input" selector="#page_tabs_main_section_content input[name='password_confirmation']"/> + <element name="interfaceLocale" type="select" selector="#page_tabs_main_section_content select[name='interface_locale']"/> + <element name="currentPassword" type="input" selector="#page_tabs_main_section_content input[name='current_password']"/> + + <element name="userRoleTab" type="button" selector="#page_tabs_roles_section"/> + <element name="search" type="button" selector="#page_tabs_roles_section_content #permissionsUserRolesGrid [data-action='grid-filter-apply']" /> + <element name="resetFilter" type="button" selector="#page_tabs_roles_section_content #permissionsUserRolesGrid [data-action='grid-filter-reset']" /> + <element name="roleFilterField" type="input" selector="#page_tabs_roles_section_content #permissionsUserRolesGrid input[name='role_name']" /> + <element name="roleRadiobutton" type="radio" selector="//table[@id='permissionsUserRolesGrid_table']//tr[./td[contains(@class, 'col-role_name') and contains(., '{{roleName}}')]]//input[@name='roles[]']" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/User/Test/Mftf/Section/AdminUserFormMessagesSection.xml b/app/code/Magento/User/Test/Mftf/Section/AdminUserFormMessagesSection.xml new file mode 100644 index 0000000000000..ec4f4d8bf3ad7 --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/Section/AdminUserFormMessagesSection.xml @@ -0,0 +1,14 @@ +<?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="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminUserFormMessagesSection"> + <element name="messageByType" type="block" selector="#messages .message-{{messageType}}" parameterized="true" /> + </section> +</sections> diff --git a/app/code/Magento/User/Test/Mftf/Test/AdminResetUserPasswordFailedTest.xml b/app/code/Magento/User/Test/Mftf/Test/AdminResetUserPasswordFailedTest.xml new file mode 100644 index 0000000000000..4b48c65a18994 --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/Test/AdminResetUserPasswordFailedTest.xml @@ -0,0 +1,43 @@ +<?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="AdminResetUserPasswordFailedTest"> + <annotations> + <features value="User"/> + <title value="Admin user should not be able to trigger the password reset procedure twice"/> + <description value="Admin user should not be able to trigger the password reset procedure twice"/> + <testCaseId value="MC-14389" /> + <group value="security"/> + <group value="mtf_migrated"/> + </annotations> + + <!-- First attempt to reset password --> + <actionGroup ref="AdminOpenForgotPasswordPageActionGroup" stepKey="openAdminForgotPasswordPage1"/> + <actionGroup ref="AdminFillForgotPasswordFormActionGroup" stepKey="fillAdminForgotPasswordForm1"> + <argument name="email" value="customer@example.com"/> + </actionGroup> + <actionGroup ref="AdminSubmitForgotPasswordFormActionGroup" stepKey="submitAdminForgotPasswordForm1"/> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="seeSuccessMessage"> + <argument name="messageType" value="success"/> + <argument name="message" value="We'll email you a link to reset your password."/> + </actionGroup> + + <!-- Second attempt to reset password --> + <actionGroup ref="AdminOpenForgotPasswordPageActionGroup" stepKey="openAdminForgotPasswordPage2"/> + <actionGroup ref="AdminFillForgotPasswordFormActionGroup" stepKey="fillAdminForgotPasswordForm2"> + <argument name="email" value="customer@example.com"/> + </actionGroup> + <actionGroup ref="AdminSubmitForgotPasswordFormActionGroup" stepKey="submitAdminForgotPasswordForm2"/> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="seeErrorMessage"> + <argument name="messageType" value="error"/> + <argument name="message" value="We received too many requests for password resets. Please wait and try again later or contact hello@example.com."/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/User/Test/Mftf/Test/AdminSystemAllUsersNavigateMenuTest.xml b/app/code/Magento/User/Test/Mftf/Test/AdminSystemAllUsersNavigateMenuTest.xml new file mode 100644 index 0000000000000..b899320403d71 --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/Test/AdminSystemAllUsersNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminSystemAllUsersNavigateMenuTest"> + <annotations> + <features value="User"/> + <stories value="Menu Navigation"/> + <title value="Admin system all users navigate menu test"/> + <description value="Admin should be able to navigate to System > All Users"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14123"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToSystemAllUsersPage"> + <argument name="menuUiId" value="{{AdminMenuSystem.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSystemPermissionsAllUsers.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSystemPermissionsAllUsers.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/User/Test/Mftf/Test/AdminSystemLockedUsersNavigateMenuTest.xml b/app/code/Magento/User/Test/Mftf/Test/AdminSystemLockedUsersNavigateMenuTest.xml new file mode 100644 index 0000000000000..aea46f3273157 --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/Test/AdminSystemLockedUsersNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminSystemLockedUsersNavigateMenuTest"> + <annotations> + <features value="User"/> + <stories value="Menu Navigation"/> + <title value="Admin system locked users navigate menu test"/> + <description value="Admin should be able to navigate to System > Locked Users"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14121"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToSystemLockedUsersPage"> + <argument name="menuUiId" value="{{AdminMenuSystem.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSystemPermissionsLockedUsers.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSystemPermissionsLockedUsers.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/User/Test/Mftf/Test/AdminSystemManageEncryptionKeyNavigateMenuTest.xml b/app/code/Magento/User/Test/Mftf/Test/AdminSystemManageEncryptionKeyNavigateMenuTest.xml new file mode 100644 index 0000000000000..f8013a54058c3 --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/Test/AdminSystemManageEncryptionKeyNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminSystemManageEncryptionKeyNavigateMenuTest"> + <annotations> + <features value="User"/> + <stories value="Menu Navigation"/> + <title value="Admin system manage encryption key navigate menu test"/> + <description value="Admin should be able to navigate to System > Manage Encryption Key"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14122"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToManageEncryptionKeyPage"> + <argument name="menuUiId" value="{{AdminMenuSystem.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSystemOtherSettingsManageEncryptionKey.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSystemOtherSettingsManageEncryptionKey.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/User/Test/Mftf/Test/AdminSystemUserRolesNavigateMenuTest.xml b/app/code/Magento/User/Test/Mftf/Test/AdminSystemUserRolesNavigateMenuTest.xml new file mode 100644 index 0000000000000..c4052a7f4219c --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/Test/AdminSystemUserRolesNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminSystemUserRolesNavigateMenuTest"> + <annotations> + <features value="User"/> + <stories value="Menu Navigation"/> + <title value="Admin system user roles navigate menu test"/> + <description value="Admin should be able to navigate to System > User Roles"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14124"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToSystemUserRolesPage"> + <argument name="menuUiId" value="{{AdminMenuSystem.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSystemPermissionsUserRoles.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSystemPermissionsUserRoles.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/User/Test/Unit/Model/Authorization/AdminSessionUserContextTest.php b/app/code/Magento/User/Test/Unit/Model/Authorization/AdminSessionUserContextTest.php deleted file mode 100644 index 23681c4b8da26..0000000000000 --- a/app/code/Magento/User/Test/Unit/Model/Authorization/AdminSessionUserContextTest.php +++ /dev/null @@ -1,89 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\User\Test\Unit\Model\Authorization; - -use Magento\Authorization\Model\UserContextInterface; - -/** - * Tests Magento\User\Model\Authorization\AdminSessionUserContext - */ -class AdminSessionUserContextTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager - */ - protected $objectManager; - - /** - * @var \Magento\User\Model\Authorization\AdminSessionUserContext - */ - protected $adminSessionUserContext; - - /** - * @var \Magento\Backend\Model\Auth\Session - */ - protected $adminSession; - - protected function setUp() - { - $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - - $this->adminSession = $this->getMockBuilder(\Magento\Backend\Model\Auth\Session::class) - ->disableOriginalConstructor() - ->setMethods(['hasUser', 'getUser', 'getId']) - ->getMock(); - - $this->adminSessionUserContext = $this->objectManager->getObject( - \Magento\User\Model\Authorization\AdminSessionUserContext::class, - ['adminSession' => $this->adminSession] - ); - } - - public function testGetUserIdExist() - { - $userId = 1; - - $this->setupUserId($userId); - - $this->assertEquals($userId, $this->adminSessionUserContext->getUserId()); - } - - public function testGetUserIdDoesNotExist() - { - $userId = null; - - $this->setupUserId($userId); - - $this->assertEquals($userId, $this->adminSessionUserContext->getUserId()); - } - - public function testGetUserType() - { - $this->assertEquals(UserContextInterface::USER_TYPE_ADMIN, $this->adminSessionUserContext->getUserType()); - } - - /** - * @param int|null $userId - * @return void - */ - public function setupUserId($userId) - { - $this->adminSession->expects($this->once()) - ->method('hasUser') - ->will($this->returnValue($userId)); - - if ($userId) { - $this->adminSession->expects($this->once()) - ->method('getUser') - ->will($this->returnSelf()); - - $this->adminSession->expects($this->once()) - ->method('getId') - ->will($this->returnValue($userId)); - } - } -} diff --git a/app/code/Magento/User/Test/Unit/Model/UserTest.php b/app/code/Magento/User/Test/Unit/Model/UserTest.php index 670316c2500fc..ab06c8754b2f0 100644 --- a/app/code/Magento/User/Test/Unit/Model/UserTest.php +++ b/app/code/Magento/User/Test/Unit/Model/UserTest.php @@ -44,31 +44,6 @@ protected function setUp() ); } - /** - * @return void - */ - public function testSleep() - { - $excludedProperties = [ - '_eventManager', - '_cacheManager', - '_registry', - '_appState', - '_userData', - '_config', - '_validatorObject', - '_roleFactory', - '_encryptor', - '_transportBuilder', - '_storeManager', - '_validatorBeforeSave' - ]; - $actualResult = $this->model->__sleep(); - $this->assertNotEmpty($actualResult); - $expectedResult = array_intersect($actualResult, $excludedProperties); - $this->assertEmpty($expectedResult); - } - /** * @return void */ diff --git a/app/code/Magento/User/view/adminhtml/layout/adminhtml_locks_block.xml b/app/code/Magento/User/view/adminhtml/layout/adminhtml_locks_block.xml index 13cfbfe859333..d195ac93c16e5 100644 --- a/app/code/Magento/User/view/adminhtml/layout/adminhtml_locks_block.xml +++ b/app/code/Magento/User/view/adminhtml/layout/adminhtml_locks_block.xml @@ -12,7 +12,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.block.locks.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">lockedAdminsGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\User\Model\ResourceModel\User\Locked\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\User\Model\ResourceModel\User\Locked\Collection</argument> <argument name="default_sort" xsi:type="string">user_id</argument> <argument name="use_ajax" xsi:type="string">1</argument> <argument name="grid_url" xsi:type="url" path="*/*/grid"> diff --git a/app/code/Magento/User/view/adminhtml/layout/adminhtml_user_grid_block.xml b/app/code/Magento/User/view/adminhtml/layout/adminhtml_user_grid_block.xml index a4bc9aa5ed48b..dca45de046ef1 100644 --- a/app/code/Magento/User/view/adminhtml/layout/adminhtml_user_grid_block.xml +++ b/app/code/Magento/User/view/adminhtml/layout/adminhtml_user_grid_block.xml @@ -11,11 +11,12 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.user.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">permissionsUserGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\User\Model\ResourceModel\User\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\User\Model\ResourceModel\User\Collection</argument> <argument name="use_ajax" xsi:type="string">1</argument> <argument name="default_sort" xsi:type="string">username</argument> <argument name="default_dir" xsi:type="string">asc</argument> <argument name="grid_url" xsi:type="url" path="*/*/roleGrid"/> + <argument name="save_parameters_in_session" xsi:type="boolean">true</argument> </arguments> <block class="Magento\Backend\Block\Widget\Grid\ColumnSet" as="grid.columnSet" name="permission.user.grid.columnSet"> <arguments> diff --git a/app/code/Magento/User/view/adminhtml/layout/adminhtml_user_role_grid_block.xml b/app/code/Magento/User/view/adminhtml/layout/adminhtml_user_role_grid_block.xml index 6d7ce67e2352b..f2d699b6cc08b 100644 --- a/app/code/Magento/User/view/adminhtml/layout/adminhtml_user_role_grid_block.xml +++ b/app/code/Magento/User/view/adminhtml/layout/adminhtml_user_role_grid_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.role.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">roleGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\Authorization\Model\ResourceModel\Role\Grid\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Authorization\Model\ResourceModel\Role\Grid\Collection</argument> <argument name="save_parameters_in_session" xsi:type="string">1</argument> <argument name="default_sort" xsi:type="string">role_id</argument> <argument name="default_dir" xsi:type="string">asc</argument> diff --git a/app/code/Magento/Variable/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Variable/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..e094239767486 --- /dev/null +++ b/app/code/Magento/Variable/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,16 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> +<entity name="AdminMenuSystemOtherSettingsCustomVariables"> + <data key="pageTitle">Custom Variables</data> + <data key="title">Custom Variables</data> + <data key="dataUiId">magento-variable-system-variable</data> +</entity> +</entities> diff --git a/app/code/Magento/Variable/Test/Mftf/Test/AdminSystemCustomVariablesNavigateMenuTest.xml b/app/code/Magento/Variable/Test/Mftf/Test/AdminSystemCustomVariablesNavigateMenuTest.xml new file mode 100644 index 0000000000000..74446cf601348 --- /dev/null +++ b/app/code/Magento/Variable/Test/Mftf/Test/AdminSystemCustomVariablesNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminSystemCustomVariablesNavigateMenuTest"> + <annotations> + <features value="Variable"/> + <stories value="Menu Navigation"/> + <title value="Admin system custom variables navigate menu test"/> + <description value="Admin should be able to navigate to System > Custom Variables"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14126"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToSystemCustomVariablesPage"> + <argument name="menuUiId" value="{{AdminMenuSystem.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSystemOtherSettingsCustomVariables.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuSystemOtherSettingsCustomVariables.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Variable/view/adminhtml/layout/adminhtml_system_variable_grid_block.xml b/app/code/Magento/Variable/view/adminhtml/layout/adminhtml_system_variable_grid_block.xml index d934e46117fb1..edee7fcca8222 100644 --- a/app/code/Magento/Variable/view/adminhtml/layout/adminhtml_system_variable_grid_block.xml +++ b/app/code/Magento/Variable/view/adminhtml/layout/adminhtml_system_variable_grid_block.xml @@ -11,7 +11,7 @@ <block class="Magento\Backend\Block\Widget\Grid" name="adminhtml.system.variable.grid" as="grid"> <arguments> <argument name="id" xsi:type="string">customVariablesGrid</argument> - <argument name="dataSource" xsi:type="object">Magento\Variable\Model\ResourceModel\Variable\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Variable\Model\ResourceModel\Variable\Collection</argument> <argument name="default_sort" xsi:type="string">variable_id</argument> <argument name="default_dir" xsi:type="string">ASC</argument> </arguments> diff --git a/app/code/Magento/VaultGraphQl/etc/schema.graphqls b/app/code/Magento/VaultGraphQl/etc/schema.graphqls index cdaeced027f6f..64484fe9e814f 100644 --- a/app/code/Magento/VaultGraphQl/etc/schema.graphqls +++ b/app/code/Magento/VaultGraphQl/etc/schema.graphqls @@ -11,7 +11,7 @@ type DeletePaymentTokenOutput { } type Query { - customerPaymentTokens: CustomerPaymentTokens @doc(description: "Return a list of customer payment tokens") @resolver(class: "\\Magento\\VaultGraphQl\\Model\\Resolver\\PaymentTokens") + customerPaymentTokens: CustomerPaymentTokens @doc(description: "Return a list of customer payment tokens") @resolver(class: "\\Magento\\VaultGraphQl\\Model\\Resolver\\PaymentTokens") @cache(cacheable: false) } type CustomerPaymentTokens @resolver(class: "\\Magento\\VaultGraphQl\\Model\\Resolver\\PaymentTokens") { diff --git a/app/code/Magento/Webapi/Controller/PathProcessor.php b/app/code/Magento/Webapi/Controller/PathProcessor.php index c5748cc6e848e..f32c93fb0c761 100644 --- a/app/code/Magento/Webapi/Controller/PathProcessor.php +++ b/app/code/Magento/Webapi/Controller/PathProcessor.php @@ -1,11 +1,12 @@ <?php - /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Webapi\Controller; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\NoSuchEntityException; /** @@ -21,12 +22,23 @@ class PathProcessor */ private $storeManager; + /** + * @var \Magento\Framework\Locale\ResolverInterface + */ + private $localeResolver; + /** * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param \Magento\Framework\Locale\ResolverInterface $localeResolver */ - public function __construct(\Magento\Store\Model\StoreManagerInterface $storeManager) - { + public function __construct( + \Magento\Store\Model\StoreManagerInterface $storeManager, + \Magento\Framework\Locale\ResolverInterface $localeResolver = null + ) { $this->storeManager = $storeManager; + $this->localeResolver = $localeResolver ?: ObjectManager::getInstance()->get( + \Magento\Framework\Locale\ResolverInterface::class + ); } /** @@ -57,9 +69,11 @@ public function process($pathInfo) $stores = $this->storeManager->getStores(false, true); if (isset($stores[$storeCode])) { $this->storeManager->setCurrentStore($storeCode); + $this->localeResolver->emulate($this->storeManager->getStore()->getId()); $path = '/' . (isset($pathParts[1]) ? $pathParts[1] : ''); } elseif ($storeCode === self::ALL_STORE_CODE) { $this->storeManager->setCurrentStore(\Magento\Store\Model\Store::ADMIN_CODE); + $this->localeResolver->emulate($this->storeManager->getStore()->getId()); $path = '/' . (isset($pathParts[1]) ? $pathParts[1] : ''); } else { $path = '/' . implode('/', $pathParts); diff --git a/app/code/Magento/Webapi/Test/Unit/Controller/PathProcessorTest.php b/app/code/Magento/Webapi/Test/Unit/Controller/PathProcessorTest.php index c213c72b5185a..e587756eec7bf 100644 --- a/app/code/Magento/Webapi/Test/Unit/Controller/PathProcessorTest.php +++ b/app/code/Magento/Webapi/Test/Unit/Controller/PathProcessorTest.php @@ -8,11 +8,17 @@ use Magento\Store\Model\Store; +/** + * Test for Magento\Webapi\Controller\PathProcessor class. + */ class PathProcessorTest extends \PHPUnit\Framework\TestCase { /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Store\Model\StoreManagerInterface */ private $storeManagerMock; + /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Framework\Locale\ResolverInterface */ + private $localeResolverMock; + /** @var \Magento\Webapi\Controller\PathProcessor */ private $model; @@ -24,13 +30,22 @@ class PathProcessorTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->storeManagerMock->expects($this->once()) - ->method('getStores') - ->willReturn([$this->arbitraryStoreCode => 'store object', 'default' => 'default store object']); - $this->model = new \Magento\Webapi\Controller\PathProcessor($this->storeManagerMock); + $store = $this->createMock(\Magento\Store\Api\Data\StoreInterface::class); + $store->method('getId')->willReturn(2); + + $this->storeManagerMock = $this->createConfiguredMock( + \Magento\Store\Model\StoreManagerInterface::class, + [ + 'getStores' => [$this->arbitraryStoreCode => 'store object', 'default' => 'default store object'], + 'getStore' => $store, + ] + ); + $this->storeManagerMock->expects($this->once())->method('getStores'); + + $this->localeResolverMock = $this->createMock(\Magento\Framework\Locale\ResolverInterface::class); + $this->localeResolverMock->method('emulate')->with(2); + + $this->model = new \Magento\Webapi\Controller\PathProcessor($this->storeManagerMock, $this->localeResolverMock); } /** @@ -47,6 +62,10 @@ public function testAllStoreCode($storeCodeInPath, $storeCodeSet, $setCurrentSto $this->storeManagerMock->expects($this->exactly($setCurrentStoreCallCtr)) ->method('setCurrentStore') ->with($storeCodeSet); + if ($setCurrentStoreCallCtr > 0) { + $this->localeResolverMock->expects($this->once()) + ->method('emulate'); + } $result = $this->model->process($inPath); $this->assertSame($this->endpointPath, $result); } @@ -60,7 +79,7 @@ public function processPathDataProvider() 'All store code' => ['all', Store::ADMIN_CODE], 'Default store code' => ['', 'default', 0], 'Arbitrary store code' => [$this->arbitraryStoreCode, $this->arbitraryStoreCode], - 'Explicit default store code' => ['default', 'default'] + 'Explicit default store code' => ['default', 'default'], ]; } } diff --git a/app/code/Magento/WebapiAsync/Test/Unit/Controller/PathProcessorTest.php b/app/code/Magento/WebapiAsync/Test/Unit/Controller/PathProcessorTest.php index e5453f7574540..570df6afd3c5d 100644 --- a/app/code/Magento/WebapiAsync/Test/Unit/Controller/PathProcessorTest.php +++ b/app/code/Magento/WebapiAsync/Test/Unit/Controller/PathProcessorTest.php @@ -10,11 +10,17 @@ use Magento\Store\Model\Store; +/** + * Test for Magento\Webapi\Controller\PathProcessor class. + */ class PathProcessorTest extends \PHPUnit\Framework\TestCase { /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Store\Model\StoreManagerInterface */ private $storeManagerMock; + /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Framework\Locale\ResolverInterface */ + private $localeResolverMock; + /** @var \Magento\Webapi\Controller\PathProcessor */ private $model; @@ -26,16 +32,22 @@ class PathProcessorTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->storeManagerMock->expects($this->once()) - ->method('getStores') - ->willReturn([ - $this->arbitraryStoreCode => 'store object', - 'default' => 'default store object', - ]); - $this->model = new \Magento\Webapi\Controller\PathProcessor($this->storeManagerMock); + $store = $this->createMock(\Magento\Store\Api\Data\StoreInterface::class); + $store->method('getId')->willReturn(2); + + $this->storeManagerMock = $this->createConfiguredMock( + \Magento\Store\Model\StoreManagerInterface::class, + [ + 'getStores' => [$this->arbitraryStoreCode => 'store object', 'default' => 'default store object'], + 'getStore' => $store, + ] + ); + $this->storeManagerMock->expects($this->once())->method('getStores'); + + $this->localeResolverMock = $this->createMock(\Magento\Framework\Locale\ResolverInterface::class); + $this->localeResolverMock->method('emulate')->with(2); + + $this->model = new \Magento\Webapi\Controller\PathProcessor($this->storeManagerMock, $this->localeResolverMock); } /** diff --git a/app/code/Magento/Weee/etc/adminhtml/system.xml b/app/code/Magento/Weee/etc/adminhtml/system.xml index ae02b27d10c72..d3e9efb8f0b46 100644 --- a/app/code/Magento/Weee/etc/adminhtml/system.xml +++ b/app/code/Magento/Weee/etc/adminhtml/system.xml @@ -44,6 +44,7 @@ <group id="totals_sort"> <field id="weee" translate="label" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> <label>Fixed Product Tax</label> + <validate>required-number validate-number</validate> </field> </group> </section> diff --git a/app/code/Magento/Widget/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Widget/Test/Mftf/Data/AdminMenuData.xml new file mode 100644 index 0000000000000..8fa652b8f73eb --- /dev/null +++ b/app/code/Magento/Widget/Test/Mftf/Data/AdminMenuData.xml @@ -0,0 +1,16 @@ +<?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="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminMenuContentElementsWidgets"> + <data key="pageTitle">Widgets</data> + <data key="title">Widgets</data> + <data key="dataUiId">magento-widget-cms-widget-instance</data> + </entity> +</entities> diff --git a/app/code/Magento/Widget/Test/Mftf/Test/AdminContentWidgetsNavigateMenuTest.xml b/app/code/Magento/Widget/Test/Mftf/Test/AdminContentWidgetsNavigateMenuTest.xml new file mode 100644 index 0000000000000..f5af024ec1d51 --- /dev/null +++ b/app/code/Magento/Widget/Test/Mftf/Test/AdminContentWidgetsNavigateMenuTest.xml @@ -0,0 +1,36 @@ +<?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="AdminContentWidgetsNavigateMenuTest"> + <annotations> + <features value="Widget"/> + <stories value="Menu Navigation"/> + <title value="Admin content widgets navigate menu test"/> + <description value="Admin should be able to navigate to Content > Widgets"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14147"/> + <group value="menu"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToContentWidgetsPage"> + <argument name="menuUiId" value="{{AdminMenuContent.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuContentElementsWidgets.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitle"> + <argument name="title" value="{{AdminMenuContentElementsWidgets.pageTitle}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml b/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml index 934b0ca1a85b2..8ca3fab413b25 100644 --- a/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml +++ b/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml @@ -13,7 +13,7 @@ <argument name="id" xsi:type="string">widgetInstanceGrid</argument> <argument name="default_sort" xsi:type="string">instance_id</argument> <argument name="default_dir" xsi:type="string">ASC</argument> - <argument name="dataSource" xsi:type="object">Magento\Widget\Model\ResourceModel\Widget\Instance\Collection</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Widget\Model\ResourceModel\Widget\Instance\Collection</argument> </arguments> <block class="Magento\Backend\Block\Widget\Grid\ColumnSet" name="adminhtml.widget.instance.grid.columnSet" as="grid.columnSet"> <arguments> diff --git a/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Cart.php b/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Cart.php index b043a8d4b684c..5625dd5aabb73 100644 --- a/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Cart.php +++ b/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Cart.php @@ -6,6 +6,12 @@ namespace Magento\Wishlist\Block\Customer\Wishlist\Item\Column; +use Magento\Catalog\Block\Product\View; +use Magento\Catalog\Controller\Adminhtml\Product\Initialization\StockDataFilter; +use Magento\Catalog\Model\Product\Image\UrlBuilder; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\View\ConfigInterface; + /** * Wishlist block customer item cart column * @@ -14,6 +20,31 @@ */ class Cart extends \Magento\Wishlist\Block\Customer\Wishlist\Item\Column { + /** + * @var View + */ + private $productView; + + /** + * @param \Magento\Catalog\Block\Product\Context $context + * @param \Magento\Framework\App\Http\Context $httpContext + * @param array $data + * @param ConfigInterface|null $config + * @param UrlBuilder|null $urlBuilder + * @param View|null $productView + */ + public function __construct( + \Magento\Catalog\Block\Product\Context $context, + \Magento\Framework\App\Http\Context $httpContext, + array $data = [], + ?ConfigInterface $config = null, + ?UrlBuilder $urlBuilder = null, + ?View $productView = null + ) { + $this->productView = $productView ?: ObjectManager::getInstance()->get(View::class); + parent::__construct($context, $httpContext, $data, $config, $urlBuilder); + } + /** * Returns qty to show visually to user * @@ -23,7 +54,9 @@ class Cart extends \Magento\Wishlist\Block\Customer\Wishlist\Item\Column public function getAddToCartQty(\Magento\Wishlist\Model\Item $item) { $qty = $item->getQty(); - return $qty ? $qty : 1; + $qty = $qty < $this->productView->getProductDefaultQty($this->getProductItem()) + ? $this->productView->getProductDefaultQty($this->getProductItem()) : $qty; + return $qty ?: 1; } /** diff --git a/app/code/Magento/Wishlist/Helper/Data.php b/app/code/Magento/Wishlist/Helper/Data.php index 3b9f431566da0..3d25e16294fcd 100644 --- a/app/code/Magento/Wishlist/Helper/Data.php +++ b/app/code/Magento/Wishlist/Helper/Data.php @@ -13,6 +13,7 @@ * * @author Magento Core Team <core@magentocommerce.com> * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * * @api * @since 100.0.2 @@ -171,7 +172,7 @@ public function setCustomer(\Magento\Customer\Api\Data\CustomerInterface $custom public function getCustomer() { if (!$this->_currentCustomer && $this->_customerSession->isLoggedIn()) { - $this->_currentCustomer = $this->_customerSession->getCustomerDataObject(); + $this->_currentCustomer = $this->_customerSession->getCustomerData(); } return $this->_currentCustomer; } @@ -355,7 +356,7 @@ public function getMoveFromCartParams($itemId) * * @param \Magento\Catalog\Model\Product|\Magento\Wishlist\Model\Item $item * - * @return string|false + * @return string|false */ public function getUpdateParams($item) { @@ -382,7 +383,7 @@ public function getUpdateParams($item) * Retrieve params for adding item to shopping cart * * @param string|\Magento\Catalog\Model\Product|\Magento\Wishlist\Model\Item $item - * @return string + * @return string */ public function getAddToCartUrl($item) { @@ -428,7 +429,7 @@ public function addRefererToParams(array $params) * Retrieve URL for adding item to shopping cart from shared wishlist * * @param string|\Magento\Catalog\Model\Product|\Magento\Wishlist\Model\Item $item - * @return string + * @return string */ public function getSharedAddToCartUrl($item) { diff --git a/app/code/Magento/Wishlist/view/adminhtml/layout/customer_index_wishlist.xml b/app/code/Magento/Wishlist/view/adminhtml/layout/customer_index_wishlist.xml index 95b786603390c..e364087405ed9 100644 --- a/app/code/Magento/Wishlist/view/adminhtml/layout/customer_index_wishlist.xml +++ b/app/code/Magento/Wishlist/view/adminhtml/layout/customer_index_wishlist.xml @@ -9,7 +9,7 @@ <container name="root"> <block class="Magento\Backend\Block\Widget\Grid" name="customer.wishlist.edit.tab"> <arguments> - <argument name="dataSource" xsi:type="object">Magento\Wishlist\Model\ResourceModel\Item\Collection\Grid</argument> + <argument name="dataSource" xsi:type="object" shared="false">Magento\Wishlist\Model\ResourceModel\Item\Collection\Grid</argument> <argument name="id" xsi:type="string">wishlistGrid</argument> <argument name="use_ajax" xsi:type="string">true</argument> <argument name="default_sort" xsi:type="string">added_at</argument> diff --git a/app/code/Magento/Wishlist/view/frontend/templates/item/column/cart.phtml b/app/code/Magento/Wishlist/view/frontend/templates/item/column/cart.phtml index 6cb32d70ee1d8..f296b950f3abb 100644 --- a/app/code/Magento/Wishlist/view/frontend/templates/item/column/cart.phtml +++ b/app/code/Magento/Wishlist/view/frontend/templates/item/column/cart.phtml @@ -25,7 +25,7 @@ $allowedQty = $viewModel->setItem($item)->getMinMaxQty(); <label class="label" for="qty[<?= $block->escapeHtmlAttr($item->getId()) ?>]"><span><?= $block->escapeHtml(__('Qty')) ?></span></label> <div class="control"> <input type="number" data-role="qty" id="qty[<?= $block->escapeHtmlAttr($item->getId()) ?>]" class="input-text qty" data-validate="{'required-number':true,'validate-greater-than-zero':true, 'validate-item-quantity':{'minAllowed':<?= /* @noEscape */ $allowedQty['minAllowed'] ?>,'maxAllowed':<?= /* @noEscape */ $allowedQty['maxAllowed'] ?>}}" - name="qty[<?= $block->escapeHtmlAttr($item->getId()) ?>]" value="<?= /* @noEscape */ (int)($block->getAddToCartQty($item) * 1) ?>"> + name="qty[<?= $block->escapeHtmlAttr($item->getId()) ?>]" value="<?= /* @noEscape */ (int)($block->getAddToCartQty($item) * 1) ?>" <?= $product->isSaleable() ? '' : 'disabled="disabled"' ?>> </div> </div> <?php endif; ?> diff --git a/app/code/Magento/Wishlist/view/frontend/templates/item/column/comment.phtml b/app/code/Magento/Wishlist/view/frontend/templates/item/column/comment.phtml index 17e2404ee23cf..5ab5bc5422e7e 100644 --- a/app/code/Magento/Wishlist/view/frontend/templates/item/column/comment.phtml +++ b/app/code/Magento/Wishlist/view/frontend/templates/item/column/comment.phtml @@ -17,6 +17,6 @@ $item = $block->getItem(); <span><?= $block->escapeHtml(__('Comment')) ?></span> </label> <div class="control"> - <textarea id="product-item-comment-<?= $block->escapeHtmlAttr($item->getWishlistItemId()) ?>" placeholder="<?= /* @noEscape */ $this->helper('Magento\Wishlist\Helper\Data')->defaultCommentString() ?>" name="description[<?= $block->escapeHtmlAttr($item->getWishlistItemId()) ?>]" title="<?= $block->escapeHtmlAttr(__('Comment')) ?>" class="product-item-comment"><?= ($block->escapeHtml($item->getDescription())) ?></textarea> + <textarea id="product-item-comment-<?= $block->escapeHtmlAttr($item->getWishlistItemId()) ?>" placeholder="<?= /* @noEscape */ $this->helper('Magento\Wishlist\Helper\Data')->defaultCommentString() ?>" name="description[<?= $block->escapeHtmlAttr($item->getWishlistItemId()) ?>]" title="<?= $block->escapeHtmlAttr(__('Comment')) ?>" class="product-item-comment" <?= $item->getProduct()->isSaleable() ? '' : 'disabled="disabled"' ?>><?= ($block->escapeHtml($item->getDescription())) ?></textarea> </div> </div> diff --git a/app/code/Magento/WishlistGraphQl/etc/schema.graphqls b/app/code/Magento/WishlistGraphQl/etc/schema.graphqls index f5b5034fb734f..2aa5f03a787d0 100644 --- a/app/code/Magento/WishlistGraphQl/etc/schema.graphqls +++ b/app/code/Magento/WishlistGraphQl/etc/schema.graphqls @@ -2,7 +2,7 @@ # See COPYING.txt for license details. type Query { - wishlist: WishlistOutput @resolver(class: "\\Magento\\WishlistGraphQl\\Model\\Resolver\\WishlistResolver") @doc(description: "The wishlist query returns the contents of a customer's wish list") + wishlist: WishlistOutput @resolver(class: "\\Magento\\WishlistGraphQl\\Model\\Resolver\\WishlistResolver") @doc(description: "The wishlist query returns the contents of a customer's wish list") @cache(cacheable: false) } type WishlistOutput { diff --git a/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/_order.less b/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/_order.less index fa1ae25628986..ffa5ee963952c 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/_order.less +++ b/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/_order.less @@ -55,6 +55,7 @@ } .order-billing-address, + .order-billing-method, .order-history, .order-information, .order-payment-method, @@ -64,6 +65,7 @@ } .order-shipping-address, + .order-shipping-method, .order-totals, .order-view-account-information .order-account-information { float: right; diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less index 5698afdaac7ae..66c9086c15aa7 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less @@ -81,7 +81,7 @@ min-width: 0; padding: 0; - // Filedset section + // Fieldset section .fieldset-wrapper { &.admin__fieldset-section { > .fieldset-wrapper-title { diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_temp.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_temp.less index 5d9bf80ce2255..71f57b765ff0e 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_temp.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_temp.less @@ -470,8 +470,6 @@ label.mage-error { } .admin__data-grid-header-row { - &:extend(.abs-cleafix); - .action-select-multiselect { -webkit-appearance: menulist-button; appearance: menulist-button; diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_field-tooltips.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_field-tooltips.less old mode 100644 new mode 100755 index 8184a5c4bb248..befd27fa57df6 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_field-tooltips.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_field-tooltips.less @@ -22,7 +22,7 @@ @field-tooltip-content__width: 32rem; @field-tooltip-content__z-index: 1; -@field-tooltip-action__margin-left: 2rem; +@field-tooltip-action__margin-left: 0; // // Form Fields diff --git a/app/design/adminhtml/Magento/backend/web/css/styles-old.less b/app/design/adminhtml/Magento/backend/web/css/styles-old.less index 2dbe68ef96eec..c9e56abbba8d4 100644 --- a/app/design/adminhtml/Magento/backend/web/css/styles-old.less +++ b/app/design/adminhtml/Magento/backend/web/css/styles-old.less @@ -820,7 +820,7 @@ padding-right: 44px; - &:focus { + &:active { background-image+: url('../images/arrows-bg.svg'); background-position+: ~'calc(100% - 12px)' 13px; diff --git a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less index d3b314836ae8e..299c138832064 100644 --- a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less @@ -507,17 +507,6 @@ min-height: inherit; } } - - // - // Category page 1 column layout - // --------------------------------------------- - - .catalog-category-view.page-layout-1column { - .column.main { - min-height: inherit; - } - } - } // diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_tooltip.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_tooltip.less index 664726ddfd798..423923d5e6457 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_tooltip.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_tooltip.less @@ -55,6 +55,10 @@ } } + .label { + .lib-visually-hidden(); + } + .field-tooltip-action { .lib-icon-font( @checkout-tooltip-icon__content, @@ -173,10 +177,10 @@ width: 0; } .field-tooltip .field-tooltip-content::before { - border-bottom-color: @color-gray40; + .lib-css(border-bottom-color, @checkout-tooltip-content__border-color); } .field-tooltip .field-tooltip-content::after { - border-bottom-color: @color-gray-light01; + .lib-css(border-bottom-color, @checkout-tooltip-content__background-color); top: 1px; } } diff --git a/app/design/frontend/Magento/blank/Magento_Sales/web/css/source/_email.less b/app/design/frontend/Magento/blank/Magento_Sales/web/css/source/_email.less index 215d7d8b322b4..b189d4e08ba17 100644 --- a/app/design/frontend/Magento/blank/Magento_Sales/web/css/source/_email.less +++ b/app/design/frontend/Magento/blank/Magento_Sales/web/css/source/_email.less @@ -68,8 +68,10 @@ } // Remove address and phone number link color on iOS -.address-details a { - &:extend(.no-link a); +.email-non-inline() { + .address-details a { + &:extend(.no-link a); + } } .media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__xs) { diff --git a/app/design/frontend/Magento/blank/web/css/source/components/_modals_extend.less b/app/design/frontend/Magento/blank/web/css/source/components/_modals_extend.less index 5cdb1444094e9..764a63bd88064 100644 --- a/app/design/frontend/Magento/blank/web/css/source/components/_modals_extend.less +++ b/app/design/frontend/Magento/blank/web/css/source/components/_modals_extend.less @@ -148,10 +148,9 @@ } } } -} -.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { .modal-popup { + pointer-events: auto; &.modal-slide { .modal-inner-wrap[class] { .lib-css(background-color, @modal-slide-mobile__background-color); diff --git a/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_email.less b/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_email.less index 3f19d1020bab9..31c128e07e3a6 100644 --- a/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_email.less +++ b/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_email.less @@ -76,8 +76,10 @@ } // Remove address and phone number link color on iOS -.address-details a { - &:extend(.no-link a); +.email-non-inline() { + .address-details a { + &:extend(.no-link a); + } } .media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__xs) { diff --git a/app/design/frontend/Magento/luma/Magento_Theme/layout/default_head_blocks.xml b/app/design/frontend/Magento/luma/Magento_Theme/layout/default_head_blocks.xml index 2dfb1d3ee6bf0..7e64e5f3f01cd 100644 --- a/app/design/frontend/Magento/luma/Magento_Theme/layout/default_head_blocks.xml +++ b/app/design/frontend/Magento/luma/Magento_Theme/layout/default_head_blocks.xml @@ -7,6 +7,6 @@ --> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <head> - <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no"/> + <meta name="viewport" content="width=device-width, initial-scale=1"/> </head> </page> diff --git a/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less index 1f1ea93d0b54a..dfcc51e0a0a26 100644 --- a/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less @@ -112,6 +112,10 @@ font-size: @font-size__base; margin: 0 0 0 15px; + &.customer-welcome { + margin: 0 0 0 5px; + } + > a { .lib-link( @_link-color: @header-panel__text-color, diff --git a/app/design/frontend/Magento/luma/web/css/source/components/_modals_extend.less b/app/design/frontend/Magento/luma/web/css/source/components/_modals_extend.less index 7e3ee14ca5fa4..855700806fdba 100644 --- a/app/design/frontend/Magento/luma/web/css/source/components/_modals_extend.less +++ b/app/design/frontend/Magento/luma/web/css/source/components/_modals_extend.less @@ -148,10 +148,9 @@ } } } -} -.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { .modal-popup { + pointer-events: auto; &.modal-slide { .modal-inner-wrap[class] { .lib-css(background-color, @modal-slide-mobile__background-color); diff --git a/app/etc/di.xml b/app/etc/di.xml index d0b45ea16c855..200a56201239d 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -153,7 +153,6 @@ <preference for="Magento\Framework\Pricing\Amount\AmountInterface" type="Magento\Framework\Pricing\Amount\Base" /> <preference for="Magento\Framework\Api\SearchResultsInterface" type="Magento\Framework\Api\SearchResults" /> <preference for="Magento\Framework\Api\AttributeInterface" type="Magento\Framework\Api\AttributeValue" /> - <preference for="Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface" type="Magento\Framework\Model\ResourceModel\ResourceModelPool" /> <preference for="Magento\Framework\Model\ResourceModel\Db\TransactionManagerInterface" type="Magento\Framework\Model\ResourceModel\Db\TransactionManager" /> <preference for="Magento\Framework\Api\Data\ImageContentInterface" type="Magento\Framework\Api\ImageContent" /> <preference for="Magento\Framework\Api\ImageContentValidatorInterface" type="Magento\Framework\Api\ImageContentValidator" /> @@ -210,6 +209,7 @@ <preference for="Magento\Framework\MessageQueue\Bulk\ExchangeFactoryInterface" type="Magento\Framework\MessageQueue\Bulk\ExchangeFactory" /> <preference for="Magento\Framework\MessageQueue\QueueFactoryInterface" type="Magento\Framework\MessageQueue\QueueFactory" /> <preference for="Magento\Framework\Search\Request\IndexScopeResolverInterface" type="Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver"/> + <preference for="Magento\Framework\HTTP\ClientInterface" type="Magento\Framework\HTTP\Client\Curl" /> <type name="Magento\Framework\Model\ResourceModel\Db\TransactionManager" shared="false" /> <type name="Magento\Framework\Acl\Data\Cache"> <arguments> @@ -1739,7 +1739,7 @@ <argument name="map" xsi:type="array"> <item name="OPTIONS" xsi:type="string">\Magento\Framework\App\Action\HttpOptionsActionInterface</item> <item name="GET" xsi:type="string">\Magento\Framework\App\Action\HttpGetActionInterface</item> - <item name="HEAD" xsi:type="string">\Magento\Framework\App\Action\HttpHeadActionInterface</item> + <item name="HEAD" xsi:type="string">\Magento\Framework\App\Action\HttpGetActionInterface</item> <item name="POST" xsi:type="string">\Magento\Framework\App\Action\HttpPostActionInterface</item> <item name="PUT" xsi:type="string">\Magento\Framework\App\Action\HttpPutActionInterface</item> <item name="PATCH" xsi:type="string">\Magento\Framework\App\Action\HttpPatchActionInterface</item> @@ -1764,4 +1764,8 @@ <argument name="delayTimeout" xsi:type="number">20</argument> </arguments> </type> + <preference for="Magento\Framework\MessageQueue\PoisonPill\PoisonPillCompareInterface" type="Magento\Framework\MessageQueue\PoisonPill\PoisonPillCompare"/> + <preference for="Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface" type="Magento\Framework\MessageQueue\PoisonPill\PoisonPillPut"/> + <preference for="Magento\Framework\MessageQueue\PoisonPill\PoisonPillReadInterface" type="Magento\Framework\MessageQueue\PoisonPill\PoisonPillRead"/> + <preference for="Magento\Framework\MessageQueue\CallbackInvokerInterface" type="Magento\Framework\MessageQueue\CallbackInvoker"/> </config> diff --git a/composer.json b/composer.json index 525f3a21d9577..cff2d676038d7 100644 --- a/composer.json +++ b/composer.json @@ -159,6 +159,7 @@ "magento/module-google-analytics": "*", "magento/module-google-optimizer": "*", "magento/module-graph-ql": "*", + "magento/module-graph-ql-cache": "*", "magento/module-catalog-graph-ql": "*", "magento/module-catalog-url-rewrite-graph-ql": "*", "magento/module-configurable-product-graph-ql": "*", diff --git a/composer.lock b/composer.lock index 06ab71ee75970..5d9f7fbdf695c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c43d19692d25afef14dd42eb893eb4ca", + "content-hash": "597fe6a47b695221292482fead498d83", "packages": [ { "name": "braintree/braintree_php", diff --git a/dev/tests/api-functional/_files/Magento/TestModuleJoinDirectives/etc/extension_attributes.xml b/dev/tests/api-functional/_files/Magento/TestModuleJoinDirectives/etc/extension_attributes.xml index 3bd64a7aff728..8254a9d8e92cf 100644 --- a/dev/tests/api-functional/_files/Magento/TestModuleJoinDirectives/etc/extension_attributes.xml +++ b/dev/tests/api-functional/_files/Magento/TestModuleJoinDirectives/etc/extension_attributes.xml @@ -31,4 +31,16 @@ </join> </attribute> </extension_attributes> + <extension_attributes for="Magento\Sales\Api\Data\OrderInterface"> + <attribute code="orderApiTestAttribute" type="Magento\User\Api\Data\UserInterface"> + <join reference_table="admin_user" + join_on_field="store_id" + reference_field="user_id" + > + <field>firstname</field> + <field>lastname</field> + <field>email</field> + </join> + </attribute> + </extension_attributes> </config> diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php index add0510c6b40c..cdc9d5e8a4c9c 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php @@ -51,7 +51,7 @@ public function __construct( * @return array|string|int|float|bool * @throws \Exception */ - public function postQuery(string $query, array $variables = [], string $operationName = '', array $headers = []) + public function post(string $query, array $variables = [], string $operationName = '', array $headers = []) { $url = $this->getEndpointUrl(); $headers = array_merge($headers, ['Accept: application/json', 'Content-Type: application/json']); @@ -63,19 +63,92 @@ public function postQuery(string $query, array $variables = [], string $operatio $postData = $this->json->jsonEncode($requestArray); $responseBody = $this->curlClient->post($url, $postData, $headers); - $responseBodyArray = $this->json->jsonDecode($responseBody); + return $this->processResponse($responseBody); + } + + /** + * Perform HTTP GET request for query + * + * @param string $query + * @param array $variables + * @param string $operationName + * @param array $headers + * @return mixed + * @throws \Exception + */ + public function get(string $query, array $variables = [], string $operationName = '', array $headers = []) + { + $url = $this->getEndpointUrl(); + $requestArray = [ + 'query' => $query, + 'variables' => $variables ? $this->json->jsonEncode($variables) : null, + 'operationName' => $operationName ?? null + ]; + array_filter($requestArray); - if (!is_array($responseBodyArray)) { - throw new \Exception('Unknown GraphQL response body: ' . json_encode($responseBodyArray)); + try { + $responseBody = $this->curlClient->get($url, $requestArray, $headers); + } catch (\Exception $e) { + // if response code > 400 then response is the exception message + $responseBody = $e->getMessage(); } + return $this->processResponse($responseBody); + } - $this->processErrors($responseBodyArray); + /** + * Process response from GraphQl server + * + * @param string $response + * @return mixed + * @throws \Exception + */ + private function processResponse(string $response) + { + $responseArray = $this->json->jsonDecode($response); + + if (!is_array($responseArray)) { + //phpcs:ignore Magento2.Exceptions.DirectThrow + throw new \Exception('Unknown GraphQL response body: ' . $response); + } - if (!isset($responseBodyArray['data'])) { - throw new \Exception('Unknown GraphQL response body: ' . json_encode($responseBodyArray)); - } else { - return $responseBodyArray['data']; + $this->processErrors($responseArray); + + if (!isset($responseArray['data'])) { + //phpcs:ignore Magento2.Exceptions.DirectThrow + throw new \Exception('Unknown GraphQL response body: ' . $response); } + + return $responseArray['data']; + } + + /** + * Perform HTTP GET request, return response data and headers + * + * @param string $query + * @param array $variables + * @param string $operationName + * @param array $headers + * @return array + */ + public function getWithResponseHeaders( + string $query, + array $variables = [], + string $operationName = '', + array $headers = [] + ): array { + $url = $this->getEndpointUrl(); + $requestArray = [ + 'query' => $query, + 'variables' => $variables ? $this->json->jsonEncode($variables) : null, + 'operationName' => !empty($operationName) ? $operationName : null + ]; + array_filter($requestArray); + + $response = $this->curlClient->getWithFullResponse($url, $requestArray, $headers); + $responseBody = $this->processResponse($response['body']); + $responseHeaders = !empty($response['header']) ? $this->processResponseHeaders($response['header']) : []; + + return ['headers' => $responseHeaders, 'body' => $responseBody]; } /** @@ -107,6 +180,7 @@ private function processErrors($responseBodyArray) $responseBodyArray ); } + //phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception('GraphQL responded with an unknown error: ' . json_encode($responseBodyArray)); } } @@ -121,4 +195,27 @@ public function getEndpointUrl() { return rtrim(TESTS_BASE_URL, '/') . '/graphql'; } + + /** + * Parse response headers into associative array + * + * @param string $headers + * @return array + */ + private function processResponseHeaders(string $headers): array + { + $headersArray = []; + + $headerLines = preg_split('/((\r?\n)|(\r\n?))/', $headers); + foreach ($headerLines as $headerLine) { + $headerParts = preg_split('/:/', $headerLine); + if (count($headerParts) == 2) { + $headersArray[trim($headerParts[0])] = trim($headerParts[1]); + } elseif (preg_match('/HTTP\/[\.0-9]+/', $headerLine)) { + $headersArray[trim('Status-Line')] = trim($headerLine); + } + } + + return $headersArray; + } } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php index 790581c476da1..94eb5ddec8604 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php @@ -6,6 +6,7 @@ namespace Magento\TestFramework\TestCase; use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\App\Request\Http; /** * Test case for Web API functional tests for Graphql. @@ -27,7 +28,7 @@ abstract class GraphQlAbstract extends WebapiAbstract private $appCache; /** - * Perform GraphQL call to the system under test. + * Perform GraphQL query call via GET to the system under test. * * @see \Magento\TestFramework\TestCase\GraphQl\Client::call() * @param string $query @@ -43,7 +44,55 @@ public function graphQlQuery( string $operationName = '', array $headers = [] ) { - return $this->getGraphQlClient()->postQuery( + return $this->getGraphQlClient()->get( + $query, + $variables, + $operationName, + $this->composeHeaders($headers) + ); + } + + /** + * Perform GraphQL mutations call via POST to the system under test. + * + * @see \Magento\TestFramework\TestCase\GraphQl\Client::call() + * @param string $query + * @param array $variables + * @param string $operationName + * @param array $headers + * @return array|int|string|float|bool GraphQL call results + * @throws \Exception + */ + public function graphQlMutation( + string $query, + array $variables = [], + string $operationName = '', + array $headers = [] + ) { + return $this->getGraphQlClient()->post( + $query, + $variables, + $operationName, + $this->composeHeaders($headers) + ); + } + + /** + * Perform GraphQL query via GET and returns only the response headers + * + * @param string $query + * @param array $variables + * @param string $operationName + * @param array $headers + * @return array + */ + public function graphQlQueryWithResponseHeaders( + string $query, + array $variables = [], + string $operationName = '', + array $headers = [] + ): array { + return $this->getGraphQlClient()->getWithResponseHeaders( $query, $variables, $operationName, diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php index 787f207ef33e9..67691a3c909be 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php @@ -32,6 +32,25 @@ public function get($url, $data = [], $headers = []) return $resp["body"]; } + /** + * Perform a HTTP GET request and return the full response + * + * @param string $url + * @param array $data + * @param array $headers + * @return array + */ + public function getWithFullResponse($url, $data = [], $headers = []): array + { + if (!empty($data)) { + $url .= '?' . http_build_query($data); + } + + $curlOpts = []; + $curlOpts[CURLOPT_CUSTOMREQUEST] = \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET; + return $this->invokeApi($url, $curlOpts, $headers); + } + /** * Perform HTTP DELETE request * @@ -98,8 +117,10 @@ public function put($url, $data, $headers = []) public function invokeApi($url, $additionalCurlOpts, $headers = []) { // initialize cURL + // phpcs:ignore Magento2.Functions.DiscouragedFunction $curl = curl_init($url); if ($curl === false) { + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception("Error Initializing cURL for baseUrl: " . $url); } @@ -108,28 +129,40 @@ public function invokeApi($url, $additionalCurlOpts, $headers = []) // add CURL opts foreach ($curlOpts as $opt => $val) { + // phpcs:ignore Magento2.Functions.DiscouragedFunction curl_setopt($curl, $opt, $val); } + // phpcs:ignore Magento2.Functions.DiscouragedFunction $response = curl_exec($curl); if ($response === false) { - throw new \Exception(curl_error($curl)); + // phpcs:ignore Magento2.Functions.DiscouragedFunction + $error = curl_error($curl); + // phpcs:ignore Magento2.Exceptions.DirectThrow + throw new \Exception($error); } $resp = []; + // phpcs:ignore Magento2.Functions.DiscouragedFunction $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE); $resp["header"] = substr($response, 0, $headerSize); $resp["body"] = substr($response, $headerSize); + // phpcs:ignore Magento2.Functions.DiscouragedFunction $resp["meta"] = curl_getinfo($curl); if ($resp["meta"] === false) { - throw new \Exception(curl_error($curl)); + // phpcs:ignore Magento2.Functions.DiscouragedFunction + $error = curl_error($curl); + // phpcs:ignore Magento2.Exceptions.DirectThrow + throw new \Exception($error); } + // phpcs:ignore Magento2.Functions.DiscouragedFunction curl_close($curl); $meta = $resp["meta"]; if ($meta && $meta['http_code'] >= 400) { + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception($resp["body"], $meta['http_code']); } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClientWithCookies.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClientWithCookies.php index ecd748d89bb20..1dd9d17f904b4 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClientWithCookies.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClientWithCookies.php @@ -23,8 +23,6 @@ class CurlClientWithCookies protected $jsonSerializer; /** - * CurlClient constructor. - * * @param CurlClient $curlClient * @param \Magento\TestFramework\Helper\JsonSerializer $jsonSerializer */ @@ -38,6 +36,8 @@ public function __construct( } /** + * Compose the resource url + * * @param string $resourcePath Resource URL like /V1/Resource1/123 * @return string resource URL * @throws \Exception @@ -82,7 +82,7 @@ public function get($resourcePath, $data = [], $headers = []) * ], * ] * - * @param $headerBlock + * @param string $headerBlock * @return array */ private function cookieParse($headerBlock) diff --git a/dev/tests/api-functional/testsuite/Magento/Downloadable/Api/ProductRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Downloadable/Api/ProductRepositoryTest.php index 4608b255459b6..769abadf20585 100644 --- a/dev/tests/api-functional/testsuite/Magento/Downloadable/Api/ProductRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Downloadable/Api/ProductRepositoryTest.php @@ -51,11 +51,13 @@ protected function getLinkData() 'link_type' => 'file', 'link_file_content' => [ 'name' => 'link1_content.jpg', + // phpcs:ignore Magento2.Functions.DiscouragedFunction 'file_data' => base64_encode(file_get_contents($this->testImagePath)), ], 'sample_type' => 'file', 'sample_file_content' => [ 'name' => 'link1_sample.jpg', + // phpcs:ignore Magento2.Functions.DiscouragedFunction 'file_data' => base64_encode(file_get_contents($this->testImagePath)), ], ], @@ -114,6 +116,7 @@ protected function getSampleData() 'sample_type' => 'file', 'sample_file_content' => [ 'name' => 'sample2.jpg', + // phpcs:ignore Magento2.Functions.DiscouragedFunction 'file_data' => base64_encode(file_get_contents($this->testImagePath)), ], ], @@ -146,7 +149,9 @@ protected function createDownloadableProduct() "price" => 10, 'attribute_set_id' => 4, "extension_attributes" => [ + // phpcs:ignore Magento2.Functions.DiscouragedFunction "downloadable_product_links" => array_values($this->getLinkData()), + // phpcs:ignore Magento2.Functions.DiscouragedFunction "downloadable_product_samples" => array_values($this->getSampleData()), ], ]; @@ -301,11 +306,13 @@ public function testUpdateDownloadableProductLinksWithNewFile() 'link_type' => 'file', 'link_file_content' => [ 'name' => $linkFile . $extension, + // phpcs:ignore Magento2.Functions.DiscouragedFunction 'file_data' => base64_encode(file_get_contents($this->testImagePath)), ], 'sample_type' => 'file', 'sample_file_content' => [ 'name' => $sampleFile . $extension, + // phpcs:ignore Magento2.Functions.DiscouragedFunction 'file_data' => base64_encode(file_get_contents($this->testImagePath)), ], ]; @@ -319,11 +326,13 @@ public function testUpdateDownloadableProductLinksWithNewFile() 'link_type' => 'file', 'link_file_content' => [ 'name' => 'link2_content.jpg', + // phpcs:ignore Magento2.Functions.DiscouragedFunction 'file_data' => base64_encode(file_get_contents($this->testImagePath)), ], 'sample_type' => 'file', 'sample_file_content' => [ 'name' => 'link2_sample.jpg', + // phpcs:ignore Magento2.Functions.DiscouragedFunction 'file_data' => base64_encode(file_get_contents($this->testImagePath)), ], ]; @@ -463,6 +472,7 @@ public function testUpdateDownloadableProductSamplesWithNewFile() 'sample_type' => 'file', 'sample_file_content' => [ 'name' => 'sample1.jpg', + // phpcs:ignore Magento2.Functions.DiscouragedFunction 'file_data' => base64_encode(file_get_contents($this->testImagePath)), ], ]; @@ -472,6 +482,11 @@ public function testUpdateDownloadableProductSamplesWithNewFile() 'title' => 'sample2_updated', 'sort_order' => 2, 'sample_type' => 'file', + 'sample_file_content' => [ + 'name' => 'sample2.jpg', + // phpcs:ignore Magento2.Functions.DiscouragedFunction + 'file_data' => base64_encode(file_get_contents($this->testImagePath)), + ], ]; $response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["downloadable_product_samples"] = @@ -606,7 +621,7 @@ protected function deleteProductBySku($productSku) protected function saveProduct($product) { if (isset($product['custom_attributes'])) { - for ($i=0; $i<sizeof($product['custom_attributes']); $i++) { + for ($i = 0, $iMax = count($product['custom_attributes']); $i < $iMax; $i++) { if ($product['custom_attributes'][$i]['attribute_code'] == 'category_ids' && !is_array($product['custom_attributes'][$i]['value']) ) { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php index b2ce0400f7d83..63073a389f278 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php @@ -347,6 +347,7 @@ public function testCategoryProducts() $this->assertAttributes($response['category']['products']['items'][0]); $this->assertWebsites($firstProduct, $response['category']['products']['items'][0]['websites']); } + /** * @magentoApiDataFixture Magento/Catalog/_files/categories.php */ diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductInMultipleStoresTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductInMultipleStoresTest.php index 2ef081ebcfa90..d17b434f39d9f 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductInMultipleStoresTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductInMultipleStoresTest.php @@ -10,10 +10,13 @@ use Magento\TestFramework\ObjectManager; use Magento\TestFramework\TestCase\GraphQlAbstract; +/** + * Class ProductInMultipleStoresTest + */ class ProductInMultipleStoresTest extends GraphQlAbstract { - /** + * Test a product from a specific and a default store * * @magentoApiDataFixture Magento/Store/_files/second_store.php * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php @@ -94,7 +97,7 @@ public function testProductFromSpecificAndDefaultStore() $nonExistingStoreCode = "non_existent_store"; $headerMapInvalidStoreCode = ['Store' => $nonExistingStoreCode]; $this->expectException(\Exception::class); - $this->expectExceptionMessage('Store code non_existent_store does not exist'); + $this->expectExceptionMessage('Requested store is not found'); $this->graphQlQuery($query, [], '', $headerMapInvalidStoreCode); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php index 17c2af8dc59d0..6f27693eeb4a7 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php @@ -7,28 +7,19 @@ namespace Magento\GraphQl\CatalogInventory; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; -use Magento\Quote\Model\QuoteFactory; -use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; -use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +/** + * Add simple product to cart testcases related to inventory + */ class AddProductToCartTest extends GraphQlAbstract { /** - * @var QuoteResource - */ - private $quoteResource; - - /** - * @var QuoteFactory + * @var GetMaskedQuoteIdByReservedOrderId */ - private $quoteFactory; - - /** - * @var QuoteIdToMaskedQuoteIdInterface - */ - private $quoteIdToMaskedId; + private $getMaskedQuoteIdByReservedOrderId; /** * @inheritdoc @@ -36,9 +27,7 @@ class AddProductToCartTest extends GraphQlAbstract protected function setUp() { $objectManager = Bootstrap::getObjectManager(); - $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quoteFactory = $objectManager->get(QuoteFactory::class); - $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); } /** @@ -50,12 +39,11 @@ protected function setUp() public function testAddProductIfQuantityIsNotAvailable() { $sku = 'simple'; - $qty = 200; + $quantity = 200; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); - $maskedQuoteId = $this->getMaskedQuoteId(); - $query = $this->getAddSimpleProductQuery($maskedQuoteId, $sku, $qty); - $this->graphQlQuery($query); - self::fail('Should be "The requested qty is not available" error message.'); + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); + $this->graphQlMutation($query); } /** @@ -70,32 +58,54 @@ public function testAddMoreProductsThatAllowed() $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/167'); $sku = 'custom-design-simple-product'; - $qty = 7; + $quantity = 7; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); - $maskedQuoteId = $this->getMaskedQuoteId(); - $query = $this->getAddSimpleProductQuery($maskedQuoteId, $sku, $qty); - $this->graphQlQuery($query); - self::fail('Should be "The most you may purchase is 5." error message.'); + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); + $this->graphQlMutation($query); } /** - * @return string + * @magentoApiDataFixture Magento/Catalog/_files/products.php + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + * @expectedException \Exception + * @expectedExceptionMessage Please enter a number greater than 0 in this field. + */ + public function testAddSimpleProductToCartWithNegativeQuantity() + { + $sku = 'simple'; + $quantity = -2; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); + + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php */ - public function getMaskedQuoteId() : string + public function testAddProductIfQuantityIsDecimal() { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id'); + $sku = 'simple_product'; + $quantity = 0.2; + + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); - return $this->quoteIdToMaskedId->execute((int)$quote->getId()); + $this->expectExceptionMessage( + "Could not add the product with SKU {$sku} to the shopping cart: The fewest you may purchase is 1" + ); + $this->graphQlMutation($query); } /** * @param string $maskedQuoteId * @param string $sku - * @param int $qty + * @param float $quantity * @return string */ - public function getAddSimpleProductQuery(string $maskedQuoteId, string $sku, int $qty) : string + private function getQuery(string $maskedQuoteId, string $sku, float $quantity) : string { return <<<QUERY mutation { @@ -105,7 +115,7 @@ public function getAddSimpleProductQuery(string $maskedQuoteId, string $sku, int cartItems: [ { data: { - qty: $qty + quantity: $quantity sku: "$sku" } } @@ -114,7 +124,7 @@ public function getAddSimpleProductQuery(string $maskedQuoteId, string $sku, int ) { cart { items { - qty + quantity } } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/UpdateCartItemsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/UpdateCartItemsTest.php new file mode 100644 index 0000000000000..3b238f8641637 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/UpdateCartItemsTest.php @@ -0,0 +1,86 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\CatalogInventory; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\GraphQl\Quote\GetQuoteItemIdByReservedQuoteIdAndSku; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for updating/removing shopping cart items + */ +class UpdateCartItemsTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @var GetQuoteItemIdByReservedQuoteIdAndSku + */ + private $getQuoteItemIdByReservedQuoteIdAndSku; + + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->getQuoteItemIdByReservedQuoteIdAndSku = $objectManager->get( + GetQuoteItemIdByReservedQuoteIdAndSku::class + ); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testUpdateCartItemDecimalQuantity() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $itemId = $this->getQuoteItemIdByReservedQuoteIdAndSku->execute('test_quote', 'simple_product'); + + $quantity = 0.5; + $this->expectExceptionMessage( + "Could not update the product with SKU simple_product: The fewest you may purchase is 1" + ); + $query = $this->getQuery($maskedQuoteId, $itemId, $quantity); + $this->graphQlMutation($query); + } + + /** + * @param string $maskedQuoteId + * @param int $itemId + * @param float $quantity + * @return string + */ + private function getQuery(string $maskedQuoteId, int $itemId, float $quantity): string + { + return <<<QUERY +mutation { + updateCartItems(input: { + cart_id: "{$maskedQuoteId}" + cart_items:[ + { + cart_item_id: {$itemId} + quantity: {$quantity} + } + ] + }) { + cart { + items { + id + quantity + } + } + } +} +QUERY; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddConfigurableProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php similarity index 53% rename from dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddConfigurableProductToCartTest.php rename to dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php index b3f16c8734203..6810f9d44ce91 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddConfigurableProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php @@ -5,30 +5,21 @@ */ declare(strict_types=1); -namespace Magento\GraphQl\Quote; +namespace Magento\GraphQl\ConfigurableProduct; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; -use Magento\Quote\Model\Quote; -use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; -use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +/** + * Add configurable product to cart testcases + */ class AddConfigurableProductToCartTest extends GraphQlAbstract { /** - * @var QuoteResource - */ - private $quoteResource; - - /** - * @var Quote + * @var GetMaskedQuoteIdByReservedOrderId */ - private $quote; - - /** - * @var QuoteIdToMaskedQuoteIdInterface - */ - private $quoteIdToMaskedId; + private $getMaskedQuoteIdByReservedOrderId; /** * @inheritdoc @@ -36,9 +27,7 @@ class AddConfigurableProductToCartTest extends GraphQlAbstract protected function setUp() { $objectManager = Bootstrap::getObjectManager(); - $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quote = $objectManager->create(Quote::class); - $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); } /** @@ -48,15 +37,14 @@ protected function setUp() public function testAddConfigurableProductToCart() { $variantSku = 'simple_41'; - $qty = 2; - - $maskedQuoteId = $this->getMaskedQuoteId(); + $quantity = 2; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); - $query = $this->getAddConfigurableProductMutationQuery($maskedQuoteId, $variantSku, $qty); + $query = $this->getQuery($maskedQuoteId, $variantSku, $quantity); + $response = $this->graphQlMutation($query); - $response = $this->graphQlQuery($query); $cartItems = $response['addConfigurableProductsToCart']['cart']['items']; - self::assertEquals($qty, $cartItems[0]['qty']); + self::assertEquals($quantity, $cartItems[0]['quantity']); self::assertEquals($variantSku, $cartItems[0]['product']['sku']); } @@ -69,12 +57,11 @@ public function testAddConfigurableProductToCart() public function testAddProductIfQuantityIsNotAvailable() { $variantSku = 'simple_41'; - $qty = 200; + $quantity = 200; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); - $maskedQuoteId = $this->getMaskedQuoteId(); - $query = $this->getAddConfigurableProductMutationQuery($maskedQuoteId, $variantSku, $qty); - - $this->graphQlQuery($query); + $query = $this->getQuery($maskedQuoteId, $variantSku, $quantity); + $this->graphQlMutation($query); } /** @@ -86,36 +73,20 @@ public function testAddProductIfQuantityIsNotAvailable() public function testAddOutOfStockProduct() { $variantSku = 'simple_1010'; - $qty = 1; - $maskedQuoteId = $this->getMaskedQuoteId(); - $query = $this->getAddConfigurableProductMutationQuery($maskedQuoteId, $variantSku, $qty); - - $this->graphQlQuery($query); - } + $quantity = 1; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); - /** - * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php - * @return string - * @throws \Magento\Framework\Exception\NoSuchEntityException - */ - private function getMaskedQuoteId() - { - $this->quoteResource->load( - $this->quote, - 'test_order_1', - 'reserved_order_id' - ); - return $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + $query = $this->getQuery($maskedQuoteId, $variantSku, $quantity); + $this->graphQlMutation($query); } /** * @param string $maskedQuoteId - * @param string $sku - * @param int $qty - * + * @param string $variantSku + * @param int $quantity * @return string */ - private function getAddConfigurableProductMutationQuery(string $maskedQuoteId, string $variantSku, int $qty): string + private function getQuery(string $maskedQuoteId, string $variantSku, int $quantity): string { return <<<QUERY mutation { @@ -126,7 +97,7 @@ private function getAddConfigurableProductMutationQuery(string $maskedQuoteId, s { variant_sku: "{$variantSku}" data: { - qty: {$qty} + quantity: {$quantity} sku: "{$variantSku}" } } @@ -136,7 +107,7 @@ private function getAddConfigurableProductMutationQuery(string $maskedQuoteId, s cart { items { id - qty + quantity product { name sku diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ChangeCustomerPasswordTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ChangeCustomerPasswordTest.php index 84c111bd25fd4..ab6a1d4b464dd 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ChangeCustomerPasswordTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ChangeCustomerPasswordTest.php @@ -14,6 +14,9 @@ use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; +/** + * Test change customer password + */ class ChangeCustomerPasswordTest extends GraphQlAbstract { /** @@ -50,7 +53,7 @@ public function testChangePassword() $query = $this->getChangePassQuery($oldCustomerPassword, $newCustomerPassword); $headerMap = $this->getCustomerAuthHeaders($customerEmail, $oldCustomerPassword); - $response = $this->graphQlQuery($query, [], '', $headerMap); + $response = $this->graphQlMutation($query, [], '', $headerMap); $this->assertEquals($customerEmail, $response['changeCustomerPassword']['email']); try { @@ -69,7 +72,7 @@ public function testChangePassword() public function testChangePasswordIfUserIsNotAuthorizedTest() { $query = $this->getChangePassQuery('currentpassword', 'newpassword'); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -87,7 +90,7 @@ public function testChangeWeakPassword() $this->expectException(\Exception::class); $this->expectExceptionMessageRegExp('/Minimum of different classes of characters in password is.*/'); - $this->graphQlQuery($query, [], '', $headerMap); + $this->graphQlMutation($query, [], '', $headerMap); } /** @@ -105,7 +108,7 @@ public function testChangePasswordIfPasswordIsInvalid() $query = $this->getChangePassQuery($incorrectPassword, $newCustomerPassword); $headerMap = $this->getCustomerAuthHeaders($customerEmail, $oldCustomerPassword); - $this->graphQlQuery($query, [], '', $headerMap); + $this->graphQlMutation($query, [], '', $headerMap); } private function getChangePassQuery($currentPassword, $newPassword) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerAddressTest.php index 264a2c7de4783..891c74ca3c1e2 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerAddressTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerAddressTest.php @@ -120,7 +120,7 @@ public function testCreateCustomerAddress() $userName = 'customer@example.com'; $password = 'password'; - $response = $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $response = $this->graphQlMutation($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); $this->assertArrayHasKey('createCustomerAddress', $response); $this->assertArrayHasKey('customer_id', $response['createCustomerAddress']); $this->assertEquals($customerId, $response['createCustomerAddress']['customer_id']); @@ -161,7 +161,7 @@ public function testCreateCustomerAddressIfUserIsNotAuthorized() } } MUTATION; - $this->graphQlQuery($mutation); + $this->graphQlMutation($mutation); } /** @@ -198,7 +198,7 @@ public function testCreateCustomerAddressWithMissingAttribute() $userName = 'customer@example.com'; $password = 'password'; - $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $this->graphQlMutation($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); } /** @@ -264,7 +264,7 @@ public function testCreateCustomerAddressWithRedundantStreetLine() $password = 'password'; self::expectExceptionMessage('"Street Address" cannot contain more than 2 lines.'); - $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $this->graphQlMutation($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php index 388028c4ca750..fc51f57a83a76 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php @@ -66,7 +66,7 @@ public function testCreateCustomerAccountWithPassword() } } QUERY; - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); $this->assertEquals($newFirstname, $response['createCustomer']['customer']['firstname']); $this->assertEquals($newLastname, $response['createCustomer']['customer']['lastname']); @@ -103,7 +103,7 @@ public function testCreateCustomerAccountWithoutPassword() } } QUERY; - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); $this->assertEquals($newFirstname, $response['createCustomer']['customer']['firstname']); $this->assertEquals($newLastname, $response['createCustomer']['customer']['lastname']); @@ -134,7 +134,7 @@ public function testCreateCustomerIfInputDataIsEmpty() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -167,7 +167,7 @@ public function testCreateCustomerIfEmailMissed() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -202,7 +202,7 @@ public function testCreateCustomerIfEmailIsNotValid() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -238,7 +238,7 @@ public function testCreateCustomerIfPassedAttributeDosNotExistsInCustomerInput() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } public function tearDown() diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/DeleteCustomerAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/DeleteCustomerAddressTest.php index 1153b9662b41a..bdfd428a78c20 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/DeleteCustomerAddressTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/DeleteCustomerAddressTest.php @@ -13,6 +13,9 @@ use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\Integration\Api\CustomerTokenServiceInterface; +/** + * Delete customer address tests + */ class DeleteCustomerAddressTest extends GraphQlAbstract { /** @@ -55,7 +58,7 @@ public function testDeleteCustomerAddress() deleteCustomerAddress(id: {$addressId}) } MUTATION; - $response = $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $response = $this->graphQlMutation($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); $this->assertArrayHasKey('deleteCustomerAddress', $response); $this->assertEquals(true, $response['deleteCustomerAddress']); } @@ -73,7 +76,7 @@ public function testDeleteCustomerAddressIfUserIsNotAuthorized() deleteCustomerAddress(id: {$addressId}) } MUTATION; - $this->graphQlQuery($mutation); + $this->graphQlMutation($mutation); } /** @@ -99,7 +102,7 @@ public function testDeleteDefaultShippingCustomerAddress() deleteCustomerAddress(id: {$addressId}) } MUTATION; - $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $this->graphQlMutation($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); } /** @@ -125,7 +128,7 @@ public function testDeleteDefaultBillingCustomerAddress() deleteCustomerAddress(id: {$addressId}) } MUTATION; - $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $this->graphQlMutation($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); } /** @@ -144,7 +147,7 @@ public function testDeleteNonExistCustomerAddress() deleteCustomerAddress(id: 9999) } MUTATION; - $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $this->graphQlMutation($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GenerateCustomerTokenTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GenerateCustomerTokenTest.php index ae28e23a28bf1..88eaeaa8f9dd5 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GenerateCustomerTokenTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GenerateCustomerTokenTest.php @@ -38,7 +38,7 @@ public function testGenerateCustomerValidToken() } MUTATION; - $response = $this->graphQlQuery($mutation); + $response = $this->graphQlMutation($mutation); $this->assertArrayHasKey('generateCustomerToken', $response); $this->assertInternalType('array', $response['generateCustomerToken']); } @@ -66,6 +66,6 @@ public function testGenerateCustomerTokenWithInvalidCredentials() $this->expectException(\Exception::class); $this->expectExceptionMessage('GraphQL response contains errors: The account sign-in' . ' ' . 'was incorrect or your account is disabled temporarily. Please wait and try again later.'); - $this->graphQlQuery($mutation); + $this->graphQlMutation($mutation); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/RevokeCustomerTokenTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/RevokeCustomerTokenTest.php index 9bdbf3059eeaf..fc0c02bae5508 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/RevokeCustomerTokenTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/RevokeCustomerTokenTest.php @@ -36,7 +36,7 @@ public function testRevokeCustomerTokenValidCredentials() $customerToken = $customerTokenService->createCustomerAccessToken($userName, $password); $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; - $response = $this->graphQlQuery($query, [], '', $headerMap); + $response = $this->graphQlMutation($query, [], '', $headerMap); $this->assertTrue($response['revokeCustomerToken']['result']); } @@ -53,6 +53,6 @@ public function testRevokeCustomerTokenForGuestCustomer() } } QUERY; - $this->graphQlQuery($query, [], ''); + $this->graphQlMutation($query, [], ''); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/SubscriptionStatusTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/SubscriptionStatusTest.php index 191ea1ae6b877..2b54c97cd1e97 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/SubscriptionStatusTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/SubscriptionStatusTest.php @@ -12,6 +12,9 @@ use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; +/** + * Tests for subscription status + */ class SubscriptionStatusTest extends GraphQlAbstract { /** @@ -88,7 +91,12 @@ public function testChangeSubscriptionStatusTest() } } QUERY; - $response = $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $response = $this->graphQlMutation( + $query, + [], + '', + $this->getCustomerAuthHeaders($currentEmail, $currentPassword) + ); $this->assertTrue($response['updateCustomer']['customer']['is_subscribed']); } @@ -111,7 +119,7 @@ public function testChangeSubscriptionStatuIfUserIsNotAuthorizedTest() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php index 6a9708b4f86a2..e7a7eda2897b2 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php @@ -14,6 +14,9 @@ use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\Integration\Api\CustomerTokenServiceInterface; +/** + * Update customer address tests + */ class UpdateCustomerAddressTest extends GraphQlAbstract { /** @@ -128,7 +131,7 @@ public function testUpdateCustomerAddress() } MUTATION; - $response = $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $response = $this->graphQlMutation($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); $this->assertArrayHasKey('updateCustomerAddress', $response); $this->assertArrayHasKey('customer_id', $response['updateCustomerAddress']); $this->assertEquals($customerId, $response['updateCustomerAddress']['customer_id']); @@ -158,7 +161,7 @@ public function testUpdateCustomerAddressIfUserIsNotAuthorized() } } MUTATION; - $this->graphQlQuery($mutation); + $this->graphQlMutation($mutation); } /** @@ -187,7 +190,7 @@ public function testUpdateCustomerAddressWithMissingAttribute() } } MUTATION; - $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $this->graphQlMutation($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerTest.php index df45e1de771d9..08933f47191b9 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerTest.php @@ -13,6 +13,9 @@ use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; +/** + * Tests for update customer + */ class UpdateCustomerTest extends GraphQlAbstract { /** @@ -87,7 +90,12 @@ public function testUpdateCustomer() } } QUERY; - $response = $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $response = $this->graphQlMutation( + $query, + [], + '', + $this->getCustomerAuthHeaders($currentEmail, $currentPassword) + ); $this->assertEquals($newPrefix, $response['updateCustomer']['customer']['prefix']); $this->assertEquals($newFirstname, $response['updateCustomer']['customer']['firstname']); @@ -123,7 +131,7 @@ public function testUpdateCustomerIfInputDataIsEmpty() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $this->graphQlMutation($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); } /** @@ -147,7 +155,7 @@ public function testUpdateCustomerIfUserIsNotAuthorized() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -176,7 +184,7 @@ public function testUpdateCustomerIfAccountIsLocked() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $this->graphQlMutation($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); } /** @@ -203,7 +211,7 @@ public function testUpdateEmailIfPasswordIsMissed() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $this->graphQlMutation($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); } /** @@ -232,7 +240,7 @@ public function testUpdateEmailIfPasswordIsInvalid() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $this->graphQlMutation($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); } /** @@ -260,7 +268,7 @@ public function testUpdateEmailIfEmailAlreadyExists() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $this->graphQlMutation($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Framework/QueryComplexityLimiterTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Framework/QueryComplexityLimiterTest.php index 352947714360a..e784061d5562f 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Framework/QueryComplexityLimiterTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Framework/QueryComplexityLimiterTest.php @@ -393,7 +393,8 @@ public function testQueryComplexityIsLimited() QUERY; self::expectExceptionMessageRegExp('/Max query complexity should be 300 but got 302/'); - $this->graphQlQuery($query); + //Use POST request because request uri is too large for some servers + $this->graphQlMutation($query); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/CacheTagTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/CacheTagTest.php new file mode 100644 index 0000000000000..23bcd342ec994 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/CacheTagTest.php @@ -0,0 +1,202 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\PageCache; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\TestFramework\ObjectManager; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test the caching works properly for products and categories + */ +class CacheTagTest extends GraphQlAbstract +{ + /** + * @inheritdoc + */ + protected function setUp() + { + $this->markTestSkipped( + 'This test will stay skipped until DEVOPS-4924 is resolved' + ); + } + + /** + * Test if Magento cache tags and debug headers for products are generated properly + * + * @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php + */ + public function testCacheTagsAndCacheDebugHeaderForProducts() + { + $productSku='simple2'; + $query + = <<<QUERY + { + products(filter: {sku: {eq: "{$productSku}"}}) + { + items { + id + name + sku + } + } + } +QUERY; + + // Cache-debug should be a MISS when product is queried for first time + $responseMiss = $this->graphQlQueryWithResponseHeaders($query); + $this->assertArrayHasKey('X-Magento-Cache-Debug', $responseMiss['headers']); + $this->assertEquals('MISS', $responseMiss['headers']['X-Magento-Cache-Debug']); + + // Cache-debug should be a HIT for the second round + $responseHit = $this->graphQlQueryWithResponseHeaders($query); + $this->assertArrayHasKey('X-Magento-Cache-Debug', $responseHit['headers']); + $this->assertEquals('HIT', $responseHit['headers']['X-Magento-Cache-Debug']); + + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + /** @var Product $product */ + $product = $productRepository->get($productSku, false, null, true); + $product->setPrice(15); + $productRepository->save($product); + // Cache invalidation happens and cache-debug header value is a MISS after product update + $responseMiss = $this->graphQlQueryWithResponseHeaders($query); + $this->assertArrayHasKey('X-Magento-Cache-Debug', $responseMiss['headers']); + $this->assertEquals('MISS', $responseMiss['headers']['X-Magento-Cache-Debug']); + $this->assertArrayHasKey('X-Magento-Tags', $responseMiss['headers']); + $expectedCacheTags = ['cat_p','cat_p_' . $product->getId(),'FPC']; + $actualCacheTags = explode(',', $responseMiss['headers']['X-Magento-Tags']); + foreach ($expectedCacheTags as $expectedCacheTag) { + $this->assertContains($expectedCacheTag, $actualCacheTags); + } + } + + /** + * Test if X-Magento-Tags for categories are generated properly + * + * Also tests the use case for cache invalidation + * + * @magentoApiDataFixture Magento/Catalog/_files/product_in_multiple_categories.php + */ + public function testCacheTagForCategoriesWithProduct() + { + $firstProductSku = 'simple333'; + $secondProductSku = 'simple444'; + $categoryId ='4'; + + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + /** @var Product $firstProduct */ + $firstProduct = $productRepository->get($firstProductSku, false, null, true); + /** @var Product $secondProduct */ + $secondProduct = $productRepository->get($secondProductSku, false, null, true); + + $categoryQueryVariables =[ + 'id' => $categoryId, + 'pageSize'=> 10, + 'currentPage' => 1 + ]; + + $product1Query = $this->getProductQuery($firstProductSku); + $product2Query =$this->getProductQuery($secondProductSku); + $categoryQuery = $this->getCategoryQuery(); + + // cache-debug header value should be a MISS when category is loaded first time + $responseMiss = $this->graphQlQueryWithResponseHeaders($categoryQuery, $categoryQueryVariables); + $this->assertArrayHasKey('X-Magento-Cache-Debug', $responseMiss['headers']); + $this->assertEquals('MISS', $responseMiss['headers']['X-Magento-Cache-Debug']); + $this->assertArrayHasKey('X-Magento-Tags', $responseMiss['headers']); + $actualCacheTags = explode(',', $responseMiss['headers']['X-Magento-Tags']); + $expectedCacheTags = + [ + 'cat_c', + 'cat_c_' . $categoryId, + 'cat_p', + 'cat_p_' . $firstProduct->getId(), + 'cat_p_' . $secondProduct->getId(), + 'FPC' + ]; + $this->assertEquals($expectedCacheTags, $actualCacheTags); + + // Cache-debug header should be a MISS for product 1 on first request + $responseFirstProduct = $this->graphQlQueryWithResponseHeaders($product1Query); + $this->assertEquals('MISS', $responseFirstProduct['headers']['X-Magento-Cache-Debug']); + // Cache-debug header should be a MISS for product 2 during first load + $responseSecondProduct = $this->graphQlQueryWithResponseHeaders($product2Query); + $this->assertEquals('MISS', $responseSecondProduct['headers']['X-Magento-Cache-Debug']); + + $firstProduct->setPrice(20); + $productRepository->save($firstProduct); + // cache-debug header value should be MISS after updating product1 and reloading the Category + $responseMissCategory = $this->graphQlQueryWithResponseHeaders($categoryQuery, $categoryQueryVariables); + $this->assertArrayHasKey('X-Magento-Cache-Debug', $responseMissCategory['headers']); + $this->assertEquals('MISS', $responseMissCategory['headers']['X-Magento-Cache-Debug']); + + // cache-debug should be a MISS for product 1 after it is updated - cache invalidation + $responseMissFirstProduct = $this->graphQlQueryWithResponseHeaders($product1Query); + $this->assertArrayHasKey('X-Magento-Cache-Debug', $responseMissFirstProduct['headers']); + $this->assertEquals('MISS', $responseMissFirstProduct['headers']['X-Magento-Cache-Debug']); + // Cache-debug header should be a HIT for product 2 + $responseHitSecondProduct = $this->graphQlQueryWithResponseHeaders($product2Query); + $this->assertArrayHasKey('X-Magento-Cache-Debug', $responseHitSecondProduct['headers']); + $this->assertEquals('HIT', $responseHitSecondProduct['headers']['X-Magento-Cache-Debug']); + } + + /** + * Get Product query + * + * @param string $productSku + * @return string + */ + private function getProductQuery(string $productSku): string + { + $productQuery = <<<QUERY + { + products(filter: {sku: {eq: "{$productSku}"}}) + { + items { + id + name + sku + } + } + } +QUERY; + return $productQuery; + } + + /** + * Get category query + * + * @return string + */ + private function getCategoryQuery(): string + { + $categoryQueryString = <<<QUERY +query GetCategoryQuery(\$id: Int!, \$pageSize: Int!, \$currentPage: Int!) { + category(id: \$id) { + id + description + name + product_count + products(pageSize: \$pageSize, currentPage: \$currentPage) { + items { + id + name + url_key + } + total_count + } + } + } +QUERY; + + return $categoryQueryString; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/Cms/BlockCacheTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/Cms/BlockCacheTest.php new file mode 100644 index 0000000000000..5182ff791f576 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/Cms/BlockCacheTest.php @@ -0,0 +1,160 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\PageCache\Cms; + +use Magento\Cms\Model\Block; +use Magento\Cms\Model\BlockRepository; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test the caching works properly for CMS Blocks + */ +class BlockCacheTest extends GraphQlAbstract +{ + /** + * @inheritdoc + */ + protected function setUp() + { + $this->markTestSkipped( + 'This test will stay skipped until DEVOPS-4924 is resolved' + ); + } + + /** + * Test that X-Magento-Tags are correct + * + * @magentoApiDataFixture Magento/Cms/_files/block.php + */ + public function testCacheTagsHaveExpectedValue() + { + $blockIdentifier = 'fixture_block'; + $blockRepository = Bootstrap::getObjectManager()->get(BlockRepository::class); + $block = $blockRepository->getById($blockIdentifier); + $blockId = $block->getId(); + $query = $this->getBlockQuery([$blockIdentifier]); + + //cache-debug should be a MISS on first request + $response = $this->graphQlQueryWithResponseHeaders($query); + + $this->assertArrayHasKey('X-Magento-Tags', $response['headers']); + $actualTags = explode(',', $response['headers']['X-Magento-Tags']); + $expectedTags = ["cms_b", "cms_b_{$blockId}", "cms_b_{$blockIdentifier}", "FPC"]; + $this->assertEquals($expectedTags, $actualTags); + } + + /** + * Test the second request for the same block will return a cached result + * + * @magentoApiDataFixture Magento/Cms/_files/block.php + */ + public function testCacheIsUsedOnSecondRequest() + { + $blockIdentifier = 'fixture_block'; + $query = $this->getBlockQuery([$blockIdentifier]); + + //cache-debug should be a MISS on first request + $responseMiss = $this->graphQlQueryWithResponseHeaders($query); + $this->assertArrayHasKey('X-Magento-Cache-Debug', $responseMiss['headers']); + $this->assertEquals('MISS', $responseMiss['headers']['X-Magento-Cache-Debug']); + + //cache-debug should be a HIT on second request + $responseHit = $this->graphQlQueryWithResponseHeaders($query); + $this->assertArrayHasKey('X-Magento-Cache-Debug', $responseHit['headers']); + $this->assertEquals('HIT', $responseHit['headers']['X-Magento-Cache-Debug']); + //cached data should be correct + $this->assertNotEmpty($responseHit['body']); + $this->assertArrayNotHasKey('errors', $responseHit['body']); + $blocks = $responseHit['body']['cmsBlocks']['items']; + $this->assertEquals($blockIdentifier, $blocks[0]['identifier']); + $this->assertEquals('CMS Block Title', $blocks[0]['title']); + } + + /** + * Test that cache is invalidated when block is updated + * + * @magentoApiDataFixture Magento/Cms/_files/blocks.php + * @magentoApiDataFixture Magento/Cms/_files/block.php + */ + public function testCacheIsInvalidatedOnBlockUpdate() + { + $fixtureBlockIdentifier = 'fixture_block'; + $enabledBlockIdentifier = 'enabled_block'; + $fixtureBlockQuery = $this->getBlockQuery([$fixtureBlockIdentifier]); + $enabledBlockQuery = $this->getBlockQuery([$enabledBlockIdentifier]); + + //cache-debug should be a MISS on first request + $fixtureBlockMiss = $this->graphQlQueryWithResponseHeaders($fixtureBlockQuery); + $this->assertEquals('MISS', $fixtureBlockMiss['headers']['X-Magento-Cache-Debug']); + $enabledBlockMiss = $this->graphQlQueryWithResponseHeaders($enabledBlockQuery); + $this->assertEquals('MISS', $enabledBlockMiss['headers']['X-Magento-Cache-Debug']); + + //cache-debug should be a HIT on second request + $fixtureBlockHit = $this->graphQlQueryWithResponseHeaders($fixtureBlockQuery); + $this->assertEquals('HIT', $fixtureBlockHit['headers']['X-Magento-Cache-Debug']); + $enabledBlockHit = $this->graphQlQueryWithResponseHeaders($enabledBlockQuery); + $this->assertEquals('HIT', $enabledBlockHit['headers']['X-Magento-Cache-Debug']); + + $newBlockContent = 'New block content!!!'; + $this->updateBlockContent($fixtureBlockIdentifier, $newBlockContent); + + //cache-debug should be a MISS after update the block + $fixtureBlockMiss = $this->graphQlQueryWithResponseHeaders($fixtureBlockQuery); + $this->assertEquals('MISS', $fixtureBlockMiss['headers']['X-Magento-Cache-Debug']); + $enabledBlockHit = $this->graphQlQueryWithResponseHeaders($enabledBlockQuery); + $this->assertEquals('HIT', $enabledBlockHit['headers']['X-Magento-Cache-Debug']); + //updated block data should be correct + $this->assertNotEmpty($fixtureBlockMiss['body']); + $blocks = $fixtureBlockMiss['body']['cmsBlocks']['items']; + $this->assertArrayNotHasKey('errors', $fixtureBlockMiss['body']); + $this->assertEquals($fixtureBlockIdentifier, $blocks[0]['identifier']); + $this->assertEquals('CMS Block Title', $blocks[0]['title']); + $this->assertEquals($newBlockContent, $blocks[0]['content']); + } + + /** + * Update the content of a CMS block + * + * @param $identifier + * @param $newContent + * @return Block + */ + private function updateBlockContent($identifier, $newContent): Block + { + $blockRepository = Bootstrap::getObjectManager()->get(BlockRepository::class); + $block = $blockRepository->getById($identifier); + $block->setContent($newContent); + $blockRepository->save($block); + + return $block; + } + + /** + * Get cmsBlocks query + * + * @param array $identifiers + * @return string + */ + private function getBlockQuery(array $identifiers): string + { + $identifiersString = implode(',', $identifiers); + $query = <<<QUERY + { + cmsBlocks(identifiers: ["$identifiersString"]) { + items { + title + identifier + content + } + } + } +QUERY; + return $query; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/Cms/PageCacheTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/Cms/PageCacheTest.php new file mode 100644 index 0000000000000..34dc9eef4c339 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/Cms/PageCacheTest.php @@ -0,0 +1,153 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\PageCache\Cms; + +use Magento\Cms\Model\GetPageByIdentifier; +use Magento\Cms\Model\PageRepository; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test the caching works properly for CMS Pages + */ +class PageCacheTest extends GraphQlAbstract +{ + /** + * @var GetPageByIdentifier + */ + private $pageByIdentifier; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->markTestSkipped( + 'This test will stay skipped until DEVOPS-4924 is resolved' + ); + $this->pageByIdentifier = Bootstrap::getObjectManager()->get(GetPageByIdentifier::class); + } + + /** + * Test that X-Magento-Tags are correct + * + * @magentoApiDataFixture Magento/Cms/_files/pages.php + */ + public function testCacheTagsHaveExpectedValue() + { + $pageIdentifier = 'page100'; + $page = $this->pageByIdentifier->execute($pageIdentifier, 0); + $pageId = (int) $page->getId(); + + $query = $this->getPageQuery($pageId); + + //cache-debug should be a MISS on first request + $response = $this->graphQlQueryWithResponseHeaders($query); + + $this->assertArrayHasKey('X-Magento-Tags', $response['headers']); + $actualTags = explode(',', $response['headers']['X-Magento-Tags']); + $expectedTags = ["cms_p", "cms_p_{$pageId}", "FPC"]; + $this->assertEquals($expectedTags, $actualTags); + } + + /** + * Test the second request for the same page will return a cached result + * + * @magentoApiDataFixture Magento/Cms/_files/pages.php + */ + public function testCacheIsUsedOnSecondRequest() + { + $pageIdentifier = 'page100'; + $page = $this->pageByIdentifier->execute($pageIdentifier, 0); + $pageId = (int) $page->getId(); + + $query = $this->getPageQuery($pageId); + + //cache-debug should be a MISS on first request + $responseMiss = $this->graphQlQueryWithResponseHeaders($query); + $this->assertArrayHasKey('X-Magento-Cache-Debug', $responseMiss['headers']); + $this->assertEquals('MISS', $responseMiss['headers']['X-Magento-Cache-Debug']); + + //cache-debug should be a HIT on second request + $responseHit = $this->graphQlQueryWithResponseHeaders($query); + $this->assertArrayHasKey('X-Magento-Cache-Debug', $responseHit['headers']); + $this->assertEquals('HIT', $responseHit['headers']['X-Magento-Cache-Debug']); + //cached data should be correct + $this->assertNotEmpty($responseHit['body']); + $this->assertArrayNotHasKey('errors', $responseHit['body']); + $pageData = $responseHit['body']['cmsPage']; + $this->assertEquals('Cms Page 100', $pageData['title']); + } + + /** + * Test that cache is invalidated when page is updated + * + * @magentoApiDataFixture Magento/Cms/_files/pages.php + */ + public function testCacheIsInvalidatedOnPageUpdate() + { + $page100Identifier = 'page100'; + $page100 = $this->pageByIdentifier->execute($page100Identifier, 0); + $page100Id = (int) $page100->getId(); + $pageBlankIdentifier = 'page_design_blank'; + $pageBlank = $this->pageByIdentifier->execute($pageBlankIdentifier, 0); + $pageBlankId = (int) $pageBlank->getId(); + + $page100Query = $this->getPageQuery($page100Id); + $pageBlankQuery = $this->getPageQuery($pageBlankId); + + //cache-debug should be a MISS on first request + $page100Miss = $this->graphQlQueryWithResponseHeaders($page100Query); + $this->assertEquals('MISS', $page100Miss['headers']['X-Magento-Cache-Debug']); + $pageBlankMiss = $this->graphQlQueryWithResponseHeaders($pageBlankQuery); + $this->assertEquals('MISS', $pageBlankMiss['headers']['X-Magento-Cache-Debug']); + + //cache-debug should be a HIT on second request + $page100Hit = $this->graphQlQueryWithResponseHeaders($page100Query); + $this->assertEquals('HIT', $page100Hit['headers']['X-Magento-Cache-Debug']); + $pageBlankHit = $this->graphQlQueryWithResponseHeaders($pageBlankQuery); + $this->assertEquals('HIT', $pageBlankHit['headers']['X-Magento-Cache-Debug']); + + $pageRepository = Bootstrap::getObjectManager()->get(PageRepository::class); + $newPageContent = 'New page content for blank page.'; + $pageBlank->setContent($newPageContent); + $pageRepository->save($pageBlank); + + //cache-debug should be a MISS after updating the page + $pageBlankMiss = $this->graphQlQueryWithResponseHeaders($pageBlankQuery); + $this->assertEquals('MISS', $pageBlankMiss['headers']['X-Magento-Cache-Debug']); + $page100Hit = $this->graphQlQueryWithResponseHeaders($page100Query); + $this->assertEquals('HIT', $page100Hit['headers']['X-Magento-Cache-Debug']); + //updated page data should be correct + $this->assertNotEmpty($pageBlankMiss['body']); + $pageData = $pageBlankMiss['body']['cmsPage']; + $this->assertArrayNotHasKey('errors', $pageBlankMiss['body']); + $this->assertEquals('Cms Page Design Blank', $pageData['title']); + $this->assertEquals($newPageContent, $pageData['content']); + } + + /** + * Get page query + * + * @param int $pageId + * @return string + */ + private function getPageQuery(int $pageId): string + { + $query = <<<QUERY +{ + cmsPage(id: $pageId) { + title + url_key + content + } +} +QUERY; + return $query; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/ProductInMultipleStoresCacheTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/ProductInMultipleStoresCacheTest.php new file mode 100644 index 0000000000000..cf4cebdfe8e44 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/ProductInMultipleStoresCacheTest.php @@ -0,0 +1,322 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\PageCache; + +use Magento\TestFramework\ObjectManager; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * This test considers FPC caching but it can't check the headers since we don't guarantee that test if in dev mode. + * + * @magentoAppIsolation enabled + */ +class ProductInMultipleStoresCacheTest extends GraphQlAbstract +{ + /** + * @inheritdoc + */ + protected function setUp() + { + /** @var \Magento\Store\Model\Store $store */ + $store = ObjectManager::getInstance()->get(\Magento\Store\Model\Store::class); + $storeCodeFromFixture = 'fixture_second_store'; + + /** @var \Magento\Config\Model\ResourceModel\Config $configResource */ + $configResource = ObjectManager::getInstance()->get(\Magento\Config\Model\ResourceModel\Config::class); + /** @var \Magento\Config\App\Config\Type\System $config */ + $config = ObjectManager::getInstance()->get(\Magento\Config\App\Config\Type\System::class); + + $configResource->saveConfig( + \Magento\Directory\Model\Currency::XML_PATH_CURRENCY_DEFAULT, + 'EUR', + \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITES, + $store->load($storeCodeFromFixture)->getWebsiteId() + ); + + // allow USD & EUR currency + $configResource->saveConfig( + \Magento\Directory\Model\Currency::XML_PATH_CURRENCY_ALLOW, + 'EUR,USD', + \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITES, + $store->load($storeCodeFromFixture)->getWebsiteId() + ); + + // allow USD & EUR currency + $configResource->saveConfig( + \Magento\Directory\Model\Currency::XML_PATH_CURRENCY_ALLOW, + 'EUR,USD' + ); + + // configuration cache clean is required to reload currency setting + $config->clean(); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + /** @var \Magento\Config\App\Config\Type\System $config */ + $config = ObjectManager::getInstance()->get(\Magento\Config\App\Config\Type\System::class); + /** @var \Magento\Config\Model\ResourceModel\Config $configResource */ + $configResource = ObjectManager::getInstance()->get(\Magento\Config\Model\ResourceModel\Config::class); + + // restore allow USD currency + $configResource->saveConfig( + \Magento\Directory\Model\Currency::XML_PATH_CURRENCY_ALLOW, + 'USD' + ); + + // configuration cache clean is required to reload currency setting + $config->clean(); + parent::tearDown(); + } + + /** + * Test a non existing or non existing currency + * + * @magentoApiDataFixture Magento/Store/_files/second_website_with_second_currency.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testProductFromSpecificAndDefaultStoreWithMultiCurrencyNonExisting() + { + $productSku = 'simple'; + + $query = <<<QUERY +{ + products(filter: {sku: {eq: "{$productSku}"}}) + { + items { + attribute_set_id + created_at + id + name + price { + minimalPrice { + amount { + value + currency + } + } + } + sku + type_id + updated_at + ... on PhysicalProductInterface { + weight + } + } + } +} +QUERY; + + //test non existing currency + $headerMap = ['Store' => 'default', 'Content-Currency' => 'someNonExistentCurrency']; + $this->expectExceptionMessage('GraphQL response contains errors: Please correct the target currency'); + $this->graphQlQuery($query, [], '', $headerMap); + } + + /** + * Test a non existing or non allowed currency + * + * @magentoApiDataFixture Magento/Store/_files/second_website_with_second_currency.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testProductFromSpecificAndDefaultStoreWithMultiCurrencyNotAllowed() + { + $productSku = 'simple'; + + $query = <<<QUERY +{ + products(filter: {sku: {eq: "{$productSku}"}}) + { + items { + attribute_set_id + created_at + id + name + price { + minimalPrice { + amount { + value + currency + } + } + } + sku + type_id + updated_at + ... on PhysicalProductInterface { + weight + } + } + } +} +QUERY; + + $storeCodeFromFixture = 'fixture_second_store'; + + //test not allowed existing currency + $headerMap = ['Store' => $storeCodeFromFixture, 'Content-Currency' => 'CAD']; + $this->expectExceptionMessage( + 'GraphQL response contains errors: Please correct the target currency' + ); + $this->graphQlQuery($query, [], '', $headerMap); + } + + /** + * Test a product from a custom and default store, with cache with repeating queries asserting different results. + * + * @magentoApiDataFixture Magento/Store/_files/second_website_with_second_currency.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testProductFromSpecificAndDefaultStoreWithMultiCurrency() + { + $productSku = 'simple'; + + $query = <<<QUERY +{ + products(filter: {sku: {eq: "{$productSku}"}}) + { + items { + attribute_set_id + created_at + id + name + price { + minimalPrice { + amount { + value + currency + } + } + } + sku + type_id + updated_at + ... on PhysicalProductInterface { + weight + } + } + } +} +QUERY; + + /** @var \Magento\Store\Model\Store $store */ + $store = ObjectManager::getInstance()->get(\Magento\Store\Model\Store::class); + $storeCodeFromFixture = 'fixture_second_store'; + $storeId = $store->load($storeCodeFromFixture)->getStoreId(); + + /** @var \Magento\Catalog\Model\Product $product */ + $product = ObjectManager::getInstance()->get(\Magento\Catalog\Model\Product::class); + $product->load($product->getIdBySku($productSku)); + + $website = ObjectManager::getInstance()->get(\Magento\Store\Model\Website::class); + /** @var $website \Magento\Store\Model\Website */ + $website->load('test', 'code'); + $product->setWebsiteIds([1, $website->getId()]); + + // change product name for custom store + $productNameInFixtureStore = 'Product\'s Name in Fixture Store'; + $product->setName($productNameInFixtureStore)->setStoreId($storeId)->save(); + + // test store header only, query is cached at this point in EUR + $headerMap = ['Store' => $storeCodeFromFixture]; + $response = $this->graphQlQuery($query, [], '', $headerMap); + $this->assertEquals( + $productNameInFixtureStore, + $response['products']['items'][0]['name'], + 'Product name in fixture store is invalid.' + ); + $this->assertEquals( + 'EUR', + $response['products']['items'][0]['price']['minimalPrice']['amount']['currency'], + 'Currency code EUR in fixture ' . $storeCodeFromFixture . ' is unexpected' + ); + + // test cached store + currency header in Euros + $headerMap = ['Store' => $storeCodeFromFixture, 'Content-Currency' => 'EUR']; + $response = $this->graphQlQuery($query, [], '', $headerMap); + $this->assertEquals( + $productNameInFixtureStore, + $response['products']['items'][0]['name'], + 'Product name in fixture ' . $storeCodeFromFixture . ' is invalid.' + ); + $this->assertEquals( + 'EUR', + $response['products']['items'][0]['price']['minimalPrice']['amount']['currency'], + 'Currency code EUR in fixture ' . $storeCodeFromFixture . ' is unexpected' + ); + + // test non cached store + currency header in USD + $headerMap = ['Store' => $storeCodeFromFixture, 'Content-Currency' => 'USD']; + $response = $this->graphQlQuery($query, [], '', $headerMap); + $this->assertEquals( + $productNameInFixtureStore, + $response['products']['items'][0]['name'], + 'Product name in fixture store is invalid.' + ); + $this->assertEquals( + 'USD', + $response['products']['items'][0]['price']['minimalPrice']['amount']['currency'], + 'Currency code USD in fixture ' . $storeCodeFromFixture . ' is unexpected' + ); + + // test non cached store + currency header in USD not cached + $headerMap = ['Store' => 'default', 'Content-Currency' => 'USD']; + $response = $this->graphQlQuery($query, [], '', $headerMap); + $this->assertEquals( + 'Simple Product', + $response['products']['items'][0]['name'], + 'Product name in fixture store is invalid.' + ); + $this->assertEquals( + 'USD', + $response['products']['items'][0]['price']['minimalPrice']['amount']['currency'], + 'Currency code USD in fixture store default is unexpected' + ); + + // test non cached store + currency header in USD not cached + $headerMap = ['Store' => 'default', 'Content-Currency' => 'EUR']; + $response = $this->graphQlQuery($query, [], '', $headerMap); + $this->assertEquals( + 'Simple Product', + $response['products']['items'][0]['name'], + 'Product name in fixture store is invalid.' + ); + $this->assertEquals( + 'EUR', + $response['products']['items'][0]['price']['minimalPrice']['amount']['currency'], + 'Currency code EUR in fixture store default is unexpected' + ); + + // test non cached store + currency header in USD cached + $headerMap = ['Store' => 'default']; + $response = $this->graphQlQuery($query, [], '', $headerMap); + $this->assertEquals( + 'Simple Product', + $response['products']['items'][0]['name'], + 'Product name in fixture store is invalid.' + ); + $this->assertEquals( + 'USD', + $response['products']['items'][0]['price']['minimalPrice']['amount']['currency'], + 'Currency code USD in fixture store default is unexpected' + ); + + // test cached response store + currency header with non existing currency, and no valid response, no cache + $headerMap = ['Store' => $storeCodeFromFixture, 'Content-Currency' => 'SOMECURRENCY']; + $this->expectExceptionMessage( + 'GraphQL response contains errors: Please correct the target currency' + ); + $this->graphQlQuery($query, [], '', $headerMap); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/Quote/Guest/CartCacheTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/Quote/Guest/CartCacheTest.php new file mode 100644 index 0000000000000..e09ee8bc969af --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/Quote/Guest/CartCacheTest.php @@ -0,0 +1,129 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\PageCache\Quote\Guest; + +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test cart queries are not cached + * + * @magentoApiDataFixture Magento/Catalog/_files/products.php + */ +class CartCacheTest extends GraphQlAbstract +{ + /** + * @inheritdoc + */ + protected function setUp() + { + $this->markTestSkipped( + 'This test will stay skipped until DEVOPS-4924 is resolved' + ); + } + + public function testCartIsNotCached() + { + $qty = 2; + $sku = 'simple'; + $cartId = $this->createEmptyCart(); + $this->addSimpleProductToCart($cartId, $qty, $sku); + + $getCartQuery = $this->getCartQuery($cartId); + $responseMiss = $this->graphQlQueryWithResponseHeaders($getCartQuery); + $this->assertArrayHasKey('cart', $responseMiss['body']); + $this->assertArrayHasKey('items', $responseMiss['body']['cart']); + $this->assertEquals('MISS', $responseMiss['headers']['X-Magento-Cache-Debug']); + + /** Cache debug header value is still a MISS for any subsequent request */ + $responseMissNext = $this->graphQlQueryWithResponseHeaders($getCartQuery); + $this->assertEquals('MISS', $responseMissNext['headers']['X-Magento-Cache-Debug']); + } + + /** + * Create a guest cart which generates a maskedQuoteId + * + * @return string + */ + private function createEmptyCart(): string + { + $query = + <<<QUERY + mutation + { + createEmptyCart + } +QUERY; + + $response = $this->graphQlMutation($query); + $maskedQuoteId = $response['createEmptyCart']; + return $maskedQuoteId; + } + + /** + * Add simple product to the cart using the maskedQuoteId + * + * @param string $maskedCartId + * @param int $qty + * @param string $sku + */ + private function addSimpleProductToCart(string $maskedCartId, int $qty, string $sku): void + { + $addProductToCartQuery = + <<<QUERY + mutation { + addSimpleProductsToCart( + input: { + cart_id: "{$maskedCartId}" + cartItems: [ + { + data: { + qty: $qty + sku: "$sku" + } + } + ] + } + ) { + cart { + items { + qty + product { + sku + } + } + } + } + } +QUERY; + $response = $this->graphQlMutation($addProductToCartQuery); + self::assertArrayHasKey('cart', $response['addSimpleProductsToCart']); + } + + /** + * Get cart query string + * + * @param string $maskedQuoteId + * @return string + */ + private function getCartQuery(string $maskedQuoteId): string + { + return <<<QUERY +{ + cart(cart_id: "{$maskedQuoteId}") { + items { + id + qty + product { + sku + } + } + } +} +QUERY; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php deleted file mode 100644 index d9ab8db62a195..0000000000000 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php +++ /dev/null @@ -1,163 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\GraphQl\Quote; - -use Magento\TestFramework\Helper\Bootstrap; -use Magento\TestFramework\TestCase\GraphQlAbstract; -use Magento\Quote\Model\QuoteFactory; -use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; -use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; - -class AddSimpleProductToCartTest extends GraphQlAbstract -{ - /** - * @var QuoteResource - */ - private $quoteResource; - - /** - * @var QuoteFactory - */ - private $quoteFactory; - - /** - * @var QuoteIdToMaskedQuoteIdInterface - */ - private $quoteIdToMaskedId; - - /** - * @inheritdoc - */ - protected function setUp() - { - $objectManager = Bootstrap::getObjectManager(); - $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quoteFactory = $objectManager->get(QuoteFactory::class); - $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); - } - - /** - * @magentoApiDataFixture Magento/Catalog/_files/products.php - * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php - */ - public function testAddSimpleProductToCart() - { - $sku = 'simple'; - $qty = 2; - $maskedQuoteId = $this->getMaskedQuoteId(); - - $query = $this->getAddSimpleProductQuery($maskedQuoteId, $sku, $qty); - $response = $this->graphQlQuery($query); - self::assertArrayHasKey('cart', $response['addSimpleProductsToCart']); - - self::assertEquals($qty, $response['addSimpleProductsToCart']['cart']['items'][0]['qty']); - self::assertEquals($sku, $response['addSimpleProductsToCart']['cart']['items'][0]['product']['sku']); - } - - /** - * @magentoApiDataFixture Magento/Catalog/_files/products.php - * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php - * @expectedException \Exception - * @expectedExceptionMessage Please enter a number greater than 0 in this field. - */ - public function testAddSimpleProductToCartWithNegativeQty() - { - $sku = 'simple'; - $qty = -2; - $maskedQuoteId = $this->getMaskedQuoteId(); - - $query = $this->getAddSimpleProductQuery($maskedQuoteId, $sku, $qty); - $this->graphQlQuery($query); - } - - /** - * @return string - */ - public function getMaskedQuoteId() : string - { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id'); - - return $this->quoteIdToMaskedId->execute((int)$quote->getId()); - } - - /** - * @param string $maskedQuoteId - * @param string $sku - * @param int $qty - * @return string - */ - public function getAddSimpleProductQuery(string $maskedQuoteId, string $sku, int $qty): string - { - return <<<QUERY -mutation { - addSimpleProductsToCart( - input: { - cart_id: "{$maskedQuoteId}" - cartItems: [ - { - data: { - qty: $qty - sku: "$sku" - } - } - ] - } - ) { - cart { - items { - qty - product { - sku - } - } - } - } -} -QUERY; - } - - /** - * @magentoApiDataFixture Magento/Catalog/_files/products.php - * @expectedException \Exception - * @expectedExceptionMessage Could not find a cart with ID "wrong_cart_hash" - */ - public function testAddProductWithWrongCartHash() - { - $sku = 'simple'; - $qty = 1; - - $maskedQuoteId = 'wrong_cart_hash'; - - $query = <<<QUERY -mutation { - addSimpleProductsToCart( - input: { - cart_id: "{$maskedQuoteId}" - cartItems: [ - { - data: { - qty: $qty - sku: "$sku" - } - } - ] - } - ) { - cart { - items { - qty - } - } - } -} -QUERY; - - $this->graphQlQuery($query); - } -} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductWithCustomOptionsToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductWithCustomOptionsToCartTest.php new file mode 100644 index 0000000000000..63546298304b0 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductWithCustomOptionsToCartTest.php @@ -0,0 +1,184 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote; + +use Magento\Catalog\Api\ProductCustomOptionRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Add simple product with custom options to cart testcases + */ +class AddSimpleProductWithCustomOptionsToCartTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @var ProductCustomOptionRepositoryInterface + */ + private $productCustomOptionsRepository; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->productCustomOptionsRepository = $objectManager->get(ProductCustomOptionRepositoryInterface::class); + } + + /** + * Test adding a simple product to the shopping cart with all supported + * customizable options assigned + * + * @magentoApiDataFixture Magento/Catalog/_files/product_simple_with_options.php + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + */ + public function testAddSimpleProductWithOptions() + { + $sku = 'simple'; + $quantity = 1; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); + + $customOptionsValues = $this->getCustomOptionsValuesForQuery($sku); + + /* Generate customizable options fragment for GraphQl request */ + $queryCustomizableOptions = preg_replace('/"([^"]+)"\s*:\s*/', '$1:', json_encode($customOptionsValues)); + + $query = <<<QUERY +mutation { + addSimpleProductsToCart( + input: { + cart_id: "{$maskedQuoteId}", + cartItems: [ + { + data: { + quantity: $quantity + sku: "$sku" + }, + customizable_options: $queryCustomizableOptions + } + ] + } + ) { + cart { + items { + ... on SimpleCartItem { + customizable_options { + label + values { + value + } + } + } + } + } + } +} +QUERY; + + $response = $this->graphQlMutation($query); + + self::assertArrayHasKey('items', $response['addSimpleProductsToCart']['cart']); + self::assertCount(1, $response['addSimpleProductsToCart']['cart']); + + $customizableOptionsOutput = $response['addSimpleProductsToCart']['cart']['items'][0]['customizable_options']; + $assignedOptionsCount = count($customOptionsValues); + for ($counter = 0; $counter < $assignedOptionsCount; $counter++) { + self::assertEquals( + $customOptionsValues[$counter]['value'], + $customizableOptionsOutput[$counter]['values'][0]['value'] + ); + } + } + + /** + * Test adding a simple product with empty values for required options + * + * @magentoApiDataFixture Magento/Catalog/_files/product_simple_with_options.php + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + */ + public function testAddSimpleProductWithNoRequiredOptionsSet() + { + $sku = 'simple'; + $quantity = 1; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); + + $query = <<<QUERY +mutation { + addSimpleProductsToCart( + input: { + cart_id: "{$maskedQuoteId}", + cartItems: [ + { + data: { + quantity: $quantity + sku: "$sku" + } + } + ] + } + ) { + cart { + items { + ... on SimpleCartItem { + customizable_options { + label + values { + value + } + } + } + } + } + } +} +QUERY; + + self::expectExceptionMessage( + 'The product\'s required option(s) weren\'t entered. Make sure the options are entered and try again.' + ); + + $this->graphQlMutation($query); + } + + /** + * Generate an array with test values for customizable options + * based on the option type + * + * @param string $sku + * @return array + */ + private function getCustomOptionsValuesForQuery(string $sku): array + { + $customOptions = $this->productCustomOptionsRepository->getList($sku); + $customOptionsValues = []; + + foreach ($customOptions as $customOption) { + $optionType = $customOption->getType(); + if ($optionType == 'field' || $optionType == 'area') { + $customOptionsValues[] = [ + 'id' => (int) $customOption->getOptionId(), + 'value' => 'test' + ]; + } elseif ($optionType == 'drop_down') { + $optionSelectValues = $customOption->getValues(); + $customOptionsValues[] = [ + 'id' => (int) $customOption->getOptionId(), + 'value' => reset($optionSelectValues)->getOptionTypeId() + ]; + } + } + + return $customOptionsValues; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddVirtualProductWithCustomOptionsToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddVirtualProductWithCustomOptionsToCartTest.php new file mode 100644 index 0000000000000..94ac11ad8e0b1 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddVirtualProductWithCustomOptionsToCartTest.php @@ -0,0 +1,184 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote; + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; +use Magento\Catalog\Api\ProductCustomOptionRepositoryInterface; + +/** + * Add virtual product with custom options to cart testcases + */ +class AddVirtualProductWithCustomOptionsToCartTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @var ProductCustomOptionRepositoryInterface + */ + private $productCustomOptionsRepository; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->productCustomOptionsRepository = $objectManager->get(ProductCustomOptionRepositoryInterface::class); + } + + /** + * Test adding a virtual product to the shopping cart with all supported + * customizable options assigned + * + * @magentoApiDataFixture Magento/Catalog/_files/product_virtual_with_options.php + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + */ + public function testAddVirtualProductWithOptions() + { + $sku = 'virtual'; + $quantity = 1; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); + + $customOptionsValues = $this->getCustomOptionsValuesForQuery($sku); + + /* Generate customizable options fragment for GraphQl request */ + $queryCustomizableOptions = preg_replace('/"([^"]+)"\s*:\s*/', '$1:', json_encode($customOptionsValues)); + + $query = <<<QUERY +mutation { + addVirtualProductsToCart( + input: { + cart_id: "{$maskedQuoteId}", + cartItems: [ + { + data: { + quantity: $quantity + sku: "$sku" + }, + customizable_options: $queryCustomizableOptions + } + ] + } + ) { + cart { + items { + ... on VirtualCartItem { + customizable_options { + label + values { + value + } + } + } + } + } + } +} +QUERY; + + $response = $this->graphQlMutation($query); + + self::assertArrayHasKey('items', $response['addVirtualProductsToCart']['cart']); + self::assertCount(1, $response['addVirtualProductsToCart']['cart']); + + $customizableOptionsOutput = $response['addVirtualProductsToCart']['cart']['items'][0]['customizable_options']; + $assignedOptionsCount = count($customOptionsValues); + for ($counter = 0; $counter < $assignedOptionsCount; $counter++) { + self::assertEquals( + $customOptionsValues[$counter]['value'], + $customizableOptionsOutput[$counter]['values'][0]['value'] + ); + } + } + + /** + * Test adding a virtual product with empty values for required options + * + * @magentoApiDataFixture Magento/Catalog/_files/product_virtual_with_options.php + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + */ + public function testAddVirtualProductWithNoRequiredOptionsSet() + { + $sku = 'virtual'; + $quantity = 1; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); + + $query = <<<QUERY +mutation { + addVirtualProductsToCart( + input: { + cart_id: "{$maskedQuoteId}", + cartItems: [ + { + data: { + quantity: $quantity + sku: "$sku" + } + } + ] + } + ) { + cart { + items { + ... on VirtualCartItem { + customizable_options { + label + values { + value + } + } + } + } + } + } +} +QUERY; + + self::expectExceptionMessage( + 'The product\'s required option(s) weren\'t entered. Make sure the options are entered and try again.' + ); + + $this->graphQlMutation($query); + } + + /** + * Generate an array with test values for customizable options + * based on the option type + * + * @param string $sku + * @return array + */ + private function getCustomOptionsValuesForQuery(string $sku): array + { + $customOptions = $this->productCustomOptionsRepository->getList($sku); + $customOptionsValues = []; + + foreach ($customOptions as $customOption) { + $optionType = $customOption->getType(); + if ($optionType == 'field' || $optionType == 'area') { + $customOptionsValues[] = [ + 'id' => (int) $customOption->getOptionId(), + 'value' => 'test' + ]; + } elseif ($optionType == 'drop_down') { + $optionSelectValues = $customOption->getValues(); + $customOptionsValues[] = [ + 'id' => (int) $customOption->getOptionId(), + 'value' => reset($optionSelectValues)->getOptionTypeId() + ]; + } + } + + return $customOptionsValues; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/AddSimpleProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/AddSimpleProductToCartTest.php new file mode 100644 index 0000000000000..be22d860df122 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/AddSimpleProductToCartTest.php @@ -0,0 +1,179 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Customer; + +use Magento\Framework\Exception\AuthenticationException; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test adding simple product to Cart + */ +class AddSimpleProductToCartTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + */ + public function testAddSimpleProductToCart() + { + $sku = 'simple_product'; + $quantity = 2; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('cart', $response['addSimpleProductsToCart']); + self::assertEquals($quantity, $response['addSimpleProductsToCart']['cart']['items'][0]['quantity']); + self::assertEquals($sku, $response['addSimpleProductsToCart']['cart']['items'][0]['product']['sku']); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * + * @expectedException \Exception + * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" + */ + public function testAddProductToNonExistentCart() + { + $sku = 'simple_product'; + $quantity = 2; + $maskedQuoteId = 'non_existent_masked_id'; + + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * + * @expectedException \Exception + * @expectedExceptionMessage Could not find a product with SKU "simple_product" + */ + public function testNonExistentProductToCart() + { + $sku = 'simple_product'; + $qty = 2; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId, $sku, $qty); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + */ + public function testAddSimpleProductToGuestCart() + { + $sku = 'simple_product'; + $qty = 2; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $sku, $qty); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$maskedQuoteId\"" + ); + + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/three_customers.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + */ + public function testAddSimpleProductToAnotherCustomerCart() + { + $sku = 'simple_product'; + $qty = 2; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $sku, $qty); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$maskedQuoteId\"" + ); + + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer2@search.example.com')); + } + + /** + * @param string $maskedQuoteId + * @param string $sku + * @param float $quantity + * @return string + */ + private function getQuery(string $maskedQuoteId, string $sku, float $quantity): string + { + return <<<QUERY +mutation { + addSimpleProductsToCart(input: { + cart_id: "{$maskedQuoteId}", + cartItems: [ + { + data: { + quantity: {$quantity} + sku: "{$sku}" + } + } + ] + }) { + cart { + items { + id + quantity + product { + sku + } + } + } + } +} +QUERY; + } + + /** + * Retrieve customer authorization headers + * + * @param string $username + * @param string $password + * @return array + * @throws AuthenticationException + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + return $headerMap; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/AddVirtualProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/AddVirtualProductToCartTest.php new file mode 100644 index 0000000000000..1269b9d8e7b3d --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/AddVirtualProductToCartTest.php @@ -0,0 +1,179 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Customer; + +use Magento\Framework\Exception\AuthenticationException; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test adding virtual product to Cart + */ +class AddVirtualProductToCartTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/virtual_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + */ + public function testAddVirtualProductToCart() + { + $sku = 'virtual_product'; + $quantity = 2; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('cart', $response['addVirtualProductsToCart']); + self::assertEquals($quantity, $response['addVirtualProductsToCart']['cart']['items'][0]['quantity']); + self::assertEquals($sku, $response['addVirtualProductsToCart']['cart']['items'][0]['product']['sku']); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/virtual_product.php + * + * @expectedException \Exception + * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" + */ + public function testAddVirtualToNonExistentCart() + { + $sku = 'virtual_product'; + $qty = 2; + $nonExistentMaskedQuoteId = 'non_existent_masked_id'; + + $query = $this->getQuery($nonExistentMaskedQuoteId, $sku, $qty); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * + * @expectedException \Exception + * @expectedExceptionMessage Could not find a product with SKU "virtual_product" + */ + public function testNonExistentProductToCart() + { + $sku = 'virtual_product'; + $qty = 2; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId, $sku, $qty); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/virtual_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + */ + public function testAddVirtualProductToGuestCart() + { + $sku = 'virtual_product'; + $qty = 2; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $sku, $qty); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$maskedQuoteId\"" + ); + + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/three_customers.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/virtual_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + */ + public function testAddVirtualProductToAnotherCustomerCart() + { + $sku = 'virtual_product'; + $qty = 2; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $sku, $qty); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$maskedQuoteId\"" + ); + + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer2@search.example.com')); + } + + /** + * @param string $maskedQuoteId + * @param string $sku + * @param float $quantity + * @return string + */ + private function getQuery(string $maskedQuoteId, string $sku, float $quantity): string + { + return <<<QUERY +mutation { + addVirtualProductsToCart(input: { + cart_id: "{$maskedQuoteId}", + cartItems: [ + { + data: { + quantity: {$quantity} + sku: "{$sku}" + } + } + ] + }) { + cart { + items { + id + quantity + product { + sku + } + } + } + } +} +QUERY; + } + + /** + * Retrieve customer authorization headers + * + * @param string $username + * @param string $password + * @return array + * @throws AuthenticationException + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + return $headerMap; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/ApplyCouponToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/ApplyCouponToCartTest.php index f0d9400b1866b..5a2221a184294 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/ApplyCouponToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/ApplyCouponToCartTest.php @@ -47,7 +47,7 @@ public function testApplyCouponToCart() $couponCode = '2?ds5!2d'; $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $couponCode); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('applyCouponToCart', $response); self::assertEquals($couponCode, $response['applyCouponToCart']['cart']['applied_coupon']['code']); @@ -67,12 +67,12 @@ public function testApplyCouponTwice() $couponCode = '2?ds5!2d'; $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $couponCode); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey("applyCouponToCart", $response); self::assertEquals($couponCode, $response['applyCouponToCart']['cart']['applied_coupon']['code']); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -89,7 +89,7 @@ public function testApplyCouponToCartWithoutItems() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $couponCode); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -106,7 +106,7 @@ public function testApplyCouponToGuestCart() $query = $this->getQuery($maskedQuoteId, $couponCode); self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -125,7 +125,7 @@ public function testApplyCouponToAnotherCustomerCart() $query = $this->getQuery($maskedQuoteId, $couponCode); self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); - $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer_two@example.com')); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer_two@example.com')); } /** @@ -142,7 +142,7 @@ public function testApplyNonExistentCouponToCart() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $couponCode); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -159,7 +159,7 @@ public function testApplyCouponToNonExistentCart() $query = $this->getQuery($maskedQuoteId, $couponCode); self::expectExceptionMessage('Could not find a cart with ID "' . $maskedQuoteId . '"'); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -179,7 +179,7 @@ public function testApplyExpiredCoupon() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $couponCode); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -200,7 +200,7 @@ public function testApplyCouponWhichIsNotApplicable() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $couponCode); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -226,7 +226,7 @@ public function testApplyCouponWithMissedRequiredParameters(string $input, strin QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CartTotalsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CartTotalsTest.php new file mode 100644 index 0000000000000..bb8acfce629ff --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CartTotalsTest.php @@ -0,0 +1,208 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Customer; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test getting cart totals for registered customer + */ +class CartTotalsTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Tax/_files/tax_rule_for_region_1.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/apply_tax_for_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + */ + public function testGetCartTotalsWithTaxApplied() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('prices', $response['cart']); + $pricesResponse = $response['cart']['prices']; + self::assertEquals(21.5, $pricesResponse['grand_total']['value']); + self::assertEquals(21.5, $pricesResponse['subtotal_including_tax']['value']); + self::assertEquals(20, $pricesResponse['subtotal_excluding_tax']['value']); + self::assertEquals(20, $pricesResponse['subtotal_with_discount_excluding_tax']['value']); + + $appliedTaxesResponse = $pricesResponse['applied_taxes']; + + self::assertEquals('US-TEST-*-Rate-1', $appliedTaxesResponse[0]['label']); + self::assertEquals(1.5, $appliedTaxesResponse[0]['amount']['value']); + self::assertEquals('USD', $appliedTaxesResponse[0]['amount']['currency']); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + */ + public function testGetTotalsWithNoTaxApplied() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + + $pricesResponse = $response['cart']['prices']; + self::assertEquals(20, $pricesResponse['grand_total']['value']); + self::assertEquals(20, $pricesResponse['subtotal_including_tax']['value']); + self::assertEquals(20, $pricesResponse['subtotal_excluding_tax']['value']); + self::assertEquals(20, $pricesResponse['subtotal_with_discount_excluding_tax']['value']); + self::assertEmpty($pricesResponse['applied_taxes']); + } + + /** + * The totals calculation is based on quote address. + * But the totals should be calculated even if no address is set + * + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testGetCartTotalsWithNoAddressSet() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + + $pricesResponse = $response['cart']['prices']; + self::assertEquals(20, $pricesResponse['grand_total']['value']); + self::assertEquals(20, $pricesResponse['subtotal_including_tax']['value']); + self::assertEquals(20, $pricesResponse['subtotal_excluding_tax']['value']); + self::assertEquals(20, $pricesResponse['subtotal_with_discount_excluding_tax']['value']); + self::assertEmpty($pricesResponse['applied_taxes']); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Tax/_files/tax_rule_for_region_1.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/apply_tax_for_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + */ + public function testGetTotalsFromGuestCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$maskedQuoteId\"" + ); + $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/three_customers.php + * @magentoApiDataFixture Magento/GraphQl/Tax/_files/tax_rule_for_region_1.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/apply_tax_for_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + */ + public function testGetTotalsFromAnotherCustomerCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$maskedQuoteId\"" + ); + $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer3@search.example.com')); + } + + /** + * Generates GraphQl query for retrieving cart totals + * + * @param string $maskedQuoteId + * @return string + */ + private function getQuery(string $maskedQuoteId): string + { + return <<<QUERY +{ + cart(cart_id: "$maskedQuoteId") { + prices { + grand_total { + value, + currency + } + subtotal_including_tax { + value + currency + } + subtotal_excluding_tax { + value + currency + } + subtotal_with_discount_excluding_tax { + value + currency + } + applied_taxes { + label + amount { + value + currency + } + } + } + } +} +QUERY; + } + + /** + * @param string $username + * @param string $password + * @return array + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + return $headerMap; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CheckoutEndToEndTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CheckoutEndToEndTest.php new file mode 100644 index 0000000000000..92974f2491f75 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CheckoutEndToEndTest.php @@ -0,0 +1,524 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Customer; + +use Magento\Quote\Model\QuoteIdMaskFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Framework\Registry; +use Magento\Quote\Model\ResourceModel\Quote\CollectionFactory as QuoteCollectionFactory; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\ResourceModel\Order\CollectionFactory; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * End to checkout tests for customer + */ +class CheckoutEndToEndTest extends GraphQlAbstract +{ + /** + * @var Registry + */ + private $registry; + + /** + * @var QuoteCollectionFactory + */ + private $quoteCollectionFactory; + + /** + * @var QuoteResource + */ + private $quoteResource; + + /** + * @var QuoteIdMaskFactory + */ + private $quoteIdMaskFactory; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @var CollectionFactory + */ + private $orderCollectionFactory; + + /** + * @var OrderRepositoryInterface + */ + private $orderRepository; + + /** + * @var array + */ + private $headers = []; + + protected function setUp() + { + parent::setUp(); + + $objectManager = Bootstrap::getObjectManager(); + $this->registry = $objectManager->get(Registry::class); + $this->quoteCollectionFactory = $objectManager->get(QuoteCollectionFactory::class); + $this->quoteResource = $objectManager->get(QuoteResource::class); + $this->quoteIdMaskFactory = $objectManager->get(QuoteIdMaskFactory::class); + $this->customerRepository = Bootstrap::getObjectManager()->get(CustomerRepositoryInterface::class); + $this->orderCollectionFactory = $objectManager->get(CollectionFactory::class); + $this->orderRepository = $objectManager->get(OrderRepositoryInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/products_with_layered_navigation_attribute.php + */ + public function testCheckoutWorkflow() + { + $quantity = 2; + + $this->createCustomer(); + $token = $this->loginCustomer(); + $this->headers = ['Authorization' => 'Bearer ' . $token]; + + $sku = $this->findProduct(); + $cartId = $this->createEmptyCart(); + $this->addProductToCart($cartId, $quantity, $sku); + + $this->setBillingAddress($cartId); + $shippingMethod = $this->setShippingAddress($cartId); + + $paymentMethod = $this->setShippingMethod($cartId, $shippingMethod); + $this->setPaymentMethod($cartId, $paymentMethod); + + $orderId = $this->placeOrder($cartId); + $this->checkOrderInHistory($orderId); + } + + /** + * @return void + */ + private function createCustomer(): void + { + $query = <<<QUERY +mutation { + createCustomer( + input: { + firstname: "endto" + lastname: "endtester" + email: "customer@example.com" + password: "123123Qa" + } + ) { + customer { + id + } + } +} +QUERY; + $this->graphQlMutation($query); + } + + /** + * @return string + */ + private function loginCustomer(): string + { + $query = <<<QUERY +mutation { + generateCustomerToken( + email: "customer@example.com" + password: "123123Qa" + ) { + token + } +} +QUERY; + $response = $this->graphQlMutation($query); + self::assertArrayHasKey('generateCustomerToken', $response); + self::assertArrayHasKey('token', $response['generateCustomerToken']); + self::assertNotEmpty($response['generateCustomerToken']['token']); + + return $response['generateCustomerToken']['token']; + } + + /** + * @return string + */ + private function findProduct(): string + { + $query = <<<QUERY +{ + products ( + filter: { + sku: { + like:"simple%" + } + } + pageSize: 1 + currentPage: 1 + ) { + items { + sku + } + } +} +QUERY; + $response = $this->graphQlQuery($query); + self::assertArrayHasKey('products', $response); + self::assertArrayHasKey('items', $response['products']); + self::assertCount(1, $response['products']['items']); + + $product = current($response['products']['items']); + self::assertArrayHasKey('sku', $product); + self::assertNotEmpty($product['sku']); + + return $product['sku']; + } + + /** + * @return string + */ + private function createEmptyCart(): string + { + $query = <<<QUERY +mutation { + createEmptyCart +} +QUERY; + $response = $this->graphQlMutation($query, [], '', $this->headers); + self::assertArrayHasKey('createEmptyCart', $response); + self::assertNotEmpty($response['createEmptyCart']); + + return $response['createEmptyCart']; + } + + /** + * @param string $cartId + * @param float $qty + * @param string $sku + * @return void + */ + private function addProductToCart(string $cartId, float $qty, string $sku): void + { + $query = <<<QUERY +mutation { + addSimpleProductsToCart( + input: { + cart_id: "{$cartId}" + cartItems: [ + { + data: { + quantity: {$qty} + sku: "{$sku}" + } + } + ] + } + ) { + cart { + items { + quantity + product { + sku + } + } + } + } +} +QUERY; + $this->graphQlMutation($query, [], '', $this->headers); + } + + /** + * @param string $cartId + * @param array $auth + * @return array + */ + private function setBillingAddress(string $cartId): void + { + $query = <<<QUERY +mutation { + setBillingAddressOnCart( + input: { + cart_id: "{$cartId}" + billing_address: { + address: { + firstname: "test firstname" + lastname: "test lastname" + company: "test company" + street: ["test street 1", "test street 2"] + city: "test city" + postcode: "887766" + telephone: "88776655" + region: "TX" + country_code: "US" + save_in_address_book: false + } + } + } + ) { + cart { + billing_address { + __typename + } + } + } +} +QUERY; + $this->graphQlMutation($query, [], '', $this->headers); + } + + /** + * @param string $cartId + * @return array + */ + private function setShippingAddress(string $cartId): array + { + $query = <<<QUERY +mutation { + setShippingAddressesOnCart( + input: { + cart_id: "$cartId" + shipping_addresses: [ + { + address: { + firstname: "test firstname" + lastname: "test lastname" + company: "test company" + street: ["test street 1", "test street 2"] + city: "test city" + region: "TX" + postcode: "887766" + country_code: "US" + telephone: "88776655" + save_in_address_book: false + } + } + ] + } + ) { + cart { + shipping_addresses { + available_shipping_methods { + carrier_code + method_code + amount + } + } + } + } +} +QUERY; + $response = $this->graphQlMutation($query, [], '', $this->headers); + self::assertArrayHasKey('setShippingAddressesOnCart', $response); + self::assertArrayHasKey('cart', $response['setShippingAddressesOnCart']); + self::assertArrayHasKey('shipping_addresses', $response['setShippingAddressesOnCart']['cart']); + self::assertCount(1, $response['setShippingAddressesOnCart']['cart']['shipping_addresses']); + + $shippingAddress = current($response['setShippingAddressesOnCart']['cart']['shipping_addresses']); + self::assertArrayHasKey('available_shipping_methods', $shippingAddress); + self::assertCount(1, $shippingAddress['available_shipping_methods']); + + $availableShippingMethod = current($shippingAddress['available_shipping_methods']); + self::assertArrayHasKey('carrier_code', $availableShippingMethod); + self::assertNotEmpty($availableShippingMethod['carrier_code']); + + self::assertArrayHasKey('method_code', $availableShippingMethod); + self::assertNotEmpty($availableShippingMethod['method_code']); + + self::assertArrayHasKey('amount', $availableShippingMethod); + self::assertNotEmpty($availableShippingMethod['amount']); + + return $availableShippingMethod; + } + + /** + * @param string $cartId + * @param array $method + * @return array + */ + private function setShippingMethod(string $cartId, array $method): array + { + $query = <<<QUERY +mutation { + setShippingMethodsOnCart(input: { + cart_id: "{$cartId}", + shipping_methods: [ + { + carrier_code: "{$method['carrier_code']}" + method_code: "{$method['method_code']}" + } + ] + }) { + cart { + available_payment_methods { + code + title + } + } + } +} +QUERY; + $response = $this->graphQlMutation($query, [], '', $this->headers); + self::assertArrayHasKey('setShippingMethodsOnCart', $response); + self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); + self::assertArrayHasKey('available_payment_methods', $response['setShippingMethodsOnCart']['cart']); + self::assertCount(1, $response['setShippingMethodsOnCart']['cart']['available_payment_methods']); + + $availablePaymentMethod = current($response['setShippingMethodsOnCart']['cart']['available_payment_methods']); + self::assertArrayHasKey('code', $availablePaymentMethod); + self::assertNotEmpty($availablePaymentMethod['code']); + self::assertArrayHasKey('title', $availablePaymentMethod); + self::assertNotEmpty($availablePaymentMethod['title']); + + return $availablePaymentMethod; + } + + /** + * @param string $cartId + * @param array $method + * @return void + */ + private function setPaymentMethod(string $cartId, array $method): void + { + $query = <<<QUERY +mutation { + setPaymentMethodOnCart( + input: { + cart_id: "{$cartId}" + payment_method: { + code: "{$method['code']}" + } + } + ) { + cart { + selected_payment_method { + code + } + } + } +} +QUERY; + $this->graphQlMutation($query, [], '', $this->headers); + } + + /** + * @param string $cartId + * @return string + */ + private function placeOrder(string $cartId): string + { + $query = <<<QUERY +mutation { + placeOrder( + input: { + cart_id: "{$cartId}" + } + ) { + order { + order_id + } + } +} +QUERY; + $response = $this->graphQlMutation($query, [], '', $this->headers); + self::assertArrayHasKey('placeOrder', $response); + self::assertArrayHasKey('order', $response['placeOrder']); + self::assertArrayHasKey('order_id', $response['placeOrder']['order']); + self::assertNotEmpty($response['placeOrder']['order']['order_id']); + + return $response['placeOrder']['order']['order_id']; + } + + /** + * @param string $orderId + * @return void + */ + private function checkOrderInHistory(string $orderId): void + { + $query = <<<QUERY +{ + customerOrders { + items { + increment_id + grand_total + } + } +} +QUERY; + $response = $this->graphQlQuery($query, [], '', $this->headers); + self::assertArrayHasKey('customerOrders', $response); + self::assertArrayHasKey('items', $response['customerOrders']); + self::assertCount(1, $response['customerOrders']['items']); + + $order = current($response['customerOrders']['items']); + self::assertArrayHasKey('increment_id', $order); + self::assertEquals($orderId, $order['increment_id']); + + self::assertArrayHasKey('grand_total', $order); + } + + public function tearDown() + { + $this->deleteCustomer(); + $this->deleteQuote(); + $this->deleteOrder(); + parent::tearDown(); + } + + /** + * @return void + */ + private function deleteCustomer(): void + { + $email = 'customer@example.com'; + try { + $customer = $this->customerRepository->get($email); + } catch (\Exception $exception) { + return; + } + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', true); + $this->customerRepository->delete($customer); + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', false); + } + + /** + * @return void + */ + private function deleteQuote(): void + { + $quoteCollection = $this->quoteCollectionFactory->create(); + foreach ($quoteCollection as $quote) { + $this->quoteResource->delete($quote); + + $quoteIdMask = $this->quoteIdMaskFactory->create(); + $quoteIdMask->setQuoteId($quote->getId()) + ->delete(); + } + } + + /** + * @return void + */ + private function deleteOrder() + { + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', true); + + $orderCollection = $this->orderCollectionFactory->create(); + foreach ($orderCollection as $order) { + $this->orderRepository->delete($order); + } + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', false); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CreateEmptyCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CreateEmptyCartTest.php index 86b0d3010c0e3..fbd0cf19740d7 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CreateEmptyCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CreateEmptyCartTest.php @@ -8,9 +8,8 @@ namespace Magento\GraphQl\Quote\Customer; use Magento\Integration\Api\CustomerTokenServiceInterface; -use Magento\Quote\Model\QuoteFactory; -use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface; use Magento\Quote\Model\QuoteIdMaskFactory; +use Magento\Quote\Model\ResourceModel\Quote\CollectionFactory as QuoteCollectionFactory; use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -32,38 +31,27 @@ class CreateEmptyCartTest extends GraphQlAbstract private $customerTokenService; /** - * @var QuoteResource - */ - private $quoteResource; - - /** - * @var QuoteFactory + * @var QuoteCollectionFactory */ - private $quoteFactory; + private $quoteCollectionFactory; /** - * @var MaskedQuoteIdToQuoteIdInterface + * @var QuoteResource */ - private $maskedQuoteIdToQuoteId; + private $quoteResource; /** * @var QuoteIdMaskFactory */ private $quoteIdMaskFactory; - /** - * @var string - */ - private $maskedQuoteId; - protected function setUp() { $objectManager = Bootstrap::getObjectManager(); + $this->quoteCollectionFactory = $objectManager->get(QuoteCollectionFactory::class); $this->guestCartRepository = $objectManager->get(GuestCartRepositoryInterface::class); $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quoteFactory = $objectManager->get(QuoteFactory::class); - $this->maskedQuoteIdToQuoteId = $objectManager->get(MaskedQuoteIdToQuoteIdInterface::class); $this->quoteIdMaskFactory = $objectManager->get(QuoteIdMaskFactory::class); } @@ -73,13 +61,12 @@ protected function setUp() public function testCreateEmptyCart() { $query = $this->getQuery(); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMapWithCustomerToken()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMapWithCustomerToken()); self::assertArrayHasKey('createEmptyCart', $response); self::assertNotEmpty($response['createEmptyCart']); $guestCart = $this->guestCartRepository->get($response['createEmptyCart']); - $this->maskedQuoteId = $response['createEmptyCart']; self::assertNotNull($guestCart->getId()); self::assertEquals(1, $guestCart->getCustomer()->getId()); @@ -96,20 +83,78 @@ public function testCreateEmptyCartWithNotDefaultStore() $headerMap = $this->getHeaderMapWithCustomerToken(); $headerMap['Store'] = 'fixture_second_store'; - $response = $this->graphQlQuery($query, [], '', $headerMap); + $response = $this->graphQlMutation($query, [], '', $headerMap); self::assertArrayHasKey('createEmptyCart', $response); self::assertNotEmpty($response['createEmptyCart']); /* guestCartRepository is used for registered customer to get the cart hash */ $guestCart = $this->guestCartRepository->get($response['createEmptyCart']); - $this->maskedQuoteId = $response['createEmptyCart']; self::assertNotNull($guestCart->getId()); self::assertEquals(1, $guestCart->getCustomer()->getId()); self::assertEquals('fixture_second_store', $guestCart->getStore()->getCode()); } + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testCreateEmptyCartWithPredefinedCartId() + { + $predefinedCartId = '572cda51902b5b517c0e1a2b2fd004b4'; + + $query = <<<QUERY +mutation { + createEmptyCart (input: {cart_id: "{$predefinedCartId}"}) +} +QUERY; + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMapWithCustomerToken()); + + self::assertArrayHasKey('createEmptyCart', $response); + self::assertEquals($predefinedCartId, $response['createEmptyCart']); + + $guestCart = $this->guestCartRepository->get($response['createEmptyCart']); + self::assertNotNull($guestCart->getId()); + self::assertEquals(1, $guestCart->getCustomer()->getId()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * + * @expectedException \Exception + * @expectedExceptionMessage Cart with ID "572cda51902b5b517c0e1a2b2fd004b4" already exists. + */ + public function testCreateEmptyCartIfPredefinedCartIdAlreadyExists() + { + $predefinedCartId = '572cda51902b5b517c0e1a2b2fd004b4'; + + $query = <<<QUERY +mutation { + createEmptyCart (input: {cart_id: "{$predefinedCartId}"}) +} +QUERY; + $this->graphQlMutation($query, [], '', $this->getHeaderMapWithCustomerToken()); + $this->graphQlMutation($query, [], '', $this->getHeaderMapWithCustomerToken()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * + * @expectedException \Exception + * @expectedExceptionMessage Cart ID length should to be 32 symbols. + */ + public function testCreateEmptyCartWithWrongPredefinedCartId() + { + $predefinedCartId = '572'; + + $query = <<<QUERY +mutation { + createEmptyCart (input: {cart_id: "{$predefinedCartId}"}) +} +QUERY; + $this->graphQlMutation($query, [], '', $this->getHeaderMapWithCustomerToken()); + } + /** * @return string */ @@ -138,15 +183,12 @@ private function getHeaderMapWithCustomerToken( public function tearDown() { - if (null !== $this->maskedQuoteId) { - $quoteId = $this->maskedQuoteIdToQuoteId->execute($this->maskedQuoteId); - - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $quoteId); + $quoteCollection = $this->quoteCollectionFactory->create(); + foreach ($quoteCollection as $quote) { $this->quoteResource->delete($quote); $quoteIdMask = $this->quoteIdMaskFactory->create(); - $quoteIdMask->setQuoteId($quoteId) + $quoteIdMask->setQuoteId($quote->getId()) ->delete(); } parent::tearDown(); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartEmailTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartEmailTest.php new file mode 100644 index 0000000000000..951fe08db5e3d --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartEmailTest.php @@ -0,0 +1,125 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Customer; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for getting email from cart + */ +class GetCartEmailTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + */ + public function testGetCartEmail() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + + $this->assertArrayHasKey('cart', $response); + $this->assertArrayHasKey('email', $response['cart']); + $this->assertEquals('customer@example.com', $response['cart']['email']); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" + */ + public function testGetCartEmailFromNonExistentCart() + { + $maskedQuoteId = 'non_existent_masked_id'; + $query = $this->getQuery($maskedQuoteId); + + $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/set_guest_email.php + */ + public function testGetEmailFromGuestCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"{$maskedQuoteId}\"" + ); + $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/three_customers.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + */ + public function testGetEmailFromAnotherCustomerCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"{$maskedQuoteId}\"" + ); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer3@search.example.com')); + } + + /** + * @param string $maskedQuoteId + * @return string + */ + private function getQuery(string $maskedQuoteId): string + { + return <<<QUERY +{ + cart(cart_id:"$maskedQuoteId") { + email + } +} +QUERY; + } + + /** + * @param string $username + * @param string $password + * @return array + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + return $headerMap; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php index 19b72b9e3ca4c..99e1c0bbd1579 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php @@ -54,11 +54,11 @@ public function testGetCart() self::assertCount(2, $response['cart']['items']); self::assertNotEmpty($response['cart']['items'][0]['id']); - self::assertEquals(2, $response['cart']['items'][0]['qty']); + self::assertEquals(2, $response['cart']['items'][0]['quantity']); self::assertEquals('simple_product', $response['cart']['items'][0]['product']['sku']); self::assertNotEmpty($response['cart']['items'][1]['id']); - self::assertEquals(2, $response['cart']['items'][1]['qty']); + self::assertEquals(2, $response['cart']['items'][1]['quantity']); self::assertEquals('virtual-product', $response['cart']['items'][1]['product']['sku']); } @@ -163,7 +163,7 @@ public function testGetCartWithWrongStore() * @magentoApiDataFixture Magento/Checkout/_files/active_quote_customer_not_default_store.php * * @expectedException \Exception - * @expectedExceptionMessage Store code not_existing_store does not exist + * @expectedExceptionMessage Requested store is not found */ public function testGetCartWithNotExistingStore() { @@ -187,7 +187,7 @@ private function getQuery(string $maskedQuoteId): string cart(cart_id: "{$maskedQuoteId}") { items { id - qty + quantity product { sku } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php index ba169d7a5bbc9..5575830ea51cd 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php @@ -64,6 +64,28 @@ public function testGetSelectedShippingMethod() self::assertArrayHasKey('method_code', $shippingAddress['selected_shipping_method']); self::assertEquals('flatrate', $shippingAddress['selected_shipping_method']['method_code']); + + self::assertArrayHasKey('carrier_title', $shippingAddress['selected_shipping_method']); + self::assertEquals('Flat Rate', $shippingAddress['selected_shipping_method']['carrier_title']); + + self::assertArrayHasKey('method_title', $shippingAddress['selected_shipping_method']); + self::assertEquals('Fixed', $shippingAddress['selected_shipping_method']['method_title']); + + self::assertArrayHasKey('amount', $shippingAddress['selected_shipping_method']); + $amount = $shippingAddress['selected_shipping_method']['amount']; + + self::assertArrayHasKey('value', $amount); + self::assertEquals(10, $amount['value']); + self::assertArrayHasKey('currency', $amount); + self::assertEquals('USD', $amount['currency']); + + self::assertArrayHasKey('base_amount', $shippingAddress['selected_shipping_method']); + $baseAmount = $shippingAddress['selected_shipping_method']['base_amount']; + + self::assertArrayHasKey('value', $baseAmount); + self::assertEquals(10, $baseAmount['value']); + self::assertArrayHasKey('currency', $baseAmount); + self::assertEquals('USD', $baseAmount['currency']); } /** @@ -129,8 +151,10 @@ public function testGetGetSelectedShippingMethodIfShippingMethodIsNotSet() self::assertNull($shippingAddress['selected_shipping_method']['carrier_code']); self::assertNull($shippingAddress['selected_shipping_method']['method_code']); - self::assertNull($shippingAddress['selected_shipping_method']['label']); + self::assertNull($shippingAddress['selected_shipping_method']['carrier_title']); + self::assertNull($shippingAddress['selected_shipping_method']['method_title']); self::assertNull($shippingAddress['selected_shipping_method']['amount']); + self::assertNull($shippingAddress['selected_shipping_method']['base_amount']); } /** @@ -172,8 +196,16 @@ private function getQuery(string $maskedQuoteId): string selected_shipping_method { carrier_code method_code - label - amount + carrier_title + method_title + amount { + value + currency + } + base_amount { + value + currency + } } } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedBillingAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedBillingAddressTest.php index 1ba94346073db..1ff5ddbde54ec 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedBillingAddressTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedBillingAddressTest.php @@ -71,7 +71,7 @@ public function testGeSpecifiedBillingAddress() 'label' => 'US', ], 'telephone' => '3468676', - 'address_type' => 'BILLING', + '__typename' => 'BillingCartAddress', 'customer_notes' => null, ]; self::assertEquals($expectedBillingAddressData, $response['cart']['billing_address']); @@ -110,7 +110,7 @@ public function testGeSpecifiedBillingAddressIfBillingAddressIsNotSet() 'label' => null, ], 'telephone' => null, - 'address_type' => 'BILLING', + '__typename' => 'BillingCartAddress', 'customer_notes' => null, ]; self::assertEquals($expectedBillingAddressData, $response['cart']['billing_address']); @@ -197,7 +197,7 @@ private function getQuery(string $maskedQuoteId): string label } telephone - address_type + __typename customer_notes } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/PlaceOrderTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/PlaceOrderTest.php index 4220f8932caa1..47d0d661fb33c 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/PlaceOrderTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/PlaceOrderTest.php @@ -7,7 +7,6 @@ namespace Magento\GraphQl\Quote\Customer; -use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Registry; use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\Integration\Api\CustomerTokenServiceInterface; @@ -77,7 +76,7 @@ public function testPlaceOrder() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); $query = $this->getQuery($maskedQuoteId); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('placeOrder', $response); self::assertArrayHasKey('order_id', $response['placeOrder']['order']); @@ -98,7 +97,7 @@ public function testPlaceOrderWithNoItemsInCart() 'Unable to place order: A server error stopped your order from being placed. ' . 'Please try to place your order again' ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -116,7 +115,7 @@ public function testPlaceOrderWithNoShippingAddress() self::expectExceptionMessage( 'Unable to place order: Some addresses can\'t be used due to the configurations for specific countries' ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -135,7 +134,7 @@ public function testPlaceOrderWithNoShippingMethod() self::expectExceptionMessage( 'Unable to place order: The shipping method is missing. Select the shipping method and try again' ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -156,7 +155,7 @@ public function testPlaceOrderWithNoBillingAddress() self::expectExceptionMessageRegExp( '/Unable to place order: Please check the billing address information*/' ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -176,7 +175,7 @@ public function testPlaceOrderWithNoPaymentMethod() $query = $this->getQuery($maskedQuoteId); self::expectExceptionMessage('Unable to place order: Enter a valid payment method and try again'); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -197,7 +196,7 @@ public function testPlaceOrderWithOutOfStockProduct() $query = $this->getQuery($maskedQuoteId); self::expectExceptionMessage('Unable to place order: Some of the products are out of stock'); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -220,7 +219,7 @@ public function testPlaceOrderOfGuestCart() $query = $this->getQuery($maskedQuoteId); self::expectExceptionMessageRegExp('/The current user cannot perform operations on cart*/'); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -243,7 +242,7 @@ public function testPlaceOrderOfAnotherCustomerCart() $query = $this->getQuery($maskedQuoteId); self::expectExceptionMessageRegExp('/The current user cannot perform operations on cart*/'); - $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer3@search.example.com')); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer3@search.example.com')); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveCouponFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveCouponFromCartTest.php index feba8c5c64259..ce1c85417b165 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveCouponFromCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveCouponFromCartTest.php @@ -50,7 +50,7 @@ public function testRemoveCouponFromCart() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('removeCouponFromCart', $response); self::assertNull($response['removeCouponFromCart']['cart']['applied_coupon']['code']); @@ -66,7 +66,7 @@ public function testRemoveCouponFromNonExistentCart() $maskedQuoteId = 'non_existent_masked_id'; $query = $this->getQuery($maskedQuoteId); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -80,7 +80,7 @@ public function testRemoveCouponFromEmptyCart() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -94,7 +94,7 @@ public function testRemoveCouponFromCartIfCouponWasNotSet() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('removeCouponFromCart', $response); self::assertNull($response['removeCouponFromCart']['cart']['applied_coupon']['code']); @@ -115,7 +115,7 @@ public function testRemoveCouponFromGuestCart() $query = $this->getQuery($maskedQuoteId); self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -132,7 +132,7 @@ public function testRemoveCouponFromAnotherCustomerCart() $query = $this->getQuery($maskedQuoteId); self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); - $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer3@search.example.com')); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer3@search.example.com')); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php index 1b6d1f3b14383..5f953d43355fd 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php @@ -55,7 +55,7 @@ public function testRemoveItemFromCart() $itemId = $this->getQuoteItemIdByReservedQuoteIdAndSku->execute('test_quote', 'simple_product'); $query = $this->getQuery($maskedQuoteId, $itemId); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); $this->assertArrayHasKey('removeItemFromCart', $response); $this->assertArrayHasKey('cart', $response['removeItemFromCart']); @@ -70,7 +70,7 @@ public function testRemoveItemFromCart() public function testRemoveItemFromNonExistentCart() { $query = $this->getQuery('non_existent_masked_id', 1); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -87,7 +87,7 @@ public function testRemoveNonExistentItem() $this->expectExceptionMessage("Cart doesn't contain the {$notExistentItemId} item."); $query = $this->getQuery($maskedQuoteId, $notExistentItemId); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -107,14 +107,14 @@ public function testUpdateWithMissedItemRequiredParameters(string $input, string ) { cart { items { - qty + quantity } } } } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -153,7 +153,7 @@ public function testRemoveItemIfItemIsNotBelongToCart() $this->expectExceptionMessage("Cart doesn't contain the {$secondQuoteItemId} item."); $query = $this->getQuery($firstQuoteMaskedId, $secondQuoteItemId); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -173,7 +173,7 @@ public function testRemoveItemFromGuestCart() ); $query = $this->getQuery($guestQuoteMaskedId, $guestQuoteItemId); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -196,7 +196,7 @@ public function testRemoveItemFromAnotherCustomerCart() ); $query = $this->getQuery($anotherCustomerQuoteMaskedId, $anotherCustomerQuoteItemId); - $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer2@search.example.com')); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer2@search.example.com')); } /** @@ -216,7 +216,7 @@ private function getQuery(string $maskedQuoteId, int $itemId): string ) { cart { items { - qty + quantity } } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php index b7b6fb5851666..fc73d88be1f87 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php @@ -99,13 +99,13 @@ public function testSetNewBillingAddress() code label } - address_type + __typename } } } } QUERY; - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']); $cartResponse = $response['setBillingAddressOnCart']['cart']; @@ -159,7 +159,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() code label } - address_type + __typename } shipping_addresses { firstname @@ -173,13 +173,13 @@ public function testSetNewBillingAddressWithUseForShippingParameter() code label } - address_type + __typename } } } } QUERY; - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']); $cartResponse = $response['setBillingAddressOnCart']['cart']; @@ -188,7 +188,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() self::assertArrayHasKey('shipping_addresses', $cartResponse); $shippingAddressResponse = current($cartResponse['shipping_addresses']); $this->assertNewAddressFields($billingAddressResponse); - $this->assertNewAddressFields($shippingAddressResponse, 'SHIPPING'); + $this->assertNewAddressFields($shippingAddressResponse, 'ShippingCartAddress'); } /** @@ -230,7 +230,7 @@ public function testSetBillingAddressFromAddressBook() } } QUERY; - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']); $cartResponse = $response['setBillingAddressOnCart']['cart']; @@ -270,7 +270,7 @@ public function testSetNotExistedBillingAddressFromAddressBook() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -318,7 +318,7 @@ public function testSetNewBillingAddressAndFromAddressBookAtSameTime() self::expectExceptionMessage( 'The billing address cannot contain "customer_address_id" and "address" at the same time.' ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -355,7 +355,7 @@ public function testSetBillingAddressToGuestCart() "The current user cannot perform operations on cart \"{$maskedQuoteId}\"" ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -390,7 +390,7 @@ public function testSetBillingAddressToAnotherCustomerCart() "The current user cannot perform operations on cart \"{$maskedQuoteId}\"" ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer@search.example.com')); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer@search.example.com')); } /** @@ -424,7 +424,7 @@ public function testSetBillingAddressIfCustomerIsNotOwnerOfAddress() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer2@search.example.com')); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer2@search.example.com')); } /** @@ -454,7 +454,7 @@ public function testSetBillingAddressOnNonExistentCart() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -490,7 +490,25 @@ public function testSetBillingAddressWithoutRequiredParameters(string $input, st QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query); + $this->graphQlMutation($query); + } + + /** + * @return array + */ + public function dataProviderSetWithoutRequiredParameters(): array + { + return [ + 'missed_billing_address' => [ + 'cart_id: "cart_id_value"', + 'Field SetBillingAddressOnCartInput.billing_address of required type BillingAddressInput!' + . ' was not provided.', + ], + 'missed_cart_id' => [ + 'billing_address: {}', + 'Required parameter "cart_id" is missing' + ] + ]; } /** @@ -533,25 +551,7 @@ public function testSetNewBillingAddressWithRedundantStreetLine() } QUERY; self::expectExceptionMessage('"Street Address" cannot contain more than 2 lines.'); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); - } - - /** - * @return array - */ - public function dataProviderSetWithoutRequiredParameters(): array - { - return [ - 'missed_billing_address' => [ - 'cart_id: "cart_id_value"', - 'Field SetBillingAddressOnCartInput.billing_address of required type BillingAddressInput!' - . ' was not provided.', - ], - 'missed_cart_id' => [ - 'billing_address: {}', - 'Required parameter "cart_id" is missing' - ] - ]; + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -560,7 +560,7 @@ public function dataProviderSetWithoutRequiredParameters(): array * @param array $addressResponse * @param string $addressType */ - private function assertNewAddressFields(array $addressResponse, string $addressType = 'BILLING'): void + private function assertNewAddressFields(array $addressResponse, string $addressType = 'BillingCartAddress'): void { $assertionMap = [ ['response_field' => 'firstname', 'expected_value' => 'test firstname'], @@ -571,7 +571,7 @@ private function assertNewAddressFields(array $addressResponse, string $addressT ['response_field' => 'postcode', 'expected_value' => '887766'], ['response_field' => 'telephone', 'expected_value' => '88776655'], ['response_field' => 'country', 'expected_value' => ['code' => 'US', 'label' => 'US']], - ['response_field' => 'address_type', 'expected_value' => $addressType] + ['response_field' => '__typename', 'expected_value' => $addressType] ]; $this->assertResponseFields($addressResponse, $assertionMap); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetGuestEmailOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetGuestEmailOnCartTest.php new file mode 100644 index 0000000000000..a4a84c2f8c740 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetGuestEmailOnCartTest.php @@ -0,0 +1,104 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Customer; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for setGuestEmailOnCart mutation + */ +class SetGuestEmailOnCartTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * + * @expectedException \Exception + * @expectedExceptionMessage The request is not allowed for logged in customers + */ + public function testSetGuestEmailOnCartForLoggedInCustomer() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $email = 'some@user.com'; + + $query = $this->getQuery($maskedQuoteId, $email); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * + * @expectedException \Exception + * @expectedExceptionMessage The request is not allowed for logged in customers + */ + public function testSetGuestEmailOnGuestCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $email = 'some@user.com'; + + $query = $this->getQuery($maskedQuoteId, $email); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * Returns GraphQl mutation query for setting email address for a guest + * + * @param string $maskedQuoteId + * @param string $email + * @return string + */ + private function getQuery(string $maskedQuoteId, string $email): string + { + return <<<QUERY +mutation { + setGuestEmailOnCart(input: { + cart_id: "$maskedQuoteId" + email: "$email" + }) { + cart { + email + } + } +} +QUERY; + } + + /** + * @param string $username + * @param string $password + * @return array + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + return $headerMap; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflinePaymentMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflinePaymentMethodsOnCartTest.php new file mode 100644 index 0000000000000..0fe7aec2d6347 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflinePaymentMethodsOnCartTest.php @@ -0,0 +1,177 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Customer; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\OfflinePayments\Model\Banktransfer; +use Magento\OfflinePayments\Model\Cashondelivery; +use Magento\OfflinePayments\Model\Checkmo; +use Magento\OfflinePayments\Model\Purchaseorder; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for setting offline payment methods on cart + */ +class SetOfflinePaymentMethodsOnCartTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + * + * @param string $methodCode + * @param string $methodTitle + * @dataProvider offlinePaymentMethodDataProvider + */ + public function testSetOfflinePaymentMethod(string $methodCode, string $methodTitle) + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId, $methodCode); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('setPaymentMethodOnCart', $response); + self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); + self::assertArrayHasKey('selected_payment_method', $response['setPaymentMethodOnCart']['cart']); + + $selectedPaymentMethod = $response['setPaymentMethodOnCart']['cart']['selected_payment_method']; + self::assertArrayHasKey('code', $selectedPaymentMethod); + self::assertEquals($methodCode, $selectedPaymentMethod['code']); + + self::assertArrayHasKey('title', $selectedPaymentMethod); + self::assertEquals($methodTitle, $selectedPaymentMethod['title']); + } + + /** + * @return array + */ + public function offlinePaymentMethodDataProvider(): array + { + return [ + 'check_mo' => [Checkmo::PAYMENT_METHOD_CHECKMO_CODE, 'Check / Money order'], + 'bank_transfer' => [Banktransfer::PAYMENT_METHOD_BANKTRANSFER_CODE, 'Bank Transfer Payment'], + 'cash_on_delivery' => [Cashondelivery::PAYMENT_METHOD_CASHONDELIVERY_CODE, 'Cash On Delivery'], + ]; + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + */ + public function testSetPurchaseOrderPaymentMethod() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $methodTitle = 'Purchase Order'; + $methodCode = Purchaseorder::PAYMENT_METHOD_PURCHASEORDER_CODE; + $poNumber = 'abc123'; + + $query = <<<QUERY +mutation { + setPaymentMethodOnCart(input: { + cart_id: "{$maskedQuoteId}", + payment_method: { + code: "{$methodCode}" + purchase_order_number: "{$poNumber}" + } + }) { + cart { + selected_payment_method { + code + title + purchase_order_number + } + } + } +} +QUERY; + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('setPaymentMethodOnCart', $response); + self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); + self::assertArrayHasKey('selected_payment_method', $response['setPaymentMethodOnCart']['cart']); + + $selectedPaymentMethod = $response['setPaymentMethodOnCart']['cart']['selected_payment_method']; + self::assertArrayHasKey('code', $selectedPaymentMethod); + self::assertEquals($methodCode, $selectedPaymentMethod['code']); + + self::assertArrayHasKey('title', $selectedPaymentMethod); + self::assertEquals($methodTitle, $selectedPaymentMethod['title']); + + self::assertArrayHasKey('purchase_order_number', $selectedPaymentMethod); + self::assertEquals($poNumber, $selectedPaymentMethod['purchase_order_number']); + } + + /** + * @param string $maskedQuoteId + * @param string $methodCode + * @return string + */ + private function getQuery( + string $maskedQuoteId, + string $methodCode + ) : string { + return <<<QUERY +mutation { + setPaymentMethodOnCart(input: { + cart_id: "{$maskedQuoteId}", + payment_method: { + code: "{$methodCode}" + } + }) { + cart { + selected_payment_method { + code + title + } + } + } +} +QUERY; + } + + /** + * @param string $username + * @param string $password + * @return array + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + return $headerMap; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflineShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflineShippingMethodsOnCartTest.php index b7b7823263106..c6c4a18bce4ab 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflineShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflineShippingMethodsOnCartTest.php @@ -8,7 +8,6 @@ namespace Magento\GraphQl\Quote\Customer; use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; -use Magento\GraphQl\Quote\GetQuoteShippingAddressIdByReservedQuoteId; use Magento\Integration\Api\CustomerTokenServiceInterface; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -23,11 +22,6 @@ class SetOfflineShippingMethodsOnCartTest extends GraphQlAbstract */ private $getMaskedQuoteIdByReservedOrderId; - /** - * @var GetQuoteShippingAddressIdByReservedQuoteId - */ - private $getQuoteShippingAddressIdByReservedQuoteId; - /** * @var CustomerTokenServiceInterface */ @@ -40,9 +34,6 @@ protected function setUp() { $objectManager = Bootstrap::getObjectManager(); $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); - $this->getQuoteShippingAddressIdByReservedQuoteId = $objectManager->get( - GetQuoteShippingAddressIdByReservedQuoteId::class - ); $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); } @@ -57,22 +48,29 @@ protected function setUp() * * @param string $carrierCode * @param string $methodCode - * @param float $amount - * @param string $label + * @param string $carrierTitle + * @param string $methodTitle + * @param array $amount + * @param array $baseAmount + * @throws \Magento\Framework\Exception\NoSuchEntityException * @dataProvider offlineShippingMethodDataProvider */ - public function testSetOfflineShippingMethod(string $carrierCode, string $methodCode, float $amount, string $label) - { + public function testSetOfflineShippingMethod( + string $carrierCode, + string $methodCode, + string $carrierTitle, + string $methodTitle, + array $amount, + array $baseAmount + ) { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); $query = $this->getQuery( $maskedQuoteId, $methodCode, - $carrierCode, - $quoteAddressId + $carrierCode ); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('setShippingMethodsOnCart', $response); self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); @@ -88,11 +86,17 @@ public function testSetOfflineShippingMethod(string $carrierCode, string $method self::assertArrayHasKey('method_code', $shippingAddress['selected_shipping_method']); self::assertEquals($methodCode, $shippingAddress['selected_shipping_method']['method_code']); + self::assertArrayHasKey('carrier_title', $shippingAddress['selected_shipping_method']); + self::assertEquals($carrierTitle, $shippingAddress['selected_shipping_method']['carrier_title']); + + self::assertArrayHasKey('method_title', $shippingAddress['selected_shipping_method']); + self::assertEquals($methodTitle, $shippingAddress['selected_shipping_method']['method_title']); + self::assertArrayHasKey('amount', $shippingAddress['selected_shipping_method']); self::assertEquals($amount, $shippingAddress['selected_shipping_method']['amount']); - self::assertArrayHasKey('label', $shippingAddress['selected_shipping_method']); - self::assertEquals($label, $shippingAddress['selected_shipping_method']['label']); + self::assertArrayHasKey('base_amount', $shippingAddress['selected_shipping_method']); + self::assertEquals($baseAmount, $shippingAddress['selected_shipping_method']['base_amount']); } /** @@ -101,9 +105,30 @@ public function testSetOfflineShippingMethod(string $carrierCode, string $method public function offlineShippingMethodDataProvider(): array { return [ - 'flatrate_flatrate' => ['flatrate', 'flatrate', 10, 'Flat Rate - Fixed'], - 'tablerate_bestway' => ['tablerate', 'bestway', 10, 'Best Way - Table Rate'], - 'freeshipping_freeshipping' => ['freeshipping', 'freeshipping', 0, 'Free Shipping - Free'], + 'flatrate_flatrate' => [ + 'flatrate', + 'flatrate', + 'Flat Rate', + 'Fixed', + ['value' => 10, 'currency' => 'USD'], + ['value' => 10, 'currency' => 'USD'], + ], + 'tablerate_bestway' => [ + 'tablerate', + 'bestway', + 'Best Way', + 'Table Rate', + ['value' => 10, 'currency' => 'USD'], + ['value' => 10, 'currency' => 'USD'], + ], + 'freeshipping_freeshipping' => [ + 'freeshipping', + 'freeshipping', + 'Free Shipping', + 'Free', + ['value' => 0, 'currency' => 'USD'], + ['value' => 0, 'currency' => 'USD'], + ], ]; } @@ -111,14 +136,12 @@ public function offlineShippingMethodDataProvider(): array * @param string $maskedQuoteId * @param string $shippingMethodCode * @param string $shippingCarrierCode - * @param int $shippingAddressId * @return string */ private function getQuery( string $maskedQuoteId, string $shippingMethodCode, - string $shippingCarrierCode, - int $shippingAddressId + string $shippingCarrierCode ): string { return <<<QUERY mutation { @@ -126,7 +149,6 @@ private function getQuery( { cart_id: "$maskedQuoteId", shipping_methods: [{ - cart_address_id: $shippingAddressId carrier_code: "$shippingCarrierCode" method_code: "$shippingMethodCode" }] @@ -136,8 +158,16 @@ private function getQuery( selected_shipping_method { carrier_code method_code - amount - label + carrier_title + method_title + amount { + value + currency + } + base_amount { + value + currency + } } } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php index 73feefe2b094b..593bb8c9fcb78 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php @@ -7,10 +7,12 @@ namespace Magento\GraphQl\Quote\Customer; +use Exception; use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\Integration\Api\CustomerTokenServiceInterface; use Magento\OfflinePayments\Model\Cashondelivery; use Magento\OfflinePayments\Model\Checkmo; +use Magento\OfflinePayments\Model\Purchaseorder; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -52,7 +54,7 @@ public function testSetPaymentOnCartWithSimpleProduct() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $methodCode); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('setPaymentMethodOnCart', $response); self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); @@ -66,7 +68,7 @@ public function testSetPaymentOnCartWithSimpleProduct() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * - * @expectedException \Exception + * @expectedException Exception * @expectedExceptionMessage The shipping address is missing. Set the address and try again. */ public function testSetPaymentOnCartWithSimpleProductAndWithoutAddress() @@ -75,7 +77,7 @@ public function testSetPaymentOnCartWithSimpleProductAndWithoutAddress() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $methodCode); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -90,7 +92,7 @@ public function testSetPaymentOnCartWithVirtualProduct() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $methodCode); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('setPaymentMethodOnCart', $response); self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); @@ -105,7 +107,7 @@ public function testSetPaymentOnCartWithVirtualProduct() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php * - * @expectedException \Exception + * @expectedException Exception * @expectedExceptionMessage The requested Payment Method is not available. */ public function testSetNonExistentPaymentMethod() @@ -114,13 +116,13 @@ public function testSetNonExistentPaymentMethod() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $methodCode); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * - * @expectedException \Exception + * @expectedException Exception * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" */ public function testSetPaymentOnNonExistentCart() @@ -129,7 +131,7 @@ public function testSetPaymentOnNonExistentCart() $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE; $query = $this->getQuery($maskedQuoteId, $methodCode); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -149,7 +151,7 @@ public function testSetPaymentMethodToGuestCart() $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -169,7 +171,7 @@ public function testSetPaymentMethodToAnotherCustomerCart() $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer2@search.example.com')); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer2@search.example.com')); } /** @@ -180,7 +182,7 @@ public function testSetPaymentMethodToAnotherCustomerCart() * * @param string $input * @param string $message - * @throws \Exception + * @throws Exception * @dataProvider dataProviderSetPaymentMethodWithoutRequiredParameters */ public function testSetPaymentMethodWithoutRequiredParameters(string $input, string $message) @@ -194,14 +196,32 @@ public function testSetPaymentMethodWithoutRequiredParameters(string $input, str ) { cart { items { - qty + quantity } } } } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @expectedException Exception + * @expectedExceptionMessage The requested Payment Method is not available. + */ + public function testSetDisabledPaymentOnCart() + { + $methodCode = Purchaseorder::PAYMENT_METHOD_PURCHASEORDER_CODE; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId, $methodCode); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -240,7 +260,7 @@ public function testReSetPayment() $methodCode = Cashondelivery::PAYMENT_METHOD_CASHONDELIVERY_CODE; $query = $this->getQuery($maskedQuoteId, $methodCode); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('setPaymentMethodOnCart', $response); self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPurchaseOrderPaymentMethodOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPurchaseOrderPaymentMethodOnCartTest.php new file mode 100644 index 0000000000000..bff66ece59b93 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPurchaseOrderPaymentMethodOnCartTest.php @@ -0,0 +1,169 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Customer; + +use Exception; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\OfflinePayments\Model\Purchaseorder; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for setting Purchase Order payment method on cart by customer + */ +class SetPurchaseOrderPaymentMethodOnCartTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + */ + public function testSetPurchaseOrderPaymentMethodOnCartWithSimpleProduct() + { + $methodCode = Purchaseorder::PAYMENT_METHOD_PURCHASEORDER_CODE; + $purchaseOrderNumber = '123456'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = <<<QUERY +mutation { + setPaymentMethodOnCart(input: { + cart_id: "$maskedQuoteId" + payment_method: { + code: "$methodCode" + purchase_order_number: "$purchaseOrderNumber" + } + }) { + cart { + selected_payment_method { + code + purchase_order_number + } + } + } +} +QUERY; + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('setPaymentMethodOnCart', $response); + self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); + self::assertArrayHasKey('selected_payment_method', $response['setPaymentMethodOnCart']['cart']); + self::assertEquals($methodCode, $response['setPaymentMethodOnCart']['cart']['selected_payment_method']['code']); + self::assertEquals( + $purchaseOrderNumber, + $response['setPaymentMethodOnCart']['cart']['selected_payment_method']['purchase_order_number'] + ); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + * + * @expectedException Exception + * @expectedExceptionMessage Purchase order number is a required field. + */ + public function testSetPurchaseOrderPaymentMethodOnCartWithoutPurchaseOrderNumber() + { + $methodCode = Purchaseorder::PAYMENT_METHOD_PURCHASEORDER_CODE; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = <<<QUERY +mutation { + setPaymentMethodOnCart(input: { + cart_id: "$maskedQuoteId" + payment_method: { + code: "$methodCode" + } + }) { + cart { + selected_payment_method { + code + } + } + } +} +QUERY; + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * + * @expectedException Exception + * @expectedExceptionMessage The requested Payment Method is not available. + */ + public function testSetDisabledPurchaseOrderPaymentMethodOnCart() + { + $methodCode = Purchaseorder::PAYMENT_METHOD_PURCHASEORDER_CODE; + $purchaseOrderNumber = '123456'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = <<<QUERY +mutation { + setPaymentMethodOnCart(input: { + cart_id: "$maskedQuoteId" + payment_method: { + code: "$methodCode" + purchase_order_number: "$purchaseOrderNumber" + } + }) { + cart { + selected_payment_method { + code + purchase_order_number + } + } + } +} +QUERY; + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @param string $username + * @param string $password + * @return array + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + return $headerMap; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php index 0756ba6aeaf22..5d7a073d2d6d5 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php @@ -101,13 +101,13 @@ public function testSetNewShippingAddressOnCartWithSimpleProduct() label code } - address_type + __typename } } } } QUERY; - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('cart', $response['setShippingAddressesOnCart']); $cartResponse = $response['setShippingAddressesOnCart']['cart']; @@ -160,7 +160,7 @@ public function testSetNewShippingAddressOnCartWithVirtualProduct() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -200,7 +200,7 @@ public function testSetShippingAddressFromAddressBook() } } QUERY; - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('cart', $response['setShippingAddressesOnCart']); $cartResponse = $response['setShippingAddressesOnCart']['cart']; @@ -242,7 +242,7 @@ public function testSetNonExistentShippingAddressFromAddressBook() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -291,7 +291,7 @@ public function testSetNewShippingAddressAndFromAddressBookAtSameTime() self::expectExceptionMessage( 'The shipping address cannot contain "customer_address_id" and "address" at the same time.' ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -328,7 +328,7 @@ public function testSetShippingAddressIfCustomerIsNotOwnerOfAddress() } QUERY; - $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer2@search.example.com')); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer2@search.example.com')); } /** @@ -366,7 +366,7 @@ public function testSetShippingAddressToAnotherCustomerCart() $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer2@search.example.com')); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer2@search.example.com')); } /** @@ -405,7 +405,7 @@ public function testSetNewShippingAddressWithMissedRequiredParameters(string $in } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -483,7 +483,7 @@ public function testSetMultipleNewShippingAddresses() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -529,7 +529,7 @@ public function testSetNewShippingAddressOnCartWithRedundantStreetLine() QUERY; self::expectExceptionMessage('"Street Address" cannot contain more than 2 lines.'); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -548,7 +548,7 @@ private function assertNewShippingAddressFields(array $shippingAddressResponse): ['response_field' => 'postcode', 'expected_value' => '887766'], ['response_field' => 'telephone', 'expected_value' => '88776655'], ['response_field' => 'country', 'expected_value' => ['code' => 'US', 'label' => 'US']], - ['response_field' => 'address_type', 'expected_value' => 'SHIPPING'] + ['response_field' => '__typename', 'expected_value' => 'ShippingCartAddress'] ]; $this->assertResponseFields($shippingAddressResponse, $assertionMap); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php index 9219b5a67022c..9ddedbfc46541 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php @@ -7,8 +7,8 @@ namespace Magento\GraphQl\Quote\Customer; +use Exception; use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; -use Magento\GraphQl\Quote\GetQuoteShippingAddressIdByReservedQuoteId; use Magento\Integration\Api\CustomerTokenServiceInterface; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -23,11 +23,6 @@ class SetShippingMethodsOnCartTest extends GraphQlAbstract */ private $getMaskedQuoteIdByReservedOrderId; - /** - * @var GetQuoteShippingAddressIdByReservedQuoteId - */ - private $getQuoteShippingAddressIdByReservedQuoteId; - /** * @var CustomerTokenServiceInterface */ @@ -40,9 +35,6 @@ protected function setUp() { $objectManager = Bootstrap::getObjectManager(); $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); - $this->getQuoteShippingAddressIdByReservedQuoteId = $objectManager->get( - GetQuoteShippingAddressIdByReservedQuoteId::class - ); $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); } @@ -58,15 +50,13 @@ public function testSetShippingMethodOnCartWithSimpleProduct() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $carrierCode = 'flatrate'; $methodCode = 'flatrate'; - $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); $query = $this->getQuery( $maskedQuoteId, $methodCode, - $carrierCode, - $quoteAddressId + $carrierCode ); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('setShippingMethodsOnCart', $response); self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); @@ -77,10 +67,32 @@ public function testSetShippingMethodOnCartWithSimpleProduct() self::assertArrayHasKey('selected_shipping_method', $shippingAddress); self::assertArrayHasKey('carrier_code', $shippingAddress['selected_shipping_method']); - self::assertEquals($carrierCode, $shippingAddress['selected_shipping_method']['carrier_code']); + self::assertEquals('flatrate', $shippingAddress['selected_shipping_method']['carrier_code']); self::assertArrayHasKey('method_code', $shippingAddress['selected_shipping_method']); - self::assertEquals($methodCode, $shippingAddress['selected_shipping_method']['method_code']); + self::assertEquals('flatrate', $shippingAddress['selected_shipping_method']['method_code']); + + self::assertArrayHasKey('carrier_title', $shippingAddress['selected_shipping_method']); + self::assertEquals('Flat Rate', $shippingAddress['selected_shipping_method']['carrier_title']); + + self::assertArrayHasKey('method_title', $shippingAddress['selected_shipping_method']); + self::assertEquals('Fixed', $shippingAddress['selected_shipping_method']['method_title']); + + self::assertArrayHasKey('amount', $shippingAddress['selected_shipping_method']); + $amount = $shippingAddress['selected_shipping_method']['amount']; + + self::assertArrayHasKey('value', $amount); + self::assertEquals(10, $amount['value']); + self::assertArrayHasKey('currency', $amount); + self::assertEquals('USD', $amount['currency']); + + self::assertArrayHasKey('base_amount', $shippingAddress['selected_shipping_method']); + $baseAmount = $shippingAddress['selected_shipping_method']['base_amount']; + + self::assertArrayHasKey('value', $baseAmount); + self::assertEquals(10, $baseAmount['value']); + self::assertArrayHasKey('currency', $baseAmount); + self::assertEquals('USD', $baseAmount['currency']); } /** @@ -97,15 +109,13 @@ public function testReSetShippingMethod() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $carrierCode = 'freeshipping'; $methodCode = 'freeshipping'; - $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); $query = $this->getQuery( $maskedQuoteId, $methodCode, - $carrierCode, - $quoteAddressId + $carrierCode ); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('setShippingMethodsOnCart', $response); self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); @@ -132,13 +142,12 @@ public function testReSetShippingMethod() * @param string $input * @param string $message * @dataProvider dataProviderSetShippingMethodWithWrongParameters - * @throws \Exception + * @throws Exception */ public function testSetShippingMethodWithWrongParameters(string $input, string $message) { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); - $input = str_replace(['cart_id_value', 'cart_address_id_value'], [$maskedQuoteId, $quoteAddressId], $input); + $input = str_replace('cart_id_value', $maskedQuoteId, $input); $query = <<<QUERY mutation { @@ -156,7 +165,7 @@ public function testSetShippingMethodWithWrongParameters(string $input, string $ } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -168,7 +177,6 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array return [ 'missed_cart_id' => [ 'shipping_methods: [{ - cart_address_id: cart_address_id_value carrier_code: "flatrate" method_code: "flatrate" }]', @@ -182,31 +190,14 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array 'cart_id: "cart_id_value" shipping_methods: []', 'Required parameter "shipping_methods" is missing' ], - 'missed_cart_address_id' => [ - 'cart_id: "cart_id_value", shipping_methods: [{ - carrier_code: "flatrate" - method_code: "flatrate" - }]', - 'Required parameter "cart_address_id" is missing.' - ], - 'non_existent_cart_address_id' => [ - 'cart_id: "cart_id_value", shipping_methods: [{ - cart_address_id: -1 - carrier_code: "flatrate" - method_code: "flatrate" - }]', - 'Could not find a cart address with ID "-1"' - ], 'missed_carrier_code' => [ 'cart_id: "cart_id_value", shipping_methods: [{ - cart_address_id: cart_address_id_value method_code: "flatrate" }]', 'Field ShippingMethodInput.carrier_code of required type String! was not provided.' ], 'empty_carrier_code' => [ 'cart_id: "cart_id_value", shipping_methods: [{ - cart_address_id: cart_address_id_value carrier_code: "" method_code: "flatrate" }]', @@ -214,7 +205,6 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array ], 'non_existent_carrier_code' => [ 'cart_id: "cart_id_value", shipping_methods: [{ - cart_address_id: cart_address_id_value carrier_code: "wrong-carrier-code" method_code: "flatrate" }]', @@ -222,14 +212,12 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array ], 'missed_method_code' => [ 'cart_id: "cart_id_value", shipping_methods: [{ - cart_address_id: cart_address_id_value carrier_code: "flatrate" }]', 'Required parameter "method_code" is missing.' ], 'empty_method_code' => [ 'cart_id: "cart_id_value", shipping_methods: [{ - cart_address_id: cart_address_id_value carrier_code: "flatrate" method_code: "" }]', @@ -237,7 +225,6 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array ], 'non_existent_method_code' => [ 'cart_id: "cart_id_value", shipping_methods: [{ - cart_address_id: cart_address_id_value carrier_code: "flatrate" method_code: "wrong-carrier-code" }]', @@ -245,12 +232,18 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array ], 'non_existent_shopping_cart' => [ 'cart_id: "non_existent_masked_id", shipping_methods: [{ - cart_address_id: cart_address_id_value carrier_code: "flatrate" method_code: "flatrate" }]', 'Could not find a cart with ID "non_existent_masked_id"' ], + 'disabled_shipping_method' => [ + 'cart_id: "cart_id_value", shipping_methods: [{ + carrier_code: "freeshipping" + method_code: "freeshipping" + }]', + 'Carrier with such method not found: freeshipping, freeshipping' + ] ]; } @@ -261,13 +254,12 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php - * @expectedException \Exception + * @expectedException Exception * @expectedExceptionMessage You cannot specify multiple shipping methods. */ public function testSetMultipleShippingMethods() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); $query = <<<QUERY mutation { @@ -275,12 +267,10 @@ public function testSetMultipleShippingMethods() cart_id: "{$maskedQuoteId}", shipping_methods: [ { - cart_address_id: {$quoteAddressId} carrier_code: "flatrate" method_code: "flatrate" } { - cart_address_id: {$quoteAddressId} carrier_code: "flatrate" method_code: "flatrate" } @@ -296,7 +286,7 @@ public function testSetMultipleShippingMethods() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -307,25 +297,23 @@ public function testSetMultipleShippingMethods() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php * - * @expectedException \Exception + * @expectedException Exception */ public function testSetShippingMethodToGuestCart() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $carrierCode = 'flatrate'; $methodCode = 'flatrate'; - $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); $query = $this->getQuery( $maskedQuoteId, $methodCode, - $carrierCode, - $quoteAddressId + $carrierCode ); $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -336,67 +324,35 @@ public function testSetShippingMethodToGuestCart() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php * - * @expectedException \Exception + * @expectedException Exception */ public function testSetShippingMethodToAnotherCustomerCart() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $carrierCode = 'flatrate'; $methodCode = 'flatrate'; - $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); $query = $this->getQuery( $maskedQuoteId, $methodCode, - $carrierCode, - $quoteAddressId + $carrierCode ); $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer2@search.example.com')); - } - - /** - * _security - * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php - * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php - * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php - * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php - * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/quote_with_address.php - */ - public function testSetShippingMethodIfCustomerIsNotOwnerOfAddress() - { - $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $carrierCode = 'flatrate'; - $methodCode = 'flatrate'; - $anotherQuoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('guest_quote_with_address'); - $query = $this->getQuery( - $maskedQuoteId, - $methodCode, - $carrierCode, - $anotherQuoteAddressId - ); - - $this->expectExceptionMessage( - "Cart does not contain address with ID \"{$anotherQuoteAddressId}\"" - ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer2@search.example.com')); } /** * @param string $maskedQuoteId * @param string $shippingMethodCode * @param string $shippingCarrierCode - * @param int $shippingAddressId * @return string */ private function getQuery( string $maskedQuoteId, string $shippingMethodCode, - string $shippingCarrierCode, - int $shippingAddressId + string $shippingCarrierCode ): string { return <<<QUERY mutation { @@ -404,7 +360,6 @@ private function getQuery( { cart_id: "$maskedQuoteId", shipping_methods: [{ - cart_address_id: $shippingAddressId carrier_code: "$shippingCarrierCode" method_code: "$shippingMethodCode" }] @@ -414,6 +369,16 @@ private function getQuery( selected_shipping_method { carrier_code method_code + carrier_title + method_title + amount { + value + currency + } + base_amount { + value + currency + } } } } @@ -422,6 +387,28 @@ private function getQuery( QUERY; } + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * + * @expectedException Exception + * @expectedExceptionMessage The shipping method can't be set for an empty cart. Add an item to cart and try again. + */ + public function testSetShippingMethodOnAnEmptyCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $carrierCode = 'flatrate'; + $methodCode = 'flatrate'; + + $query = $this->getQuery( + $maskedQuoteId, + $methodCode, + $carrierCode + ); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + /** * @param string $username * @param string $password diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/UpdateCartItemsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/UpdateCartItemsTest.php index 74e7aa8b5d0a4..bc88bd1ddb438 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/UpdateCartItemsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/UpdateCartItemsTest.php @@ -58,16 +58,16 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php */ - public function testUpdateCartItemQty() + public function testUpdateCartItemQuantity() { $quote = $this->quoteFactory->create(); $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id'); $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId()); $itemId = (int)$quote->getItemByProduct($this->productRepository->get('simple'))->getId(); - $qty = 2; + $quantity = 2; - $query = $this->getQuery($maskedQuoteId, $itemId, $qty); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $query = $this->getQuery($maskedQuoteId, $itemId, $quantity); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); $this->assertArrayHasKey('updateCartItems', $response); $this->assertArrayHasKey('cart', $response['updateCartItems']); @@ -76,7 +76,7 @@ public function testUpdateCartItemQty() $item = current($responseCart['items']); $this->assertEquals($itemId, $item['id']); - $this->assertEquals($qty, $item['qty']); + $this->assertEquals($quantity, $item['quantity']); } /** @@ -88,10 +88,10 @@ public function testRemoveCartItemIfQuantityIsZero() $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id'); $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId()); $itemId = (int)$quote->getItemByProduct($this->productRepository->get('simple'))->getId(); - $qty = 0; + $quantity = 0; - $query = $this->getQuery($maskedQuoteId, $itemId, $qty); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $query = $this->getQuery($maskedQuoteId, $itemId, $quantity); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); $this->assertArrayHasKey('updateCartItems', $response); $this->assertArrayHasKey('cart', $response['updateCartItems']); @@ -108,7 +108,7 @@ public function testRemoveCartItemIfQuantityIsZero() public function testUpdateItemInNonExistentCart() { $query = $this->getQuery('non_existent_masked_id', 1, 2); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -124,7 +124,7 @@ public function testUpdateNonExistentItem() $this->expectExceptionMessage("Could not find cart item with id: {$notExistentItemId}."); $query = $this->getQuery($maskedQuoteId, $notExistentItemId, 2); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -152,7 +152,7 @@ public function testUpdateItemIfItemIsNotBelongToCart() $this->expectExceptionMessage("Could not find cart item with id: {$secondQuoteItemId}."); $query = $this->getQuery($firstQuoteMaskedId, $secondQuoteItemId, 2); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -177,7 +177,7 @@ public function testUpdateItemInGuestCart() ); $query = $this->getQuery($guestQuoteMaskedId, $guestQuoteItemId, 2); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -206,7 +206,7 @@ public function testUpdateItemInAnotherCustomerCart() ); $query = $this->getQuery($anotherCustomerQuoteMaskedId, $anotherCustomerQuoteItemId, 2); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -229,13 +229,13 @@ public function testUpdateWithMissedCartItemId() cart { items { id - qty + quantity } } } } QUERY; - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -259,14 +259,14 @@ public function testUpdateWithMissedItemRequiredParameters(string $input, string cart { items { id - qty + quantity } } } } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -285,7 +285,7 @@ public function dataProviderUpdateWithMissedRequiredParameters(): array ], 'missed_cart_item_qty' => [ 'cart_items: [{ cart_item_id: 1 }]', - 'Required parameter "quantity" for "cart_items" is missing.' + 'Field CartItemUpdateInput.quantity of required type Float! was not provided.' ], ]; } @@ -293,10 +293,10 @@ public function dataProviderUpdateWithMissedRequiredParameters(): array /** * @param string $maskedQuoteId * @param int $itemId - * @param float $qty + * @param float $quantity * @return string */ - private function getQuery(string $maskedQuoteId, int $itemId, float $qty): string + private function getQuery(string $maskedQuoteId, int $itemId, float $quantity): string { return <<<QUERY mutation { @@ -305,14 +305,14 @@ private function getQuery(string $maskedQuoteId, int $itemId, float $qty): strin cart_items:[ { cart_item_id: {$itemId} - quantity: {$qty} + quantity: {$quantity} } ] }) { cart { items { id - qty + quantity } } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php new file mode 100644 index 0000000000000..8b20320fc2f02 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php @@ -0,0 +1,137 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Guest; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Add simple product to cart testcases + */ +class AddSimpleProductToCartTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + */ + public function testAddSimpleProductToCart() + { + $sku = 'simple_product'; + $quantity = 2; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); + $response = $this->graphQlMutation($query); + self::assertArrayHasKey('cart', $response['addSimpleProductsToCart']); + + self::assertEquals($quantity, $response['addSimpleProductsToCart']['cart']['items'][0]['quantity']); + self::assertEquals($sku, $response['addSimpleProductsToCart']['cart']['items'][0]['product']['sku']); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * + * @expectedException \Exception + * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" + */ + public function testAddProductToNonExistentCart() + { + $sku = 'simple_product'; + $quantity = 1; + $maskedQuoteId = 'non_existent_masked_id'; + + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * + * @expectedException \Exception + * @expectedExceptionMessage Could not find a product with SKU "simple_product" + */ + public function testNonExistentProductToCart() + { + $sku = 'simple_product'; + $quantity = 1; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + */ + public function testAddSimpleProductToCustomerCart() + { + $sku = 'simple_product'; + $quantity = 2; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$maskedQuoteId\"" + ); + + $this->graphQlMutation($query); + } + + /** + * @param string $maskedQuoteId + * @param string $sku + * @param float $quantity + * @return string + */ + private function getQuery(string $maskedQuoteId, string $sku, float $quantity): string + { + return <<<QUERY +mutation { + addSimpleProductsToCart( + input: { + cart_id: "{$maskedQuoteId}" + cartItems: [ + { + data: { + quantity: $quantity + sku: "$sku" + } + } + ] + } + ) { + cart { + items { + quantity + product { + sku + } + } + } + } +} +QUERY; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddVirtualProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddVirtualProductToCartTest.php new file mode 100644 index 0000000000000..cb429e7b9cb9e --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddVirtualProductToCartTest.php @@ -0,0 +1,138 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Guest; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Add virtual product to cart testcases + */ +class AddVirtualProductToCartTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/virtual_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + */ + public function testAddVirtualProductToCart() + { + $sku = 'virtual_product'; + $quantity = 2; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); + $response = $this->graphQlMutation($query); + + self::assertArrayHasKey('cart', $response['addVirtualProductsToCart']); + self::assertEquals($quantity, $response['addVirtualProductsToCart']['cart']['items'][0]['quantity']); + self::assertEquals($sku, $response['addVirtualProductsToCart']['cart']['items'][0]['product']['sku']); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/virtual_product.php + * + * @expectedException \Exception + * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" + */ + public function testAddVirtualToNonExistentCart() + { + $sku = 'virtual_product'; + $quantity = 1; + $maskedQuoteId = 'non_existent_masked_id'; + + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * + * @expectedException \Exception + * @expectedExceptionMessage Could not find a product with SKU "virtual_product" + */ + public function testNonExistentProductToCart() + { + $sku = 'virtual_product'; + $quantity = 1; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); + $this->graphQlMutation($query); + } + + /** + * _security + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/virtual_product.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + */ + public function testAddVirtualProductToCustomerCart() + { + $sku = 'virtual_product'; + $quantity = 2; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$maskedQuoteId\"" + ); + + $this->graphQlMutation($query); + } + + /** + * @param string $maskedQuoteId + * @param string $sku + * @param float $quantity + * @return string + */ + private function getQuery(string $maskedQuoteId, string $sku, float $quantity): string + { + return <<<QUERY +mutation { + addVirtualProductsToCart( + input: { + cart_id: "{$maskedQuoteId}" + cartItems: [ + { + data: { + quantity: {$quantity} + sku: "{$sku}" + } + } + ] + } + ) { + cart { + items { + quantity + product { + sku + } + } + } + } +} +QUERY; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/ApplyCouponToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/ApplyCouponToCartTest.php index 045a28834e0dd..affe36ea8617d 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/ApplyCouponToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/ApplyCouponToCartTest.php @@ -38,7 +38,7 @@ public function testApplyCouponToCart() $couponCode = '2?ds5!2d'; $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $couponCode); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('applyCouponToCart', $response); self::assertEquals($couponCode, $response['applyCouponToCart']['cart']['applied_coupon']['code']); @@ -57,12 +57,12 @@ public function testApplyCouponTwice() $couponCode = '2?ds5!2d'; $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $couponCode); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey("applyCouponToCart", $response); self::assertEquals($couponCode, $response['applyCouponToCart']['cart']['applied_coupon']['code']); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -78,7 +78,7 @@ public function testApplyCouponToCartWithoutItems() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $couponCode); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -95,7 +95,7 @@ public function testApplyCouponToCustomerCart() $query = $this->getQuery($maskedQuoteId, $couponCode); self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -111,7 +111,7 @@ public function testApplyNonExistentCouponToCart() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $couponCode); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -125,7 +125,7 @@ public function testApplyCouponToNonExistentCart() $query = $this->getQuery($maskedQuoteId, $couponCode); self::expectExceptionMessage('Could not find a cart with ID "' . $maskedQuoteId . '"'); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -144,7 +144,7 @@ public function testApplyExpiredCoupon() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $couponCode); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -164,7 +164,7 @@ public function testApplyCouponWhichIsNotApplicable() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $couponCode); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -190,7 +190,7 @@ public function testApplyCouponWithMissedRequiredParameters(string $input, strin QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CartTotalsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CartTotalsTest.php new file mode 100644 index 0000000000000..ee2d6a2b31de0 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CartTotalsTest.php @@ -0,0 +1,168 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Guest; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test getting cart totals for guest + */ +class CartTotalsTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Tax/_files/tax_rule_for_region_1.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/apply_tax_for_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + */ + public function testGetCartTotalsWithTaxApplied() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query); + + self::assertArrayHasKey('prices', $response['cart']); + $pricesResponse = $response['cart']['prices']; + self::assertEquals(21.5, $pricesResponse['grand_total']['value']); + self::assertEquals(21.5, $pricesResponse['subtotal_including_tax']['value']); + self::assertEquals(20, $pricesResponse['subtotal_excluding_tax']['value']); + self::assertEquals(20, $pricesResponse['subtotal_with_discount_excluding_tax']['value']); + + $appliedTaxesResponse = $pricesResponse['applied_taxes']; + + self::assertEquals('US-TEST-*-Rate-1', $appliedTaxesResponse[0]['label']); + self::assertEquals(1.5, $appliedTaxesResponse[0]['amount']['value']); + self::assertEquals('USD', $appliedTaxesResponse[0]['amount']['currency']); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + */ + public function testGetTotalsWithNoTaxApplied() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query); + + $pricesResponse = $response['cart']['prices']; + self::assertEquals(20, $pricesResponse['grand_total']['value']); + self::assertEquals(20, $pricesResponse['subtotal_including_tax']['value']); + self::assertEquals(20, $pricesResponse['subtotal_excluding_tax']['value']); + self::assertEquals(20, $pricesResponse['subtotal_with_discount_excluding_tax']['value']); + self::assertEmpty($pricesResponse['applied_taxes']); + } + + /** + * The totals calculation is based on quote address. + * But the totals should be calculated even if no address is set + * + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @group recent + */ + public function testGetCartTotalsWithNoAddressSet() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query); + + $pricesResponse = $response['cart']['prices']; + self::assertEquals(20, $pricesResponse['grand_total']['value']); + self::assertEquals(20, $pricesResponse['subtotal_including_tax']['value']); + self::assertEquals(20, $pricesResponse['subtotal_excluding_tax']['value']); + self::assertEquals(20, $pricesResponse['subtotal_with_discount_excluding_tax']['value']); + self::assertEmpty($pricesResponse['applied_taxes']); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Tax/_files/tax_rule_for_region_1.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/apply_tax_for_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + */ + public function testGetSelectedShippingMethodFromCustomerCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$maskedQuoteId\"" + ); + $this->graphQlQuery($query); + } + + /** + * Generates GraphQl query for retrieving cart totals + * + * @param string $maskedQuoteId + * @return string + */ + private function getQuery(string $maskedQuoteId): string + { + return <<<QUERY +{ + cart(cart_id: "$maskedQuoteId") { + prices { + grand_total { + value, + currency + } + subtotal_including_tax { + value + currency + } + subtotal_excluding_tax { + value + currency + } + subtotal_with_discount_excluding_tax { + value + currency + } + applied_taxes { + label + amount { + value + currency + } + } + } + } +} +QUERY; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CheckoutEndToEndTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CheckoutEndToEndTest.php new file mode 100644 index 0000000000000..bddee2151f5cd --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CheckoutEndToEndTest.php @@ -0,0 +1,435 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Guest; + +use Magento\Quote\Model\QuoteIdMaskFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\Framework\Registry; +use Magento\Quote\Model\ResourceModel\Quote\CollectionFactory as QuoteCollectionFactory; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\ResourceModel\Order\CollectionFactory; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * End to checkout tests for guest + */ +class CheckoutEndToEndTest extends GraphQlAbstract +{ + /** + * @var Registry + */ + private $registry; + + /** + * @var QuoteCollectionFactory + */ + private $quoteCollectionFactory; + + /** + * @var QuoteResource + */ + private $quoteResource; + + /** + * @var QuoteIdMaskFactory + */ + private $quoteIdMaskFactory; + + /** + * @var CollectionFactory + */ + private $orderCollectionFactory; + + /** + * @var OrderRepositoryInterface + */ + private $orderRepository; + + protected function setUp() + { + parent::setUp(); + + $objectManager = Bootstrap::getObjectManager(); + $this->registry = $objectManager->get(Registry::class); + $this->quoteCollectionFactory = $objectManager->get(QuoteCollectionFactory::class); + $this->quoteResource = $objectManager->get(QuoteResource::class); + $this->quoteIdMaskFactory = $objectManager->get(QuoteIdMaskFactory::class); + $this->orderCollectionFactory = $objectManager->get(CollectionFactory::class); + $this->orderRepository = $objectManager->get(OrderRepositoryInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/products_with_layered_navigation_attribute.php + */ + public function testCheckoutWorkflow() + { + $quantity = 2; + + $sku = $this->findProduct(); + $cartId = $this->createEmptyCart(); + $this->setGuestEmailOnCart($cartId); + $this->addProductToCart($cartId, $quantity, $sku); + + $this->setBillingAddress($cartId); + $shippingMethod = $this->setShippingAddress($cartId); + + $paymentMethod = $this->setShippingMethod($cartId, $shippingMethod); + $this->setPaymentMethod($cartId, $paymentMethod); + + $this->placeOrder($cartId); + } + + /** + * @return string + */ + private function findProduct(): string + { + $query = <<<QUERY +{ + products ( + filter: { + sku: { + like:"simple%" + } + } + pageSize: 1 + currentPage: 1 + ) { + items { + sku + } + } +} +QUERY; + $response = $this->graphQlQuery($query); + self::assertArrayHasKey('products', $response); + self::assertArrayHasKey('items', $response['products']); + self::assertCount(1, $response['products']['items']); + + $product = current($response['products']['items']); + self::assertArrayHasKey('sku', $product); + self::assertNotEmpty($product['sku']); + + return $product['sku']; + } + + /** + * @return string + */ + private function createEmptyCart(): string + { + $query = <<<QUERY +mutation { + createEmptyCart +} +QUERY; + $response = $this->graphQlMutation($query); + self::assertArrayHasKey('createEmptyCart', $response); + self::assertNotEmpty($response['createEmptyCart']); + + return $response['createEmptyCart']; + } + + /** + * @param string $cartId + * @return void + */ + private function setGuestEmailOnCart(string $cartId): void + { + $query = <<<QUERY +mutation { + setGuestEmailOnCart( + input: { + cart_id: "{$cartId}" + email: "customer@example.com" + } + ) { + cart { + email + } + } +} +QUERY; + $this->graphQlMutation($query); + } + + /** + * @param string $cartId + * @param float $quantity + * @param string $sku + * @return void + */ + private function addProductToCart(string $cartId, float $quantity, string $sku): void + { + $query = <<<QUERY +mutation { + addSimpleProductsToCart( + input: { + cart_id: "{$cartId}" + cartItems: [ + { + data: { + quantity: {$quantity} + sku: "{$sku}" + } + } + ] + } + ) { + cart { + items { + quantity + product { + sku + } + } + } + } +} +QUERY; + $this->graphQlMutation($query); + } + + /** + * @param string $cartId + * @param array $auth + * @return array + */ + private function setBillingAddress(string $cartId): void + { + $query = <<<QUERY +mutation { + setBillingAddressOnCart( + input: { + cart_id: "{$cartId}" + billing_address: { + address: { + firstname: "test firstname" + lastname: "test lastname" + company: "test company" + street: ["test street 1", "test street 2"] + city: "test city" + postcode: "887766" + telephone: "88776655" + region: "TX" + country_code: "US" + save_in_address_book: false + } + } + } + ) { + cart { + billing_address { + __typename + } + } + } +} +QUERY; + $this->graphQlMutation($query); + } + + /** + * @param string $cartId + * @return array + */ + private function setShippingAddress(string $cartId): array + { + $query = <<<QUERY +mutation { + setShippingAddressesOnCart( + input: { + cart_id: "$cartId" + shipping_addresses: [ + { + address: { + firstname: "test firstname" + lastname: "test lastname" + company: "test company" + street: ["test street 1", "test street 2"] + city: "test city" + region: "TX" + postcode: "887766" + country_code: "US" + telephone: "88776655" + save_in_address_book: false + } + } + ] + } + ) { + cart { + shipping_addresses { + available_shipping_methods { + carrier_code + method_code + amount + } + } + } + } +} +QUERY; + $response = $this->graphQlMutation($query); + self::assertArrayHasKey('setShippingAddressesOnCart', $response); + self::assertArrayHasKey('cart', $response['setShippingAddressesOnCart']); + self::assertArrayHasKey('shipping_addresses', $response['setShippingAddressesOnCart']['cart']); + self::assertCount(1, $response['setShippingAddressesOnCart']['cart']['shipping_addresses']); + + $shippingAddress = current($response['setShippingAddressesOnCart']['cart']['shipping_addresses']); + self::assertArrayHasKey('available_shipping_methods', $shippingAddress); + self::assertCount(1, $shippingAddress['available_shipping_methods']); + + $availableShippingMethod = current($shippingAddress['available_shipping_methods']); + self::assertArrayHasKey('carrier_code', $availableShippingMethod); + self::assertNotEmpty($availableShippingMethod['carrier_code']); + + self::assertArrayHasKey('method_code', $availableShippingMethod); + self::assertNotEmpty($availableShippingMethod['method_code']); + + self::assertArrayHasKey('amount', $availableShippingMethod); + self::assertNotEmpty($availableShippingMethod['amount']); + + return $availableShippingMethod; + } + + /** + * @param string $cartId + * @param array $method + * @return array + */ + private function setShippingMethod(string $cartId, array $method): array + { + $query = <<<QUERY +mutation { + setShippingMethodsOnCart(input: { + cart_id: "{$cartId}", + shipping_methods: [ + { + carrier_code: "{$method['carrier_code']}" + method_code: "{$method['method_code']}" + } + ] + }) { + cart { + available_payment_methods { + code + title + } + } + } +} +QUERY; + $response = $this->graphQlMutation($query); + self::assertArrayHasKey('setShippingMethodsOnCart', $response); + self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); + self::assertArrayHasKey('available_payment_methods', $response['setShippingMethodsOnCart']['cart']); + self::assertCount(1, $response['setShippingMethodsOnCart']['cart']['available_payment_methods']); + + $availablePaymentMethod = current($response['setShippingMethodsOnCart']['cart']['available_payment_methods']); + self::assertArrayHasKey('code', $availablePaymentMethod); + self::assertNotEmpty($availablePaymentMethod['code']); + self::assertArrayHasKey('title', $availablePaymentMethod); + self::assertNotEmpty($availablePaymentMethod['title']); + + return $availablePaymentMethod; + } + + /** + * @param string $cartId + * @param array $method + * @return void + */ + private function setPaymentMethod(string $cartId, array $method): void + { + $query = <<<QUERY +mutation { + setPaymentMethodOnCart( + input: { + cart_id: "{$cartId}" + payment_method: { + code: "{$method['code']}" + } + } + ) { + cart { + selected_payment_method { + code + } + } + } +} +QUERY; + $this->graphQlMutation($query); + } + + /** + * @param string $cartId + * @return void + */ + private function placeOrder(string $cartId): void + { + $query = <<<QUERY +mutation { + placeOrder( + input: { + cart_id: "{$cartId}" + } + ) { + order { + order_id + } + } +} +QUERY; + $response = $this->graphQlMutation($query); + self::assertArrayHasKey('placeOrder', $response); + self::assertArrayHasKey('order', $response['placeOrder']); + self::assertArrayHasKey('order_id', $response['placeOrder']['order']); + self::assertNotEmpty($response['placeOrder']['order']['order_id']); + } + + public function tearDown() + { + $this->deleteQuote(); + $this->deleteOrder(); + parent::tearDown(); + } + + /** + * @return void + */ + private function deleteQuote(): void + { + $quoteCollection = $this->quoteCollectionFactory->create(); + foreach ($quoteCollection as $quote) { + $this->quoteResource->delete($quote); + + $quoteIdMask = $this->quoteIdMaskFactory->create(); + $quoteIdMask->setQuoteId($quote->getId()) + ->delete(); + } + } + + /** + * @return void + */ + private function deleteOrder() + { + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', true); + + $orderCollection = $this->orderCollectionFactory->create(); + foreach ($orderCollection as $order) { + $this->orderRepository->delete($order); + } + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', false); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CreateEmptyCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CreateEmptyCartTest.php index fe46a1edcdf1a..6ed91d21f0ae2 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CreateEmptyCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CreateEmptyCartTest.php @@ -7,9 +7,8 @@ namespace Magento\GraphQl\Quote\Guest; -use Magento\Quote\Model\QuoteFactory; -use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface; use Magento\Quote\Model\QuoteIdMaskFactory; +use Magento\Quote\Model\ResourceModel\Quote\CollectionFactory as QuoteCollectionFactory; use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -26,50 +25,38 @@ class CreateEmptyCartTest extends GraphQlAbstract private $guestCartRepository; /** - * @var QuoteResource - */ - private $quoteResource; - - /** - * @var QuoteFactory + * @var QuoteCollectionFactory */ - private $quoteFactory; + private $quoteCollectionFactory; /** - * @var MaskedQuoteIdToQuoteIdInterface + * @var QuoteResource */ - private $maskedQuoteIdToQuoteId; + private $quoteResource; /** * @var QuoteIdMaskFactory */ private $quoteIdMaskFactory; - /** - * @var string - */ - private $maskedQuoteId; - protected function setUp() { $objectManager = Bootstrap::getObjectManager(); $this->guestCartRepository = $objectManager->get(GuestCartRepositoryInterface::class); + $this->quoteCollectionFactory = $objectManager->get(QuoteCollectionFactory::class); $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quoteFactory = $objectManager->get(QuoteFactory::class); - $this->maskedQuoteIdToQuoteId = $objectManager->get(MaskedQuoteIdToQuoteIdInterface::class); $this->quoteIdMaskFactory = $objectManager->get(QuoteIdMaskFactory::class); } public function testCreateEmptyCart() { $query = $this->getQuery(); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('createEmptyCart', $response); self::assertNotEmpty($response['createEmptyCart']); $guestCart = $this->guestCartRepository->get($response['createEmptyCart']); - $this->maskedQuoteId = $response['createEmptyCart']; self::assertNotNull($guestCart->getId()); self::assertNull($guestCart->getCustomer()->getId()); @@ -83,7 +70,7 @@ public function testCreateEmptyCartWithNotDefaultStore() { $query = $this->getQuery(); $headerMap = ['Store' => 'fixture_second_store']; - $response = $this->graphQlQuery($query, [], '', $headerMap); + $response = $this->graphQlMutation($query, [], '', $headerMap); self::assertArrayHasKey('createEmptyCart', $response); self::assertNotEmpty($response['createEmptyCart']); @@ -96,6 +83,65 @@ public function testCreateEmptyCartWithNotDefaultStore() self::assertSame('fixture_second_store', $guestCart->getStore()->getCode()); } + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testCreateEmptyCartWithPredefinedCartId() + { + $predefinedCartId = '572cda51902b5b517c0e1a2b2fd004b4'; + + $query = <<<QUERY +mutation { + createEmptyCart (input: {cart_id: "{$predefinedCartId}"}) +} +QUERY; + $response = $this->graphQlMutation($query); + + self::assertArrayHasKey('createEmptyCart', $response); + self::assertEquals($predefinedCartId, $response['createEmptyCart']); + + $guestCart = $this->guestCartRepository->get($response['createEmptyCart']); + self::assertNotNull($guestCart->getId()); + self::assertNull($guestCart->getCustomer()->getId()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * + * @expectedException \Exception + * @expectedExceptionMessage Cart with ID "572cda51902b5b517c0e1a2b2fd004b4" already exists. + */ + public function testCreateEmptyCartIfPredefinedCartIdAlreadyExists() + { + $predefinedCartId = '572cda51902b5b517c0e1a2b2fd004b4'; + + $query = <<<QUERY +mutation { + createEmptyCart (input: {cart_id: "{$predefinedCartId}"}) +} +QUERY; + $this->graphQlMutation($query); + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * + * @expectedException \Exception + * @expectedExceptionMessage Cart ID length should to be 32 symbols. + */ + public function testCreateEmptyCartWithWrongPredefinedCartId() + { + $predefinedCartId = '572'; + + $query = <<<QUERY +mutation { + createEmptyCart (input: {cart_id: "{$predefinedCartId}"}) +} +QUERY; + $this->graphQlMutation($query); + } + /** * @return string */ @@ -110,15 +156,12 @@ private function getQuery(): string public function tearDown() { - if (null !== $this->maskedQuoteId) { - $quoteId = $this->maskedQuoteIdToQuoteId->execute($this->maskedQuoteId); - - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $quoteId); + $quoteCollection = $this->quoteCollectionFactory->create(); + foreach ($quoteCollection as $quote) { $this->quoteResource->delete($quote); $quoteIdMask = $this->quoteIdMaskFactory->create(); - $quoteIdMask->setQuoteId($quoteId) + $quoteIdMask->setQuoteId($quote->getId()) ->delete(); } parent::tearDown(); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartEmailTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartEmailTest.php new file mode 100644 index 0000000000000..8c6ecd075049f --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartEmailTest.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Guest; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for getting email from cart + */ +class GetCartEmailTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/set_guest_email.php + */ + public function testGetCartEmail() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query); + + $this->assertArrayHasKey('cart', $response); + $this->assertArrayHasKey('email', $response['cart']); + $this->assertEquals('guest@example.com', $response['cart']['email']); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" + */ + public function testGetCartEmailFromNonExistentCart() + { + $maskedQuoteId = 'non_existent_masked_id'; + $query = $this->getQuery($maskedQuoteId); + + $this->graphQlQuery($query); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + */ + public function testGetCartEmailFromCustomerCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"{$maskedQuoteId}\"" + ); + $this->graphQlQuery($query); + } + + /** + * @param string $maskedQuoteId + * @return string + */ + private function getQuery(string $maskedQuoteId): string + { + return <<<QUERY +{ + cart(cart_id:"$maskedQuoteId") { + email + } +} +QUERY; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php index 832e15058a4ee..b35d689af7d20 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php @@ -46,11 +46,11 @@ public function testGetCart() self::assertCount(2, $response['cart']['items']); self::assertNotEmpty($response['cart']['items'][0]['id']); - self::assertEquals(2, $response['cart']['items'][0]['qty']); + self::assertEquals(2, $response['cart']['items'][0]['quantity']); self::assertEquals('simple_product', $response['cart']['items'][0]['product']['sku']); self::assertNotEmpty($response['cart']['items'][1]['id']); - self::assertEquals(2, $response['cart']['items'][1]['qty']); + self::assertEquals(2, $response['cart']['items'][1]['quantity']); self::assertEquals('virtual-product', $response['cart']['items'][1]['product']['sku']); } @@ -133,7 +133,7 @@ public function testGetCartWithWrongStore() * @magentoApiDataFixture Magento/Checkout/_files/active_quote_guest_not_default_store.php * * @expectedException \Exception - * @expectedExceptionMessage Store code not_existing_store does not exist + * @expectedExceptionMessage Requested store is not found */ public function testGetCartWithNotExistingStore() { @@ -156,7 +156,7 @@ private function getQuery(string $maskedQuoteId): string cart(cart_id: "{$maskedQuoteId}") { items { id - qty + quantity product { sku } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php index bfdecca782319..bd684a950b590 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php @@ -56,6 +56,28 @@ public function testGetSelectedShippingMethod() self::assertArrayHasKey('method_code', $shippingAddress['selected_shipping_method']); self::assertEquals('flatrate', $shippingAddress['selected_shipping_method']['method_code']); + + self::assertArrayHasKey('carrier_title', $shippingAddress['selected_shipping_method']); + self::assertEquals('Flat Rate', $shippingAddress['selected_shipping_method']['carrier_title']); + + self::assertArrayHasKey('method_title', $shippingAddress['selected_shipping_method']); + self::assertEquals('Fixed', $shippingAddress['selected_shipping_method']['method_title']); + + self::assertArrayHasKey('amount', $shippingAddress['selected_shipping_method']); + $amount = $shippingAddress['selected_shipping_method']['amount']; + + self::assertArrayHasKey('value', $amount); + self::assertEquals(10, $amount['value']); + self::assertArrayHasKey('currency', $amount); + self::assertEquals('USD', $amount['currency']); + + self::assertArrayHasKey('base_amount', $shippingAddress['selected_shipping_method']); + $baseAmount = $shippingAddress['selected_shipping_method']['base_amount']; + + self::assertArrayHasKey('value', $baseAmount); + self::assertEquals(10, $baseAmount['value']); + self::assertArrayHasKey('currency', $baseAmount); + self::assertEquals('USD', $baseAmount['currency']); } /** @@ -100,8 +122,10 @@ public function testGetGetSelectedShippingMethodIfShippingMethodIsNotSet() self::assertNull($shippingAddress['selected_shipping_method']['carrier_code']); self::assertNull($shippingAddress['selected_shipping_method']['method_code']); - self::assertNull($shippingAddress['selected_shipping_method']['label']); + self::assertNull($shippingAddress['selected_shipping_method']['carrier_title']); + self::assertNull($shippingAddress['selected_shipping_method']['method_title']); self::assertNull($shippingAddress['selected_shipping_method']['amount']); + self::assertNull($shippingAddress['selected_shipping_method']['base_amount']); } /** @@ -128,8 +152,16 @@ private function getQuery(string $maskedQuoteId): string selected_shipping_method { carrier_code method_code - label - amount + carrier_title + method_title + amount { + value + currency + } + base_amount { + value + currency + } } } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedBillingAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedBillingAddressTest.php index d592443aed499..48bff73226894 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedBillingAddressTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedBillingAddressTest.php @@ -63,7 +63,7 @@ public function testGeSpecifiedBillingAddress() 'label' => 'US', ], 'telephone' => '3468676', - 'address_type' => 'BILLING', + '__typename' => 'BillingCartAddress', ]; self::assertEquals($expectedBillingAddressData, $response['cart']['billing_address']); } @@ -100,7 +100,7 @@ public function testGeSpecifiedBillingAddressIfBillingAddressIsNotSet() 'label' => null, ], 'telephone' => null, - 'address_type' => 'BILLING', + '__typename' => 'BillingCartAddress', ]; self::assertEquals($expectedBillingAddressData, $response['cart']['billing_address']); } @@ -161,7 +161,7 @@ private function getQuery(string $maskedQuoteId): string label } telephone - address_type + __typename } } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/PlaceOrderTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/PlaceOrderTest.php new file mode 100644 index 0000000000000..30ad69eada29d --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/PlaceOrderTest.php @@ -0,0 +1,273 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Guest; + +use Magento\Framework\Registry; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\ResourceModel\Order\CollectionFactory; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for placing an order for guest + */ +class PlaceOrderTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @var CollectionFactory + */ + private $orderCollectionFactory; + + /** + * @var OrderRepositoryInterface + */ + private $orderRepository; + + /** + * @var Registry + */ + private $registry; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->orderCollectionFactory = $objectManager->get(CollectionFactory::class); + $this->orderRepository = $objectManager->get(OrderRepositoryInterface::class); + $this->registry = Bootstrap::getObjectManager()->get(Registry::class); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/set_guest_email.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php + */ + public function testPlaceOrder() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlMutation($query); + + self::assertArrayHasKey('placeOrder', $response); + self::assertArrayHasKey('order_id', $response['placeOrder']['order']); + self::assertEquals($reservedOrderId, $response['placeOrder']['order']['order_id']); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php + */ + public function testPlaceOrderWithNoEmail() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage("Guest email for cart is missing. Please enter"); + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/set_guest_email.php + */ + public function testPlaceOrderWithNoItemsInCart() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage( + 'Unable to place order: A server error stopped your order from being placed. ' . + 'Please try to place your order again' + ); + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/set_guest_email.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testPlaceOrderWithNoShippingAddress() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage( + 'Unable to place order: Some addresses can\'t be used due to the configurations for specific countries' + ); + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/set_guest_email.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + */ + public function testPlaceOrderWithNoShippingMethod() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage( + 'Unable to place order: The shipping method is missing. Select the shipping method and try again' + ); + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/set_guest_email.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + */ + public function testPlaceOrderWithNoBillingAddress() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessageRegExp( + '/Unable to place order: Please check the billing address information*/' + ); + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/set_guest_email.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + */ + public function testPlaceOrderWithNoPaymentMethod() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage('Unable to place order: Enter a valid payment method and try again'); + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/set_guest_email.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/set_simple_product_out_of_stock.php + */ + public function testPlaceOrderWithOutOfStockProduct() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage('Unable to place order: Some of the products are out of stock'); + $this->graphQlMutation($query); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/three_customers.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php + */ + public function testPlaceOrderOfCustomerCart() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessageRegExp('/The current user cannot perform operations on cart*/'); + $this->graphQlMutation($query); + } + + /** + * @param string $maskedQuoteId + * @return string + */ + private function getQuery(string $maskedQuoteId): string + { + return <<<QUERY +mutation { + placeOrder(input: {cart_id: "{$maskedQuoteId}"}) { + order { + order_id + } + } +} +QUERY; + } + + /** + * @inheritdoc + */ + public function tearDown() + { + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', true); + + $orderCollection = $this->orderCollectionFactory->create(); + foreach ($orderCollection as $order) { + $this->orderRepository->delete($order); + } + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', false); + + parent::tearDown(); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveCouponFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveCouponFromCartTest.php index edb5f9cbf267c..5adb6ce65db6f 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveCouponFromCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveCouponFromCartTest.php @@ -42,7 +42,7 @@ public function testRemoveCouponFromCart() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('removeCouponFromCart', $response); self::assertNull($response['removeCouponFromCart']['cart']['applied_coupon']['code']); @@ -57,7 +57,7 @@ public function testRemoveCouponFromNonExistentCart() $maskedQuoteId = 'non_existent_masked_id'; $query = $this->getQuery($maskedQuoteId); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -70,7 +70,7 @@ public function testRemoveCouponFromEmptyCart() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -83,7 +83,7 @@ public function testRemoveCouponFromCartIfCouponWasNotSet() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('removeCouponFromCart', $response); self::assertNull($response['removeCouponFromCart']['cart']['applied_coupon']['code']); @@ -104,7 +104,7 @@ public function testRemoveCouponFromCustomerCart() $query = $this->getQuery($maskedQuoteId); self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php index 294230c658aa0..77a057e52f686 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php @@ -47,7 +47,7 @@ public function testRemoveItemFromCart() $itemId = $this->getQuoteItemIdByReservedQuoteIdAndSku->execute('test_quote', 'simple_product'); $query = $this->getQuery($maskedQuoteId, $itemId); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); $this->assertArrayHasKey('removeItemFromCart', $response); $this->assertArrayHasKey('cart', $response['removeItemFromCart']); @@ -61,7 +61,7 @@ public function testRemoveItemFromCart() public function testRemoveItemFromNonExistentCart() { $query = $this->getQuery('non_existent_masked_id', 1); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -77,7 +77,7 @@ public function testRemoveNonExistentItem() $this->expectExceptionMessage("Cart doesn't contain the {$notExistentItemId} item."); $query = $this->getQuery($maskedQuoteId, $notExistentItemId); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -96,14 +96,14 @@ public function testUpdateWithMissedItemRequiredParameters(string $input, string ) { cart { items { - qty + quantity } } } } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -141,7 +141,7 @@ public function testRemoveItemIfItemIsNotBelongToCart() $this->expectExceptionMessage("Cart doesn't contain the {$secondQuoteItemId} item."); $query = $this->getQuery($firstQuoteMaskedId, $secondQuoteItemId); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -159,7 +159,7 @@ public function testRemoveItemFromCustomerCart() $this->expectExceptionMessage("The current user cannot perform operations on cart \"$customerQuoteMaskedId\""); $query = $this->getQuery($customerQuoteMaskedId, $customerQuoteItemId); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -179,7 +179,7 @@ private function getQuery(string $maskedQuoteId, int $itemId): string ) { cart { items { - qty + quantity } } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php index d253b6f39b0db..d8d95040eea45 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php @@ -70,13 +70,13 @@ public function testSetNewBillingAddress() code label } - address_type + __typename } } } } QUERY; - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']); $cartResponse = $response['setBillingAddressOnCart']['cart']; @@ -129,7 +129,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() code label } - address_type + __typename } shipping_addresses { firstname @@ -143,13 +143,13 @@ public function testSetNewBillingAddressWithUseForShippingParameter() code label } - address_type + __typename } } } } QUERY; - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']); $cartResponse = $response['setBillingAddressOnCart']['cart']; @@ -158,7 +158,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() self::assertArrayHasKey('shipping_addresses', $cartResponse); $shippingAddressResponse = current($cartResponse['shipping_addresses']); $this->assertNewAddressFields($billingAddressResponse); - $this->assertNewAddressFields($shippingAddressResponse, 'SHIPPING'); + $this->assertNewAddressFields($shippingAddressResponse, 'ShippingCartAddress'); } /** @@ -203,7 +203,7 @@ public function testSetBillingAddressToCustomerCart() $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -237,7 +237,7 @@ public function testSetBillingAddressFromAddressBook() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -276,7 +276,7 @@ public function testSetBillingAddressOnNonExistentCart() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -310,7 +310,25 @@ public function testSetBillingAddressWithoutRequiredParameters(string $input, st } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query); + $this->graphQlMutation($query); + } + + /** + * @return array + */ + public function dataProviderSetWithoutRequiredParameters(): array + { + return [ + 'missed_billing_address' => [ + 'cart_id: "cart_id_value"', + 'Field SetBillingAddressOnCartInput.billing_address of required type BillingAddressInput!' + . ' was not provided.', + ], + 'missed_cart_id' => [ + 'billing_address: {}', + 'Required parameter "cart_id" is missing' + ] + ]; } /** @@ -353,25 +371,7 @@ public function testSetNewBillingAddressRedundantStreetLine() QUERY; self::expectExceptionMessage('"Street Address" cannot contain more than 2 lines.'); - $this->graphQlQuery($query); - } - - /** - * @return array - */ - public function dataProviderSetWithoutRequiredParameters(): array - { - return [ - 'missed_billing_address' => [ - 'cart_id: "cart_id_value"', - 'Field SetBillingAddressOnCartInput.billing_address of required type BillingAddressInput!' - . ' was not provided.', - ], - 'missed_cart_id' => [ - 'billing_address: {}', - 'Required parameter "cart_id" is missing' - ] - ]; + $this->graphQlMutation($query); } /** @@ -380,7 +380,7 @@ public function dataProviderSetWithoutRequiredParameters(): array * @param array $addressResponse * @param string $addressType */ - private function assertNewAddressFields(array $addressResponse, string $addressType = 'BILLING'): void + private function assertNewAddressFields(array $addressResponse, string $addressType = 'BillingCartAddress'): void { $assertionMap = [ ['response_field' => 'firstname', 'expected_value' => 'test firstname'], @@ -391,7 +391,7 @@ private function assertNewAddressFields(array $addressResponse, string $addressT ['response_field' => 'postcode', 'expected_value' => '887766'], ['response_field' => 'telephone', 'expected_value' => '88776655'], ['response_field' => 'country', 'expected_value' => ['code' => 'US', 'label' => 'US']], - ['response_field' => 'address_type', 'expected_value' => $addressType] + ['response_field' => '__typename', 'expected_value' => $addressType] ]; $this->assertResponseFields($addressResponse, $assertionMap); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetGuestEmailOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetGuestEmailOnCartTest.php new file mode 100644 index 0000000000000..b877dccdeba37 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetGuestEmailOnCartTest.php @@ -0,0 +1,141 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Guest; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for setGuestEmailOnCart mutation + */ +class SetGuestEmailOnCartTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + */ + public function testSetGuestEmailOnCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $email = 'some@user.com'; + + $query = $this->getQuery($maskedQuoteId, $email); + $response = $this->graphQlMutation($query); + + $this->assertArrayHasKey('setGuestEmailOnCart', $response); + $this->assertArrayHasKey('cart', $response['setGuestEmailOnCart']); + $this->assertEquals($email, $response['setGuestEmailOnCart']['cart']['email']); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + */ + public function testSetGuestEmailOnCustomerCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $email = 'some@user.com'; + + $query = $this->getQuery($maskedQuoteId, $email); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$maskedQuoteId\"" + ); + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * + * @dataProvider incorrectEmailDataProvider + * @param string $email + * @param string $exceptionMessage + */ + public function testSetGuestEmailOnCartWithIncorrectEmail( + string $email, + string $exceptionMessage + ) { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId, $email); + $this->expectExceptionMessage($exceptionMessage); + $this->graphQlMutation($query); + } + + /** + * @return array + */ + public function incorrectEmailDataProvider(): array + { + return [ + 'wrong_email' => ['some', 'Invalid email format'], + 'no_email' => ['', 'Required parameter "email" is missing'], + ]; + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" + */ + public function testSetGuestEmailOnNonExistentCart() + { + $maskedQuoteId = 'non_existent_masked_id'; + $email = 'some@user.com'; + + $query = $this->getQuery($maskedQuoteId, $email); + $this->graphQlMutation($query); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Required parameter "cart_id" is missing + */ + public function testSetGuestEmailWithEmptyCartId() + { + $maskedQuoteId = ''; + $email = 'some@user.com'; + + $query = $this->getQuery($maskedQuoteId, $email); + $this->graphQlMutation($query); + } + + /** + * Returns GraphQl mutation query for setting email address for a guest + * + * @param string $maskedQuoteId + * @param string $email + * @return string + */ + private function getQuery(string $maskedQuoteId, string $email): string + { + return <<<QUERY +mutation { + setGuestEmailOnCart(input: { + cart_id: "$maskedQuoteId" + email: "$email" + }) { + cart { + email + } + } +} +QUERY; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflinePaymentMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflinePaymentMethodsOnCartTest.php new file mode 100644 index 0000000000000..7ec3148ea85a0 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflinePaymentMethodsOnCartTest.php @@ -0,0 +1,157 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Guest; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\OfflinePayments\Model\Banktransfer; +use Magento\OfflinePayments\Model\Cashondelivery; +use Magento\OfflinePayments\Model\Checkmo; +use Magento\OfflinePayments\Model\Purchaseorder; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for setting offline payment methods on cart + */ +class SetOfflinePaymentMethodsOnCartTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + * + * @param string $methodCode + * @param string $methodTitle + * @dataProvider offlinePaymentMethodDataProvider + */ + public function testSetOfflinePaymentMethod(string $methodCode, string $methodTitle) + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId, $methodCode); + $response = $this->graphQlMutation($query); + + self::assertArrayHasKey('setPaymentMethodOnCart', $response); + self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); + self::assertArrayHasKey('selected_payment_method', $response['setPaymentMethodOnCart']['cart']); + + $selectedPaymentMethod = $response['setPaymentMethodOnCart']['cart']['selected_payment_method']; + self::assertArrayHasKey('code', $selectedPaymentMethod); + self::assertEquals($methodCode, $selectedPaymentMethod['code']); + + self::assertArrayHasKey('title', $selectedPaymentMethod); + self::assertEquals($methodTitle, $selectedPaymentMethod['title']); + } + + /** + * @return array + */ + public function offlinePaymentMethodDataProvider(): array + { + return [ + 'check_mo' => [Checkmo::PAYMENT_METHOD_CHECKMO_CODE, 'Check / Money order'], + 'bank_transfer' => [Banktransfer::PAYMENT_METHOD_BANKTRANSFER_CODE, 'Bank Transfer Payment'], + 'cash_on_delivery' => [Cashondelivery::PAYMENT_METHOD_CASHONDELIVERY_CODE, 'Cash On Delivery'], + ]; + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + */ + public function testSetPurchaseOrderPaymentMethod() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $methodCode = Purchaseorder::PAYMENT_METHOD_PURCHASEORDER_CODE; + $methodTitle = 'Purchase Order'; + $poNumber = 'abc123'; + + $query = <<<QUERY +mutation { + setPaymentMethodOnCart(input: { + cart_id: "{$maskedQuoteId}", + payment_method: { + code: "{$methodCode}" + purchase_order_number: "{$poNumber}" + } + }) { + cart { + selected_payment_method { + code + title + purchase_order_number + } + } + } +} +QUERY; + + $response = $this->graphQlMutation($query); + + self::assertArrayHasKey('setPaymentMethodOnCart', $response); + self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); + self::assertArrayHasKey('selected_payment_method', $response['setPaymentMethodOnCart']['cart']); + + $selectedPaymentMethod = $response['setPaymentMethodOnCart']['cart']['selected_payment_method']; + self::assertArrayHasKey('code', $selectedPaymentMethod); + self::assertEquals($methodCode, $selectedPaymentMethod['code']); + + self::assertArrayHasKey('title', $selectedPaymentMethod); + self::assertEquals($methodTitle, $selectedPaymentMethod['title']); + + self::assertArrayHasKey('purchase_order_number', $selectedPaymentMethod); + self::assertEquals($poNumber, $selectedPaymentMethod['purchase_order_number']); + } + + /** + * @param string $maskedQuoteId + * @param string $methodCode + * @return string + */ + private function getQuery( + string $maskedQuoteId, + string $methodCode + ) : string { + return <<<QUERY +mutation { + setPaymentMethodOnCart(input: { + cart_id: "{$maskedQuoteId}", + payment_method: { + code: "{$methodCode}" + } + }) { + cart { + selected_payment_method { + code + title + } + } + } +} +QUERY; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflineShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflineShippingMethodsOnCartTest.php index 477c93efd31d0..ff2c5f2e6b528 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflineShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflineShippingMethodsOnCartTest.php @@ -8,7 +8,6 @@ namespace Magento\GraphQl\Quote\Guest; use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; -use Magento\GraphQl\Quote\GetQuoteShippingAddressIdByReservedQuoteId; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -22,11 +21,6 @@ class SetOfflineShippingMethodsOnCartTest extends GraphQlAbstract */ private $getMaskedQuoteIdByReservedOrderId; - /** - * @var GetQuoteShippingAddressIdByReservedQuoteId - */ - private $getQuoteShippingAddressIdByReservedQuoteId; - /** * @inheritdoc */ @@ -34,9 +28,6 @@ protected function setUp() { $objectManager = Bootstrap::getObjectManager(); $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); - $this->getQuoteShippingAddressIdByReservedQuoteId = $objectManager->get( - GetQuoteShippingAddressIdByReservedQuoteId::class - ); } /** @@ -49,22 +40,29 @@ protected function setUp() * * @param string $carrierCode * @param string $methodCode - * @param float $amount - * @param string $label + * @param string $carrierTitle + * @param string $methodTitle + * @param array $amount + * @param array $baseAmount + * @throws \Magento\Framework\Exception\NoSuchEntityException * @dataProvider offlineShippingMethodDataProvider */ - public function testSetOfflineShippingMethod(string $carrierCode, string $methodCode, float $amount, string $label) - { + public function testSetOfflineShippingMethod( + string $carrierCode, + string $methodCode, + string $carrierTitle, + string $methodTitle, + array $amount, + array $baseAmount + ) { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); $query = $this->getQuery( $maskedQuoteId, $methodCode, - $carrierCode, - $quoteAddressId + $carrierCode ); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('setShippingMethodsOnCart', $response); self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); @@ -80,11 +78,17 @@ public function testSetOfflineShippingMethod(string $carrierCode, string $method self::assertArrayHasKey('method_code', $shippingAddress['selected_shipping_method']); self::assertEquals($methodCode, $shippingAddress['selected_shipping_method']['method_code']); + self::assertArrayHasKey('carrier_title', $shippingAddress['selected_shipping_method']); + self::assertEquals($carrierTitle, $shippingAddress['selected_shipping_method']['carrier_title']); + + self::assertArrayHasKey('method_title', $shippingAddress['selected_shipping_method']); + self::assertEquals($methodTitle, $shippingAddress['selected_shipping_method']['method_title']); + self::assertArrayHasKey('amount', $shippingAddress['selected_shipping_method']); self::assertEquals($amount, $shippingAddress['selected_shipping_method']['amount']); - self::assertArrayHasKey('label', $shippingAddress['selected_shipping_method']); - self::assertEquals($label, $shippingAddress['selected_shipping_method']['label']); + self::assertArrayHasKey('base_amount', $shippingAddress['selected_shipping_method']); + self::assertEquals($baseAmount, $shippingAddress['selected_shipping_method']['base_amount']); } /** @@ -93,9 +97,30 @@ public function testSetOfflineShippingMethod(string $carrierCode, string $method public function offlineShippingMethodDataProvider(): array { return [ - 'flatrate_flatrate' => ['flatrate', 'flatrate', 10, 'Flat Rate - Fixed'], - 'tablerate_bestway' => ['tablerate', 'bestway', 10, 'Best Way - Table Rate'], - 'freeshipping_freeshipping' => ['freeshipping', 'freeshipping', 0, 'Free Shipping - Free'], + 'flatrate_flatrate' => [ + 'flatrate', + 'flatrate', + 'Flat Rate', + 'Fixed', + ['value' => 10, 'currency' => 'USD'], + ['value' => 10, 'currency' => 'USD'], + ], + 'tablerate_bestway' => [ + 'tablerate', + 'bestway', + 'Best Way', + 'Table Rate', + ['value' => 10, 'currency' => 'USD'], + ['value' => 10, 'currency' => 'USD'], + ], + 'freeshipping_freeshipping' => [ + 'freeshipping', + 'freeshipping', + 'Free Shipping', + 'Free', + ['value' => 0, 'currency' => 'USD'], + ['value' => 0, 'currency' => 'USD'], + ], ]; } @@ -103,14 +128,12 @@ public function offlineShippingMethodDataProvider(): array * @param string $maskedQuoteId * @param string $shippingMethodCode * @param string $shippingCarrierCode - * @param int $shippingAddressId * @return string */ private function getQuery( string $maskedQuoteId, string $shippingMethodCode, - string $shippingCarrierCode, - int $shippingAddressId + string $shippingCarrierCode ): string { return <<<QUERY mutation { @@ -118,7 +141,6 @@ private function getQuery( { cart_id: "$maskedQuoteId", shipping_methods: [{ - cart_address_id: $shippingAddressId carrier_code: "$shippingCarrierCode" method_code: "$shippingMethodCode" }] @@ -128,8 +150,16 @@ private function getQuery( selected_shipping_method { carrier_code method_code - amount - label + carrier_title + method_title + amount { + value + currency + } + base_amount { + value + currency + } } } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php index 879d0fd917291..1b2ceecd213ab 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php @@ -7,9 +7,11 @@ namespace Magento\GraphQl\Quote\Guest; +use Exception; use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\OfflinePayments\Model\Cashondelivery; use Magento\OfflinePayments\Model\Checkmo; +use Magento\OfflinePayments\Model\Purchaseorder; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -44,7 +46,7 @@ public function testSetPaymentOnCartWithSimpleProduct() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $methodCode); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('setPaymentMethodOnCart', $response); self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); @@ -57,7 +59,7 @@ public function testSetPaymentOnCartWithSimpleProduct() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * - * @expectedException \Exception + * @expectedException Exception * @expectedExceptionMessage The shipping address is missing. Set the address and try again. */ public function testSetPaymentOnCartWithSimpleProductAndWithoutAddress() @@ -66,7 +68,7 @@ public function testSetPaymentOnCartWithSimpleProductAndWithoutAddress() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $methodCode); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -80,7 +82,7 @@ public function testSetPaymentOnCartWithVirtualProduct() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $methodCode); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('setPaymentMethodOnCart', $response); self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); @@ -94,7 +96,7 @@ public function testSetPaymentOnCartWithVirtualProduct() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php * - * @expectedException \Exception + * @expectedException Exception * @expectedExceptionMessage The requested Payment Method is not available. */ public function testSetNonExistentPaymentMethod() @@ -103,11 +105,11 @@ public function testSetNonExistentPaymentMethod() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $methodCode); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** - * @expectedException \Exception + * @expectedException Exception * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" */ public function testSetPaymentOnNonExistentCart() @@ -116,7 +118,7 @@ public function testSetPaymentOnNonExistentCart() $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE; $query = $this->getQuery($maskedQuoteId, $methodCode); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -137,7 +139,7 @@ public function testSetPaymentMethodToCustomerCart() $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -149,7 +151,7 @@ public function testSetPaymentMethodToCustomerCart() * @param string $input * @param string $message * @dataProvider dataProviderSetPaymentMethodWithoutRequiredParameters - * @throws \Exception + * @throws Exception */ public function testSetPaymentMethodWithoutRequiredParameters(string $input, string $message) { @@ -162,14 +164,14 @@ public function testSetPaymentMethodWithoutRequiredParameters(string $input, str ) { cart { items { - qty + quantity } } } } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -207,7 +209,7 @@ public function testReSetPayment() $methodCode = Cashondelivery::PAYMENT_METHOD_CASHONDELIVERY_CODE; $query = $this->getQuery($maskedQuoteId, $methodCode); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('setPaymentMethodOnCart', $response); self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); @@ -216,6 +218,23 @@ public function testReSetPayment() self::assertEquals($methodCode, $response['setPaymentMethodOnCart']['cart']['selected_payment_method']['code']); } + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @expectedException Exception + * @expectedExceptionMessage The requested Payment Method is not available. + */ + public function testSetDisabledPaymentOnCart() + { + $methodCode = Purchaseorder::PAYMENT_METHOD_PURCHASEORDER_CODE; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId, $methodCode); + $this->graphQlMutation($query); + } + /** * @param string $maskedQuoteId * @param string $methodCode diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPurchaseOrderPaymentMethodOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPurchaseOrderPaymentMethodOnCartTest.php new file mode 100644 index 0000000000000..9dd0382a5dbed --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPurchaseOrderPaymentMethodOnCartTest.php @@ -0,0 +1,147 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Guest; + +use Exception; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\OfflinePayments\Model\Purchaseorder; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for setting Purchase Order payment method on cart by guest + */ +class SetPurchaseOrderPaymentMethodOnCartTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + */ + public function testSetPurchaseOrderPaymentMethodOnCartWithSimpleProduct() + { + $methodCode = Purchaseorder::PAYMENT_METHOD_PURCHASEORDER_CODE; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $purchaseOrderNumber = '123456'; + + $query = <<<QUERY +mutation { + setPaymentMethodOnCart(input: { + cart_id: "$maskedQuoteId" + payment_method: { + code: "$methodCode" + purchase_order_number: "$purchaseOrderNumber" + } + }) { + cart { + selected_payment_method { + code + purchase_order_number + } + } + } +} +QUERY; + $response = $this->graphQlMutation($query); + + self::assertArrayHasKey('setPaymentMethodOnCart', $response); + self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); + self::assertArrayHasKey('selected_payment_method', $response['setPaymentMethodOnCart']['cart']); + self::assertEquals($methodCode, $response['setPaymentMethodOnCart']['cart']['selected_payment_method']['code']); + self::assertEquals( + $purchaseOrderNumber, + $response['setPaymentMethodOnCart']['cart']['selected_payment_method']['purchase_order_number'] + ); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + * + * @expectedException Exception + * @expectedExceptionMessage Purchase order number is a required field. + */ + public function testSetPurchaseOrderPaymentMethodOnCartWithoutPurchaseOrderNumber() + { + $methodCode = Purchaseorder::PAYMENT_METHOD_PURCHASEORDER_CODE; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = <<<QUERY +mutation { + setPaymentMethodOnCart(input: { + cart_id: "$maskedQuoteId" + payment_method: { + code: "$methodCode" + } + }) { + cart { + selected_payment_method { + code + } + } + } +} +QUERY; + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * + * @expectedException Exception + * @expectedExceptionMessage The requested Payment Method is not available. + */ + public function testSetDisabledPurchaseOrderPaymentMethodOnCart() + { + $methodCode = Purchaseorder::PAYMENT_METHOD_PURCHASEORDER_CODE; + $purchaseOrderNumber = '123456'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = <<<QUERY +mutation { + setPaymentMethodOnCart(input: { + cart_id: "$maskedQuoteId" + payment_method: { + code: "$methodCode" + purchase_order_number: "$purchaseOrderNumber" + } + }) { + cart { + selected_payment_method { + code + purchase_order_number + } + } + } +} +QUERY; + $this->graphQlMutation($query); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php index a6981e11799fb..c58098c6c2547 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php @@ -72,13 +72,13 @@ public function testSetNewShippingAddressOnCartWithSimpleProduct() code label } - address_type + __typename } } } } QUERY; - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('cart', $response['setShippingAddressesOnCart']); $cartResponse = $response['setShippingAddressesOnCart']['cart']; @@ -130,7 +130,7 @@ public function testSetNewShippingAddressOnCartWithVirtualProduct() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -166,7 +166,7 @@ public function testSetShippingAddressFromAddressBook() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -205,7 +205,7 @@ public function testSetShippingAddressToCustomerCart() $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -243,7 +243,7 @@ public function testSetNewShippingAddressWithMissedRequiredParameters(string $in } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -287,7 +287,7 @@ public function testSetNewShippingAddressOnCartWithRedundantStreetLine() } QUERY; self::expectExceptionMessage('"Street Address" cannot contain more than 2 lines.'); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -364,7 +364,7 @@ public function testSetMultipleNewShippingAddresses() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -383,7 +383,7 @@ private function assertNewShippingAddressFields(array $shippingAddressResponse): ['response_field' => 'postcode', 'expected_value' => '887766'], ['response_field' => 'telephone', 'expected_value' => '88776655'], ['response_field' => 'country', 'expected_value' => ['code' => 'US', 'label' => 'US']], - ['response_field' => 'address_type', 'expected_value' => 'SHIPPING'] + ['response_field' => '__typename', 'expected_value' => 'ShippingCartAddress'] ]; $this->assertResponseFields($shippingAddressResponse, $assertionMap); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php index 2eac002253ff0..0c2bf1453b547 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php @@ -7,8 +7,8 @@ namespace Magento\GraphQl\Quote\Guest; +use Exception; use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; -use Magento\GraphQl\Quote\GetQuoteShippingAddressIdByReservedQuoteId; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -22,11 +22,6 @@ class SetShippingMethodsOnCartTest extends GraphQlAbstract */ private $getMaskedQuoteIdByReservedOrderId; - /** - * @var GetQuoteShippingAddressIdByReservedQuoteId - */ - private $getQuoteShippingAddressIdByReservedQuoteId; - /** * @inheritdoc */ @@ -34,9 +29,6 @@ protected function setUp() { $objectManager = Bootstrap::getObjectManager(); $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); - $this->getQuoteShippingAddressIdByReservedQuoteId = $objectManager->get( - GetQuoteShippingAddressIdByReservedQuoteId::class - ); } /** @@ -50,15 +42,13 @@ public function testSetShippingMethodOnCartWithSimpleProduct() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $carrierCode = 'flatrate'; $methodCode = 'flatrate'; - $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); $query = $this->getQuery( $maskedQuoteId, $methodCode, - $carrierCode, - $quoteAddressId + $carrierCode ); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('setShippingMethodsOnCart', $response); self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); @@ -69,10 +59,32 @@ public function testSetShippingMethodOnCartWithSimpleProduct() self::assertArrayHasKey('selected_shipping_method', $shippingAddress); self::assertArrayHasKey('carrier_code', $shippingAddress['selected_shipping_method']); - self::assertEquals($carrierCode, $shippingAddress['selected_shipping_method']['carrier_code']); + self::assertEquals('flatrate', $shippingAddress['selected_shipping_method']['carrier_code']); self::assertArrayHasKey('method_code', $shippingAddress['selected_shipping_method']); - self::assertEquals($methodCode, $shippingAddress['selected_shipping_method']['method_code']); + self::assertEquals('flatrate', $shippingAddress['selected_shipping_method']['method_code']); + + self::assertArrayHasKey('carrier_title', $shippingAddress['selected_shipping_method']); + self::assertEquals('Flat Rate', $shippingAddress['selected_shipping_method']['carrier_title']); + + self::assertArrayHasKey('method_title', $shippingAddress['selected_shipping_method']); + self::assertEquals('Fixed', $shippingAddress['selected_shipping_method']['method_title']); + + self::assertArrayHasKey('amount', $shippingAddress['selected_shipping_method']); + $amount = $shippingAddress['selected_shipping_method']['amount']; + + self::assertArrayHasKey('value', $amount); + self::assertEquals(10, $amount['value']); + self::assertArrayHasKey('currency', $amount); + self::assertEquals('USD', $amount['currency']); + + self::assertArrayHasKey('base_amount', $shippingAddress['selected_shipping_method']); + $baseAmount = $shippingAddress['selected_shipping_method']['base_amount']; + + self::assertArrayHasKey('value', $baseAmount); + self::assertEquals(10, $baseAmount['value']); + self::assertArrayHasKey('currency', $baseAmount); + self::assertEquals('USD', $baseAmount['currency']); } /** @@ -85,7 +97,7 @@ public function testSetShippingMethodOnCartWithSimpleProduct() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_virtual_product.php * - * @expectedException \Exception + * @expectedException Exception * @expectedExceptionMessage The shipping address is missing. Set the address and try again. */ public function testSetShippingMethodOnCartWithSimpleProductAndWithoutAddress() @@ -93,15 +105,13 @@ public function testSetShippingMethodOnCartWithSimpleProductAndWithoutAddress() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $carrierCode = 'flatrate'; $methodCode = 'flatrate'; - $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); $query = $this->getQuery( $maskedQuoteId, $methodCode, - $carrierCode, - $quoteAddressId + $carrierCode ); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -117,15 +127,13 @@ public function testReSetShippingMethod() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $carrierCode = 'freeshipping'; $methodCode = 'freeshipping'; - $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); $query = $this->getQuery( $maskedQuoteId, $methodCode, - $carrierCode, - $quoteAddressId + $carrierCode ); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('setShippingMethodsOnCart', $response); self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); @@ -151,13 +159,12 @@ public function testReSetShippingMethod() * @param string $input * @param string $message * @dataProvider dataProviderSetShippingMethodWithWrongParameters - * @throws \Exception + * @throws Exception */ public function testSetShippingMethodWithWrongParameters(string $input, string $message) { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); - $input = str_replace(['cart_id_value', 'cart_address_id_value'], [$maskedQuoteId, $quoteAddressId], $input); + $input = str_replace('cart_id_value', $maskedQuoteId, $input); $query = <<<QUERY mutation { @@ -175,7 +182,7 @@ public function testSetShippingMethodWithWrongParameters(string $input, string $ } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -187,7 +194,6 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array return [ 'missed_cart_id' => [ 'shipping_methods: [{ - cart_address_id: cart_address_id_value carrier_code: "flatrate" method_code: "flatrate" }]', @@ -201,31 +207,14 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array 'cart_id: "cart_id_value" shipping_methods: []', 'Required parameter "shipping_methods" is missing' ], - 'missed_cart_address_id' => [ - 'cart_id: "cart_id_value", shipping_methods: [{ - carrier_code: "flatrate" - method_code: "flatrate" - }]', - 'Required parameter "cart_address_id" is missing.' - ], - 'non_existent_cart_address_id' => [ - 'cart_id: "cart_id_value", shipping_methods: [{ - cart_address_id: -1 - carrier_code: "flatrate" - method_code: "flatrate" - }]', - 'Could not find a cart address with ID "-1"' - ], 'missed_carrier_code' => [ 'cart_id: "cart_id_value", shipping_methods: [{ - cart_address_id: cart_address_id_value method_code: "flatrate" }]', 'Field ShippingMethodInput.carrier_code of required type String! was not provided.' ], 'empty_carrier_code' => [ 'cart_id: "cart_id_value", shipping_methods: [{ - cart_address_id: cart_address_id_value carrier_code: "" method_code: "flatrate" }]', @@ -233,7 +222,6 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array ], 'non_existent_carrier_code' => [ 'cart_id: "cart_id_value", shipping_methods: [{ - cart_address_id: cart_address_id_value carrier_code: "wrong-carrier-code" method_code: "flatrate" }]', @@ -241,14 +229,12 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array ], 'missed_method_code' => [ 'cart_id: "cart_id_value", shipping_methods: [{ - cart_address_id: cart_address_id_value carrier_code: "flatrate" }]', 'Required parameter "method_code" is missing.' ], 'empty_method_code' => [ 'cart_id: "cart_id_value", shipping_methods: [{ - cart_address_id: cart_address_id_value carrier_code: "flatrate" method_code: "" }]', @@ -256,7 +242,6 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array ], 'non_existent_method_code' => [ 'cart_id: "cart_id_value", shipping_methods: [{ - cart_address_id: cart_address_id_value carrier_code: "flatrate" method_code: "wrong-carrier-code" }]', @@ -264,12 +249,18 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array ], 'non_existent_shopping_cart' => [ 'cart_id: "non_existent_masked_id", shipping_methods: [{ - cart_address_id: cart_address_id_value carrier_code: "flatrate" method_code: "flatrate" }]', 'Could not find a cart with ID "non_existent_masked_id"' ], + 'disabled_shipping_method' => [ + 'cart_id: "cart_id_value", shipping_methods: [{ + carrier_code: "freeshipping" + method_code: "freeshipping" + }]', + 'Carrier with such method not found: freeshipping, freeshipping' + ], ]; } @@ -279,13 +270,12 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php - * @expectedException \Exception + * @expectedException Exception * @expectedExceptionMessage You cannot specify multiple shipping methods. */ public function testSetMultipleShippingMethods() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); $query = <<<QUERY mutation { @@ -293,12 +283,10 @@ public function testSetMultipleShippingMethods() cart_id: "{$maskedQuoteId}", shipping_methods: [ { - cart_address_id: {$quoteAddressId} carrier_code: "flatrate" method_code: "flatrate" } { - cart_address_id: {$quoteAddressId} carrier_code: "flatrate" method_code: "flatrate" } @@ -314,7 +302,7 @@ public function testSetMultipleShippingMethods() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -325,66 +313,57 @@ public function testSetMultipleShippingMethods() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php * - * @expectedException \Exception + * @expectedException Exception */ public function testSetShippingMethodToCustomerCart() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $carrierCode = 'flatrate'; $methodCode = 'flatrate'; - $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); $query = $this->getQuery( $maskedQuoteId, $methodCode, - $carrierCode, - $quoteAddressId + $carrierCode ); $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } - + /** - * _security - * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php - * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/quote_with_address.php + * + * @expectedException Exception + * @expectedExceptionMessage The shipping method can't be set for an empty cart. Add an item to cart and try again. */ - public function testSetShippingMethodIfGuestIsNotOwnerOfAddress() + public function testSetShippingMethodOnAnEmptyCart() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $carrierCode = 'flatrate'; $methodCode = 'flatrate'; - $anotherQuoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('guest_quote_with_address'); + $query = $this->getQuery( $maskedQuoteId, $methodCode, - $carrierCode, - $anotherQuoteAddressId - ); - - $this->expectExceptionMessage( - "Cart does not contain address with ID \"{$anotherQuoteAddressId}\"" + $carrierCode ); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** * @param string $maskedQuoteId * @param string $shippingMethodCode * @param string $shippingCarrierCode - * @param int $shippingAddressId * @return string */ private function getQuery( string $maskedQuoteId, string $shippingMethodCode, - string $shippingCarrierCode, - int $shippingAddressId + string $shippingCarrierCode ): string { return <<<QUERY mutation { @@ -392,7 +371,6 @@ private function getQuery( { cart_id: "$maskedQuoteId", shipping_methods: [{ - cart_address_id: $shippingAddressId carrier_code: "$shippingCarrierCode" method_code: "$shippingMethodCode" }] @@ -402,6 +380,16 @@ private function getQuery( selected_shipping_method { carrier_code method_code + carrier_title + method_title + amount { + value + currency + } + base_amount { + value + currency + } } } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/UpdateCartItemsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/UpdateCartItemsTest.php index fca7a4287620b..1e1fb0a176992 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/UpdateCartItemsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/UpdateCartItemsTest.php @@ -51,16 +51,16 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php */ - public function testUpdateCartItemQty() + public function testUpdateCartItemQuantity() { $quote = $this->quoteFactory->create(); $this->quoteResource->load($quote, 'test_order_with_simple_product_without_address', 'reserved_order_id'); $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId()); $itemId = (int)$quote->getItemByProduct($this->productRepository->get('simple'))->getId(); - $qty = 2; + $quantity = 2; - $query = $this->getQuery($maskedQuoteId, $itemId, $qty); - $response = $this->graphQlQuery($query); + $query = $this->getQuery($maskedQuoteId, $itemId, $quantity); + $response = $this->graphQlMutation($query); $this->assertArrayHasKey('updateCartItems', $response); $this->assertArrayHasKey('cart', $response['updateCartItems']); @@ -69,7 +69,7 @@ public function testUpdateCartItemQty() $item = current($responseCart['items']); $this->assertEquals($itemId, $item['id']); - $this->assertEquals($qty, $item['qty']); + $this->assertEquals($quantity, $item['quantity']); } /** @@ -81,10 +81,10 @@ public function testRemoveCartItemIfQuantityIsZero() $this->quoteResource->load($quote, 'test_order_with_simple_product_without_address', 'reserved_order_id'); $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId()); $itemId = (int)$quote->getItemByProduct($this->productRepository->get('simple'))->getId(); - $qty = 0; + $quantity = 0; - $query = $this->getQuery($maskedQuoteId, $itemId, $qty); - $response = $this->graphQlQuery($query); + $query = $this->getQuery($maskedQuoteId, $itemId, $quantity); + $response = $this->graphQlMutation($query); $this->assertArrayHasKey('updateCartItems', $response); $this->assertArrayHasKey('cart', $response['updateCartItems']); @@ -100,7 +100,7 @@ public function testRemoveCartItemIfQuantityIsZero() public function testUpdateItemInNonExistentCart() { $query = $this->getQuery('non_existent_masked_id', 1, 2); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -116,7 +116,7 @@ public function testUpdateNonExistentItem() $this->expectExceptionMessage("Could not find cart item with id: {$notExistentItemId}."); $query = $this->getQuery($maskedQuoteId, $notExistentItemId, 2); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -142,7 +142,7 @@ public function testUpdateItemIfItemIsNotBelongToCart() $this->expectExceptionMessage("Could not find cart item with id: {$secondQuoteItemId}."); $query = $this->getQuery($firstQuoteMaskedId, $secondQuoteItemId, 2); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -158,7 +158,7 @@ public function testUpdateItemFromCustomerCart() $this->expectExceptionMessage("The current user cannot perform operations on cart \"$customerQuoteMaskedId\""); $query = $this->getQuery($customerQuoteMaskedId, $customerQuoteItemId, 2); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -180,13 +180,13 @@ public function testUpdateWithMissedCartItemId() cart { items { id - qty + quantity } } } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -210,14 +210,14 @@ public function testUpdateWithMissedItemRequiredParameters(string $input, string cart { items { id - qty + quantity } } } } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -236,7 +236,7 @@ public function dataProviderUpdateWithMissedRequiredParameters(): array ], 'missed_cart_item_qty' => [ 'cart_items: [{ cart_item_id: 1 }]', - 'Required parameter "quantity" for "cart_items" is missing.' + 'Field CartItemUpdateInput.quantity of required type Float! was not provided.' ], ]; } @@ -244,10 +244,10 @@ public function dataProviderUpdateWithMissedRequiredParameters(): array /** * @param string $maskedQuoteId * @param int $itemId - * @param float $qty + * @param float $quantity * @return string */ - private function getQuery(string $maskedQuoteId, int $itemId, float $qty): string + private function getQuery(string $maskedQuoteId, int $itemId, float $quantity): string { return <<<QUERY mutation { @@ -256,14 +256,14 @@ private function getQuery(string $maskedQuoteId, int $itemId, float $qty): strin cart_items: [ { cart_item_id: {$itemId} - quantity: {$qty} + quantity: {$quantity} } ] }) { cart { items { id - qty + quantity } } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/SendFriendTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/SendFriendTest.php index 05e3e608c5e52..ae6faae7650b9 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/SendFriendTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/SendFriendTest.php @@ -12,6 +12,9 @@ use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; +/** + * Tests for send email to friend + */ class SendFriendTest extends GraphQlAbstract { @@ -66,7 +69,7 @@ public function testSendFriend() } QUERY; - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertEquals('Name', $response['sendEmailToFriend']['sender']['name']); self::assertEquals('e@mail.com', $response['sendEmailToFriend']['sender']['email']); self::assertEquals('Lorem Ipsum', $response['sendEmailToFriend']['sender']['message']); @@ -117,7 +120,7 @@ public function testSendWithoutExistProduct() $this->expectExceptionMessage( 'The product that was requested doesn\'t exist. Verify the product and try again.' ); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -181,7 +184,7 @@ public function testMaxSendEmailToFriend() QUERY; $this->expectException(\Exception::class); $this->expectExceptionMessage("No more than {$sendFriend->getMaxRecipients()} emails can be sent at a time."); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -214,7 +217,7 @@ public function testErrors(string $input, string $errorMessage) QUERY; $this->expectException(\Exception::class); $this->expectExceptionMessage($errorMessage); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -269,8 +272,9 @@ public function testLimitMessagesPerHour() "You can't send messages more than {$sendFriend->getMaxSendsToFriend()} times an hour." ); - for ($i = 0; $i <= $sendFriend->getMaxSendsToFriend() + 1; $i++) { - $this->graphQlQuery($query); + $maxSendToFriends = $sendFriend->getMaxSendsToFriend(); + for ($i = 0; $i <= $maxSendToFriends + 1; $i++) { + $this->graphQlMutation($query); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlMutationTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlMutationTest.php index b6e1a61f0357c..c85f63c083700 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlMutationTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlMutationTest.php @@ -28,10 +28,31 @@ public function testMutation() } MUTATION; - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); $this->assertArrayHasKey('testItem', $response); $testItem = $response['testItem']; $this->assertArrayHasKey('integer_list', $testItem); $this->assertEquals([4, 5, 6], $testItem['integer_list']); } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Mutation requests allowed only for POST requests + */ + public function testMutationIsNotAllowedViaGetRequest() + { + $id = 3; + + $query = <<<MUTATION +mutation { + testItem(id: {$id}) { + item_id, + name, + integer_list + } +} +MUTATION; + + $this->graphQlQuery($query, [], '', []); + } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php index d59e255daa109..2db06e383758f 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php @@ -58,4 +58,46 @@ public function testQueryTestModuleExtensionAttribute() $this->assertArrayHasKey('integer_list', $testItem); $this->assertEquals([3, 4, 5], $testItem['integer_list']); } + + public function testQueryViaGetRequestReturnsResults() + { + $id = 1; + + $query = <<<QUERY +{ + testItem(id: {$id}) + { + item_id + name + } +} +QUERY; + + $response = $this->graphQlQuery($query, [], '', []); + + $this->assertArrayHasKey('testItem', $response); + } + + public function testQueryViaGetRequestWithVariablesReturnsResults() + { + $id = 1; + + $query = <<<QUERY +query getTestItem(\$id: Int!) +{ + testItem(id: \$id) + { + item_id + name + } +} +QUERY; + $variables = [ + "id" => $id + ]; + + $response = $this->graphQlQuery($query, $variables, '', []); + + $this->assertArrayHasKey('testItem', $response); + } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Ups/SetUpsShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Ups/SetUpsShippingMethodsOnCartTest.php index 463f2c4af101f..c50ae0a70380e 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Ups/SetUpsShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Ups/SetUpsShippingMethodsOnCartTest.php @@ -7,32 +7,55 @@ namespace Magento\GraphQl\Ups; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\Integration\Api\CustomerTokenServiceInterface; -use Magento\Quote\Model\QuoteFactory; -use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; -use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; /** - * Test for setting "UPS" shipping method on cart + * Test for setting "UPS" shipping method on cart. Current class covers the next UPS shipping methods: + * + * | Code | Label + * -------------------------------------- + * | 1DM | Next Day Air Early AM + * | 1DA | Next Day Air + * | 2DA | 2nd Day Air + * | 3DS | 3 Day Select + * | GND | Ground + * | STD | Canada Standard + * | XPR | Worldwide Express + * | WXS | Worldwide Express Saver + * | XDM | Worldwide Express Plus + * | XPD | Worldwide Expedited + * + * Current class does not cover these UPS shipping methods (depends on address and sandbox settings) + * + * | Code | Label + * -------------------------------------- + * | 1DML | Next Day Air Early AM Letter + * | 1DAL | Next Day Air Letter + * | 1DAPI | Next Day Air Intra (Puerto Rico) + * | 1DP | Next Day Air Saver + * | 1DPL | Next Day Air Saver Letter + * | 2DM | 2nd Day Air AM + * | 2DML | 2nd Day Air AM Letter + * | 2DAL | 2nd Day Air Letter + * | GNDCOM | Ground Commercial + * | GNDRES | Ground Residential + * | XPRL | Worldwide Express Letter + * | XDML | Worldwide Express Plus Letter */ class SetUpsShippingMethodsOnCartTest extends GraphQlAbstract { /** - * Defines carrier code for "UPS" shipping method - */ - const CARRIER_CODE = 'ups'; - - /** - * Defines method code for the "Ground" UPS shipping + * Defines carrier title for "UPS" shipping method */ - const CARRIER_METHOD_CODE_GROUND = 'GND'; + const CARRIER_TITLE = 'United Parcel Service'; /** - * @var QuoteFactory + * Defines carrier code for "UPS" shipping method */ - private $quoteFactory; + const CARRIER_CODE = 'ups'; /** * @var CustomerTokenServiceInterface @@ -40,14 +63,9 @@ class SetUpsShippingMethodsOnCartTest extends GraphQlAbstract private $customerTokenService; /** - * @var QuoteResource - */ - private $quoteResource; - - /** - * @var QuoteIdToMaskedQuoteIdInterface + * @var GetMaskedQuoteIdByReservedOrderId */ - private $quoteIdToMaskedId; + private $getMaskedQuoteIdByReservedOrderId; /** * @inheritdoc @@ -55,52 +73,130 @@ class SetUpsShippingMethodsOnCartTest extends GraphQlAbstract protected function setUp() { $objectManager = Bootstrap::getObjectManager(); - $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quoteFactory = $objectManager->get(QuoteFactory::class); - $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Ups/_files/enable_ups_shipping_method.php + * + * @dataProvider dataProviderShippingMethods + * @param string $methodCode + * @param string $methodTitle + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function testSetUpsShippingMethod(string $methodCode, string $methodTitle) + { + $quoteReservedId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($quoteReservedId); + + $query = $this->getQuery($maskedQuoteId, self::CARRIER_CODE, $methodCode); + $response = $this->sendRequestWithToken($query); + + self::assertArrayHasKey('setShippingMethodsOnCart', $response); + self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); + self::assertArrayHasKey('shipping_addresses', $response['setShippingMethodsOnCart']['cart']); + self::assertCount(1, $response['setShippingMethodsOnCart']['cart']['shipping_addresses']); + + $shippingAddress = current($response['setShippingMethodsOnCart']['cart']['shipping_addresses']); + self::assertArrayHasKey('selected_shipping_method', $shippingAddress); + + self::assertArrayHasKey('carrier_code', $shippingAddress['selected_shipping_method']); + self::assertEquals(self::CARRIER_CODE, $shippingAddress['selected_shipping_method']['carrier_code']); + + self::assertArrayHasKey('method_code', $shippingAddress['selected_shipping_method']); + self::assertEquals($methodCode, $shippingAddress['selected_shipping_method']['method_code']); + + self::assertArrayHasKey('carrier_title', $shippingAddress['selected_shipping_method']); + self::assertEquals(self::CARRIER_TITLE, $shippingAddress['selected_shipping_method']['carrier_title']); + + self::assertArrayHasKey('method_title', $shippingAddress['selected_shipping_method']); + } + + /** + * @return array + */ + public function dataProviderShippingMethods(): array + { + return [ + 'Next Day Air Early AM' => ['1DM', 'Next Day Air Early AM'], + 'Next Day Air' => ['1DA', 'Next Day Air'], + '2nd Day Air' => ['2DA', '2nd Day Air'], + '3 Day Select' => ['3DS', '3 Day Select'], + 'Ground' => ['GND', 'Ground'], + ]; } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php - * @magentoApiDataFixture Magento/Ups/_files/enable_ups_shipping_method.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_canada_address.php + * @magentoApiDataFixture Magento/GraphQl/Ups/_files/enable_ups_shipping_method.php + * + * @dataProvider dataProviderShippingMethodsBasedOnCanadaAddress + * @param string $methodCode + * @param string $methodTitle + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function testSetUpsShippingMethod() + public function testSetUpsShippingMethodBasedOnCanadaAddress(string $methodCode, string $methodTitle) { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id'); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId()); - $shippingAddressId = (int)$quote->getShippingAddress()->getId(); - - $query = $this->getAddUpsShippingMethodQuery( - $maskedQuoteId, - $shippingAddressId, - self::CARRIER_CODE, - self::CARRIER_METHOD_CODE_GROUND - ); + $quoteReservedId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($quoteReservedId); + $query = $this->getQuery($maskedQuoteId, self::CARRIER_CODE, $methodCode); $response = $this->sendRequestWithToken($query); - $addressesInformation = $response['setShippingMethodsOnCart']['cart']['shipping_addresses']; - $expectedResult = [ - 'carrier_code' => self::CARRIER_CODE, - 'method_code' => self::CARRIER_METHOD_CODE_GROUND, - 'label' => 'United Parcel Service - Ground', + + self::assertArrayHasKey('setShippingMethodsOnCart', $response); + self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); + self::assertArrayHasKey('shipping_addresses', $response['setShippingMethodsOnCart']['cart']); + self::assertCount(1, $response['setShippingMethodsOnCart']['cart']['shipping_addresses']); + + $shippingAddress = current($response['setShippingMethodsOnCart']['cart']['shipping_addresses']); + self::assertArrayHasKey('selected_shipping_method', $shippingAddress); + + self::assertArrayHasKey('carrier_code', $shippingAddress['selected_shipping_method']); + self::assertEquals(self::CARRIER_CODE, $shippingAddress['selected_shipping_method']['carrier_code']); + + self::assertArrayHasKey('method_code', $shippingAddress['selected_shipping_method']); + self::assertEquals($methodCode, $shippingAddress['selected_shipping_method']['method_code']); + + self::assertArrayHasKey('carrier_title', $shippingAddress['selected_shipping_method']); + self::assertEquals(self::CARRIER_TITLE, $shippingAddress['selected_shipping_method']['carrier_title']); + + self::assertArrayHasKey('method_title', $shippingAddress['selected_shipping_method']); + } + + /** + * @return array + */ + public function dataProviderShippingMethodsBasedOnCanadaAddress(): array + { + return [ + 'Canada Standard' => ['STD', 'Canada Standard'], + 'Worldwide Express' => ['XPR', 'Worldwide Express'], + 'Worldwide Express Saver' => ['WXS', 'Worldwide Express Saver'], + 'Worldwide Express Plus' => ['XDM', 'Worldwide Express Plus'], + 'Worldwide Expedited' => ['XPD', 'Worldwide Expedited'], ]; - self::assertEquals($addressesInformation[0]['selected_shipping_method'], $expectedResult); } /** * Generates query for setting the specified shipping method on cart * - * @param int $shippingAddressId * @param string $maskedQuoteId * @param string $carrierCode * @param string $methodCode * @return string */ - private function getAddUpsShippingMethodQuery( + private function getQuery( string $maskedQuoteId, - int $shippingAddressId, string $carrierCode, string $methodCode ): string { @@ -110,7 +206,6 @@ private function getAddUpsShippingMethodQuery( cart_id: "$maskedQuoteId" shipping_methods: [ { - cart_address_id: $shippingAddressId carrier_code: "$carrierCode" method_code: "$methodCode" } @@ -121,7 +216,8 @@ private function getAddUpsShippingMethodQuery( selected_shipping_method { carrier_code method_code - label + carrier_title + method_title } } } @@ -142,6 +238,6 @@ private function sendRequestWithToken(string $query): array $customerToken = $this->customerTokenService->createCustomerAccessToken('customer@example.com', 'password'); $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; - return $this->graphQlQuery($query, [], '', $headerMap); + return $this->graphQlMutation($query, [], '', $headerMap); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/CustomerPaymentTokensTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/CustomerPaymentTokensTest.php index 89fbbb9c49ed3..45c82906d255d 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/CustomerPaymentTokensTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/CustomerPaymentTokensTest.php @@ -14,6 +14,9 @@ use Magento\Vault\Model\ResourceModel\PaymentToken as TokenResource; use Magento\Vault\Model\ResourceModel\PaymentToken\CollectionFactory; +/** + * Tests for customer payment tokens + */ class CustomerPaymentTokensTest extends GraphQlAbstract { /** @@ -139,7 +142,12 @@ public function testDeletePaymentToken() } } QUERY; - $response = $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $response = $this->graphQlMutation( + $query, + [], + '', + $this->getCustomerAuthHeaders($currentEmail, $currentPassword) + ); $this->assertTrue($response['deletePaymentToken']['result']); $this->assertEquals(1, count($response['deletePaymentToken']['customerPaymentTokens']['items'])); @@ -168,7 +176,7 @@ public function testDeletePaymentTokenIfUserIsNotAuthorized() } } QUERY; - $this->graphQlQuery($query, [], ''); + $this->graphQlMutation($query, [], ''); } /** @@ -190,7 +198,7 @@ public function testDeletePaymentTokenInvalidPublicHash() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $this->graphQlMutation($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); } /** 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 8e5373ea76576..92942d7acc6f2 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 @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Sales\Service\V1; use Magento\Sales\Model\Order; @@ -201,6 +202,73 @@ public function testFullRequest() } } + /** + * Test order will keep same(custom) status after partial refund, if state has not been changed. + * + * @magentoApiDataFixture Magento/Sales/_files/order_with_invoice_and_custom_status.php + */ + public function testOrderStatusPartialRefund() + { + /** @var \Magento\Sales\Model\Order $existingOrder */ + $existingOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class) + ->loadByIncrementId('100000001'); + + $items = $this->getOrderItems($existingOrder); + $items[0]['qty'] -= 1; + $result = $this->_webApiCall( + $this->getServiceData($existingOrder), + [ + 'orderId' => $existingOrder->getEntityId(), + 'items' => $items, + ] + ); + + $this->assertNotEmpty( + $result, + 'Failed asserting that the received response is correct' + ); + + /** @var \Magento\Sales\Model\Order $updatedOrder */ + $updatedOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class) + ->loadByIncrementId($existingOrder->getIncrementId()); + + $this->assertSame('custom_processing', $updatedOrder->getStatus()); + $this->assertSame('processing', $updatedOrder->getState()); + } + + /** + * Test order will change custom status after total refund, when state has been changed. + * + * @magentoApiDataFixture Magento/Sales/_files/order_with_invoice_and_custom_status.php + */ + public function testOrderStatusTotalRefund() + { + /** @var \Magento\Sales\Model\Order $existingOrder */ + $existingOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class) + ->loadByIncrementId('100000001'); + + $items = $this->getOrderItems($existingOrder); + $result = $this->_webApiCall( + $this->getServiceData($existingOrder), + [ + 'orderId' => $existingOrder->getEntityId(), + 'items' => $items, + ] + ); + + $this->assertNotEmpty( + $result, + 'Failed asserting that the received response is correct' + ); + + /** @var \Magento\Sales\Model\Order $updatedOrder */ + $updatedOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class) + ->loadByIncrementId($existingOrder->getIncrementId()); + + $this->assertSame('complete', $updatedOrder->getStatus()); + $this->assertSame('complete', $updatedOrder->getState()); + } + /** * Prepares and returns info for API service. * diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/JoinDirectivesTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/JoinDirectivesTest.php index 5f967758e21bd..8beb14e81be71 100644 --- a/dev/tests/api-functional/testsuite/Magento/Webapi/JoinDirectivesTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Webapi/JoinDirectivesTest.php @@ -6,12 +6,14 @@ namespace Magento\Webapi; +use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\SearchCriteriaBuilder; -use Magento\Framework\Api\SortOrderBuilder; use Magento\Framework\Api\SortOrder; -use Magento\Framework\Api\SearchCriteria; -use Magento\Framework\Api\FilterBuilder; +use Magento\Framework\Api\SortOrderBuilder; +/** + * Test join directives. + */ class JoinDirectivesTest extends \Magento\TestFramework\TestCase\WebapiAbstract { /** @@ -44,7 +46,8 @@ protected function setUp() } /** - * Rollback rules + * Rollback rules. + * * @magentoApiDataFixture Magento/SalesRule/_files/rules_rollback.php * @magentoApiDataFixture Magento/Sales/_files/quote.php */ @@ -124,6 +127,49 @@ public function testAutoGeneratedGetList() $this->assertEquals($expectedExtensionAttributes['email'], $testAttribute['email']); } + /** + * Test get list of orders with extension attributes. + * + * @magentoApiDataFixture Magento/Sales/_files/order.php + */ + public function testGetOrdertList() + { + $filter = $this->filterBuilder + ->setField('increment_id') + ->setValue('100000001') + ->setConditionType('eq') + ->create(); + $this->searchBuilder->addFilters([$filter]); + $searchData = $this->searchBuilder->create()->__toArray(); + + $requestData = ['searchCriteria' => $searchData]; + + $restResourcePath = '/V1/orders/'; + $soapService = 'salesOrderRepositoryV1'; + $expectedExtensionAttributes = $this->getExpectedExtensionAttributes(); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => $restResourcePath . '?' . http_build_query($requestData), + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, + ], + 'soap' => [ + 'service' => $soapService, + 'operation' => $soapService . 'GetList', + ], + ]; + $searchResult = $this->_webApiCall($serviceInfo, $requestData); + + $this->assertArrayHasKey('items', $searchResult); + $itemData = array_pop($searchResult['items']); + $this->assertArrayHasKey('extension_attributes', $itemData); + $this->assertArrayHasKey('order_api_test_attribute', $itemData['extension_attributes']); + $testAttribute = $itemData['extension_attributes']['order_api_test_attribute']; + $this->assertEquals($expectedExtensionAttributes['firstname'], $testAttribute['first_name']); + $this->assertEquals($expectedExtensionAttributes['lastname'], $testAttribute['last_name']); + $this->assertEquals($expectedExtensionAttributes['email'], $testAttribute['email']); + } + /** * Retrieve the admin user's information. * diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli.php index f0abd280f3ebc..8fa22122cce89 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli.php @@ -8,7 +8,6 @@ use Magento\Mtf\Util\Protocol\CurlInterface; use Magento\Mtf\Util\Protocol\CurlTransport; -use Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator; /** * Perform bin/magento commands from command line for functional tests executions. @@ -18,7 +17,7 @@ class Cli /** * Url to command.php. */ - const URL = '/dev/tests/functional/utils/command.php'; + const URL = 'dev/tests/functional/utils/command.php'; /** * Curl transport protocol. @@ -27,21 +26,12 @@ class Cli */ private $transport; - /** - * Webapi handler. - * - * @var WebapiDecorator - */ - private $webapiHandler; - /** * @param CurlTransport $transport - * @param WebapiDecorator $webapiHandler */ - public function __construct(CurlTransport $transport, WebapiDecorator $webapiHandler) + public function __construct(CurlTransport $transport) { $this->transport = $transport; - $this->webapiHandler = $webapiHandler; } /** @@ -53,31 +43,22 @@ public function __construct(CurlTransport $transport, WebapiDecorator $webapiHan */ public function execute($command, $options = []) { - $this->transport->write( - rtrim(str_replace('index.php', '', $_ENV['app_frontend_url']), '/') . self::URL, - $this->prepareParamArray($command, $options), - CurlInterface::POST, - [] - ); - $this->transport->read(); - $this->transport->close(); + $curl = $this->transport; + $curl->write($this->prepareUrl($command, $options), [], CurlInterface::GET); + $curl->read(); + $curl->close(); } /** - * Prepare parameter array. + * Prepare url. * * @param string $command * @param array $options [optional] - * @return array + * @return string */ - private function prepareParamArray($command, $options = []) + private function prepareUrl($command, $options = []) { - if (!empty($options)) { - $command .= ' ' . implode(' ', $options); - } - return [ - 'token' => urlencode($this->webapiHandler->getWebapiToken()), - 'command' => urlencode($command) - ]; + $command .= ' ' . implode(' ', $options); + return $_ENV['app_frontend_url'] . self::URL . '?command=' . urlencode($command); } } diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/Reader.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/Reader.php index 69df78a5cad64..d7336b51a18e2 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/Reader.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/Reader.php @@ -3,12 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Mtf\Util\Command\File\Export; use Magento\Mtf\ObjectManagerInterface; use Magento\Mtf\Util\Protocol\CurlTransport; use Magento\Mtf\Util\Protocol\CurlInterface; -use Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator; /** * File reader for Magento export files. @@ -36,29 +36,16 @@ class Reader implements ReaderInterface */ private $transport; - /** - * Webapi handler. - * - * @var WebapiDecorator - */ - private $webapiHandler; - /** * @param ObjectManagerInterface $objectManager * @param CurlTransport $transport - * @param WebapiDecorator $webapiHandler * @param string $template */ - public function __construct( - ObjectManagerInterface $objectManager, - CurlTransport $transport, - WebapiDecorator $webapiHandler, - $template - ) { + public function __construct(ObjectManagerInterface $objectManager, CurlTransport $transport, $template) + { $this->objectManager = $objectManager; $this->template = $template; $this->transport = $transport; - $this->webapiHandler = $webapiHandler; } /** @@ -83,27 +70,20 @@ public function getData() */ private function getFiles() { - $this->transport->write( - rtrim(str_replace('index.php', '', $_ENV['app_frontend_url']), '/') . self::URL, - $this->prepareParamArray(), - CurlInterface::POST, - [] - ); + $this->transport->write($this->prepareUrl(), [], CurlInterface::GET); $serializedFiles = $this->transport->read(); $this->transport->close(); - return unserialize($serializedFiles); + // phpcs:ignore Magento2.Security.InsecureFunction + return unserialize($serializedFiles, ['allowed_classes' => false]); } /** - * Prepare parameter array. + * Prepare url. * - * @return array + * @return string */ - private function prepareParamArray() + private function prepareUrl() { - return [ - 'token' => urlencode($this->webapiHandler->getWebapiToken()), - 'template' => urlencode($this->template) - ]; + return $_ENV['app_frontend_url'] . self::URL . '?template=' . urlencode($this->template); } } diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/ReaderInterface.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/ReaderInterface.php index 3666e8643efa3..93f7cf1ce9764 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/ReaderInterface.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/ReaderInterface.php @@ -14,7 +14,7 @@ interface ReaderInterface /** * Url to export.php. */ - const URL = '/dev/tests/functional/utils/export.php'; + const URL = 'dev/tests/functional/utils/export.php'; /** * Exporting files as Data object from Magento. diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Log.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Log.php index 820a5b0a82228..f4e55682857a2 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Log.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Log.php @@ -7,7 +7,6 @@ namespace Magento\Mtf\Util\Command\File; use Magento\Mtf\Util\Protocol\CurlTransport; -use Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator; /** * Get content of log file in var/log folder. @@ -17,7 +16,7 @@ class Log /** * Url to log.php. */ - const URL = '/dev/tests/functional/utils/log.php'; + const URL = 'dev/tests/functional/utils/log.php'; /** * Curl transport protocol. @@ -26,21 +25,12 @@ class Log */ private $transport; - /** - * Webapi handler. - * - * @var WebapiDecorator - */ - private $webapiHandler; - /** * @param CurlTransport $transport - * @param WebapiDecorator $webapiHandler */ - public function __construct(CurlTransport $transport, WebapiDecorator $webapiHandler) + public function __construct(CurlTransport $transport) { $this->transport = $transport; - $this->webapiHandler = $webapiHandler; } /** @@ -51,28 +41,22 @@ public function __construct(CurlTransport $transport, WebapiDecorator $webapiHan */ public function getFileContent($name) { - $this->transport->write( - rtrim(str_replace('index.php', '', $_ENV['app_frontend_url']), '/') . self::URL, - $this->prepareParamArray($name), - CurlInterface::POST, - [] - ); - $data = $this->transport->read(); - $this->transport->close(); + $curl = $this->transport; + $curl->write($this->prepareUrl($name), [], CurlTransport::GET); + $data = $curl->read(); + $curl->close(); + // phpcs:ignore Magento2.Security.InsecureFunction return unserialize($data); } /** - * Prepare parameter array. + * Prepare url. * * @param string $name - * @return array + * @return string */ - private function prepareParamArray($name) + private function prepareUrl($name) { - return [ - 'token' => urlencode($this->webapiHandler->getWebapiToken()), - 'name' => urlencode($name) - ]; + return $_ENV['app_frontend_url'] . self::URL . '?name=' . urlencode($name); } } diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/GeneratedCode.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/GeneratedCode.php index a9fefa25ffa24..dde3409ed1562 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/GeneratedCode.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/GeneratedCode.php @@ -7,7 +7,6 @@ use Magento\Mtf\Util\Protocol\CurlInterface; use Magento\Mtf\Util\Protocol\CurlTransport; -use Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator; /** * GeneratedCode removes generated code of Magento (like generated/code and generated/metadata). @@ -17,7 +16,7 @@ class GeneratedCode /** * Url to deleteMagentoGeneratedCode.php. */ - const URL = '/dev/tests/functional/utils/deleteMagentoGeneratedCode.php'; + const URL = 'dev/tests/functional/utils/deleteMagentoGeneratedCode.php'; /** * Curl transport protocol. @@ -26,21 +25,12 @@ class GeneratedCode */ private $transport; - /** - * Webapi handler. - * - * @var WebapiDecorator - */ - private $webapiHandler; - /** * @param CurlTransport $transport - * @param WebapiDecorator $webapiHandler */ - public function __construct(CurlTransport $transport, WebapiDecorator $webapiHandler) + public function __construct(CurlTransport $transport) { $this->transport = $transport; - $this->webapiHandler = $webapiHandler; } /** @@ -50,25 +40,10 @@ public function __construct(CurlTransport $transport, WebapiDecorator $webapiHan */ public function delete() { - $this->transport->write( - rtrim(str_replace('index.php', '', $_ENV['app_frontend_url']), '/') . self::URL, - $this->prepareParamArray(), - CurlInterface::POST, - [] - ); - $this->transport->read(); - $this->transport->close(); - } - - /** - * Prepare parameter array. - * - * @return array - */ - private function prepareParamArray() - { - return [ - 'token' => urlencode($this->webapiHandler->getWebapiToken()) - ]; + $url = $_ENV['app_frontend_url'] . self::URL; + $curl = $this->transport; + $curl->write($url, [], CurlInterface::GET); + $curl->read(); + $curl->close(); } } diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Locales.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Locales.php index a55d803f43087..f669d91f2f2e5 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Locales.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Locales.php @@ -7,7 +7,6 @@ use Magento\Mtf\Util\Protocol\CurlInterface; use Magento\Mtf\Util\Protocol\CurlTransport; -use Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator; /** * Returns array of locales depends on fetching type. @@ -27,7 +26,7 @@ class Locales /** * Url to locales.php. */ - const URL = '/dev/tests/functional/utils/locales.php'; + const URL = 'dev/tests/functional/utils/locales.php'; /** * Curl transport protocol. @@ -36,21 +35,12 @@ class Locales */ private $transport; - /** - * Webapi handler. - * - * @var WebapiDecorator - */ - private $webapiHandler; - /** * @param CurlTransport $transport Curl transport protocol - * @param WebapiDecorator $webapiHandler */ - public function __construct(CurlTransport $transport, WebapiDecorator $webapiHandler) + public function __construct(CurlTransport $transport) { $this->transport = $transport; - $this->webapiHandler = $webapiHandler; } /** @@ -61,28 +51,12 @@ public function __construct(CurlTransport $transport, WebapiDecorator $webapiHan */ public function getList($type = self::TYPE_ALL) { - $this->transport->write( - rtrim(str_replace('index.php', '', $_ENV['app_frontend_url']), '/') . self::URL, - $this->prepareParamArray($type), - CurlInterface::POST, - [] - ); - $result = $this->transport->read(); - $this->transport->close(); - return explode('|', $result); - } + $url = $_ENV['app_frontend_url'] . self::URL . '?type=' . $type; + $curl = $this->transport; + $curl->write($url, [], CurlInterface::GET); + $result = $curl->read(); + $curl->close(); - /** - * Prepare parameter array. - * - * @param string $type - * @return array - */ - private function prepareParamArray($type) - { - return [ - 'token' => urlencode($this->webapiHandler->getWebapiToken()), - 'type' => urlencode($type) - ]; + return explode('|', $result); } } diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/PathChecker.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/PathChecker.php index 4b12f6eec87aa..fd1f746a6f09c 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/PathChecker.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/PathChecker.php @@ -7,7 +7,6 @@ use Magento\Mtf\Util\Protocol\CurlInterface; use Magento\Mtf\Util\Protocol\CurlTransport; -use Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator; /** * PathChecker checks that path to file or directory exists. @@ -17,7 +16,7 @@ class PathChecker /** * Url to checkPath.php. */ - const URL = '/dev/tests/functional/utils/pathChecker.php'; + const URL = 'dev/tests/functional/utils/pathChecker.php'; /** * Curl transport protocol. @@ -27,21 +26,11 @@ class PathChecker private $transport; /** - * Webapi handler. - * - * @var WebapiDecorator - */ - private $webapiHandler; - - /** - * @constructor * @param CurlTransport $transport - * @param WebapiDecorator $webapiHandler */ - public function __construct(CurlTransport $transport, WebapiDecorator $webapiHandler) + public function __construct(CurlTransport $transport) { $this->transport = $transport; - $this->webapiHandler = $webapiHandler; } /** @@ -52,28 +41,12 @@ public function __construct(CurlTransport $transport, WebapiDecorator $webapiHan */ public function pathExists($path) { - $this->transport->write( - rtrim(str_replace('index.php', '', $_ENV['app_frontend_url']), '/') . self::URL, - $this->prepareParamArray($path), - CurlInterface::POST, - [] - ); - $result = $this->transport->read(); - $this->transport->close(); - return strpos($result, 'path exists: true') !== false; - } + $url = $_ENV['app_frontend_url'] . self::URL . '?path=' . urlencode($path); + $curl = $this->transport; + $curl->write($url, [], CurlInterface::GET); + $result = $curl->read(); + $curl->close(); - /** - * Prepare parameter array. - * - * @param string $path - * @return array - */ - private function prepareParamArray($path) - { - return [ - 'token' => urlencode($this->webapiHandler->getWebapiToken()), - 'path' => urlencode($path) - ]; + return strpos($result, 'path exists: true') !== false; } } diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Website.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Website.php index fec20bb2a8715..7d73634c0360d 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Website.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Website.php @@ -3,11 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Mtf\Util\Command; use Magento\Mtf\Util\Protocol\CurlInterface; use Magento\Mtf\Util\Protocol\CurlTransport; -use Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator; /** * Perform Website folder creation for functional tests executions. @@ -17,7 +17,7 @@ class Website /** * Url to website.php. */ - const URL = '/dev/tests/functional/utils/website.php'; + const URL = 'dev/tests/functional/utils/website.php'; /** * Curl transport protocol. @@ -26,22 +26,13 @@ class Website */ private $transport; - /** - * Webapi handler. - * - * @var WebapiDecorator - */ - private $webapiHandler; - /** * @constructor * @param CurlTransport $transport - * @param WebapiDecorator $webapiHandler */ - public function __construct(CurlTransport $transport, WebapiDecorator $webapiHandler) + public function __construct(CurlTransport $transport) { $this->transport = $transport; - $this->webapiHandler = $webapiHandler; } /** @@ -52,28 +43,21 @@ public function __construct(CurlTransport $transport, WebapiDecorator $webapiHan */ public function create($websiteCode) { - $this->transport->addOption(CURLOPT_HEADER, 1); - $this->transport->write( - rtrim(str_replace('index.php', '', $_ENV['app_frontend_url']), '/') . self::URL, - $this->prepareParamArray($websiteCode), - CurlInterface::POST, - [] - ); - $this->transport->read(); - $this->transport->close(); + $curl = $this->transport; + $curl->addOption(CURLOPT_HEADER, 1); + $curl->write($this->prepareUrl($websiteCode), [], CurlInterface::GET); + $curl->read(); + $curl->close(); } /** - * Prepare parameter array. + * Prepare url. * * @param string $websiteCode - * @return array + * @return string */ - private function prepareParamArray($websiteCode) + private function prepareUrl($websiteCode) { - return [ - 'token' => urlencode($this->webapiHandler->getWebapiToken()), - 'website_code' => urlencode($websiteCode) - ]; + return $_ENV['app_frontend_url'] . self::URL . '?website_code=' . urlencode($websiteCode); } } diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/BackendDecorator.php b/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/BackendDecorator.php index d7026e9b8efb3..b1c552370835c 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/BackendDecorator.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/BackendDecorator.php @@ -63,56 +63,24 @@ public function __construct(CurlTransport $transport, DataInterface $configurati */ protected function authorize() { - // There are situations where magento application backend url could be slightly different from the environment - // variable we know. It could be intentionally (e.g. InstallTest) or unintentionally. We would still want tests - // to run in this case. - // When the original app_backend_url does not work, we will try 4 variants of the it. i.e. with and without - // url rewrite, http and https. - $urls = []; - $originalUrl = rtrim($_ENV['app_backend_url'], '/') . '/'; - $urls[] = $originalUrl; - // It could be the case that the page needs a refresh, so we will try the original one twice. - $urls[] = $originalUrl; - if (strpos($originalUrl, '/index.php') !== false) { - $url2 = str_replace('/index.php', '', $originalUrl); - } else { - $url2 = $originalUrl . 'index.php/'; - } - $urls[] = $url2; - if (strpos($originalUrl, 'https') !== false) { - $urls[] = str_replace('https', 'http', $originalUrl); - } else { - $urls[] = str_replace('http', 'https', $url2); - } - - $isAuthorized = false; - foreach ($urls as $url) { - try { - // Perform GET to backend url so form_key is set - $this->transport->write($url, [], CurlInterface::GET); - $this->read(); - - $authUrl = $url . $this->configuration->get('application/0/backendLoginUrl/0/value'); - $data = [ - 'login[username]' => $this->configuration->get('application/0/backendLogin/0/value'), - 'login[password]' => $this->configuration->get('application/0/backendPassword/0/value'), - 'form_key' => $this->formKey, - ]; - - $this->transport->write($authUrl, $data, CurlInterface::POST); - $response = $this->read(); - if (strpos($response, 'login-form') !== false) { - continue; - } - $isAuthorized = true; - $_ENV['app_backend_url'] = $url; - break; - } catch (\Exception $e) { - continue; - } - } - if ($isAuthorized == false) { - throw new \Exception('Admin user cannot be logged in by curl handler!'); + // Perform GET to backend url so form_key is set + $url = $_ENV['app_backend_url']; + $this->transport->write($url, [], CurlInterface::GET); + $this->read(); + + $url = $_ENV['app_backend_url'] . $this->configuration->get('application/0/backendLoginUrl/0/value'); + $data = [ + 'login[username]' => $this->configuration->get('application/0/backendLogin/0/value'), + 'login[password]' => $this->configuration->get('application/0/backendPassword/0/value'), + 'form_key' => $this->formKey, + ]; + $this->transport->write($url, $data, CurlInterface::POST); + $response = $this->read(); + if (strpos($response, 'login-form') !== false) { + // phpcs:ignore Magento2.Exceptions.DirectThrow + throw new \Exception( + 'Admin user cannot be logged in by curl handler!' + ); } } @@ -144,6 +112,7 @@ public function write($url, $params = [], $method = CurlInterface::POST, $header if ($this->formKey) { $params['form_key'] = $this->formKey; } else { + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception(sprintf('Form key is absent! Url: "%s" Response: "%s"', $url, $this->response)); } $this->transport->write($url, http_build_query($params), $method, $headers); diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/WebapiDecorator.php b/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/WebapiDecorator.php index df5ab45a3f96d..3aa756904ab00 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/WebapiDecorator.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/WebapiDecorator.php @@ -70,13 +70,6 @@ class WebapiDecorator implements CurlInterface */ protected $response; - /** - * Webapi token. - * - * @var string - */ - protected $webapiToken; - /** * @construct * @param ObjectManager $objectManager @@ -117,9 +110,6 @@ protected function init() $integration->persist(); $this->setConfiguration($integration); - $this->webapiToken = $integration->getToken(); - } else { - $this->webapiToken = $integrationToken; } } @@ -171,13 +161,7 @@ protected function setConfiguration(Integration $integration) */ protected function isValidIntegration() { - $url = rtrim($_ENV['app_frontend_url'], '/'); - if (strpos($url, 'index.php') === false) { - $url .= '/index.php/rest/V1/modules'; - } else { - $url .= '/rest/V1/modules'; - } - $this->write($url, [], CurlInterface::GET); + $this->write($_ENV['app_frontend_url'] . 'rest/V1/modules', [], CurlInterface::GET); $response = json_decode($this->read(), true); return (null !== $response) && !isset($response['message']); @@ -235,18 +219,4 @@ public function close() { $this->transport->close(); } - - /** - * Return webapiToken. - * - * @return string - */ - public function getWebapiToken() - { - // Request token if integration is no longer valid - if (!$this->isValidIntegration()) { - $this->init(); - } - return $this->webapiToken; - } } diff --git a/dev/tests/functional/tests/app/Magento/AdminNotification/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/AdminNotification/Test/TestCase/NavigateMenuTest.xml index f2f56eb74f704..99bd9c6d9d220 100644 --- a/dev/tests/functional/tests/app/Magento/AdminNotification/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/AdminNotification/Test/TestCase/NavigateMenuTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">System > Notifications</data> <data name="pageTitle" xsi:type="string">Notifications</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.php b/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.php index fefe0d2c126e5..c2c684c89d06b 100644 --- a/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.php +++ b/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.php @@ -140,9 +140,9 @@ public function test( if ($website) { $website->persist(); $this->setupCurrencyForCustomWebsite($website, $currencyCustomWebsite); - $this->cron->run(); - $this->cron->run(); } + $this->cron->run(); + $this->cron->run(); $products = $this->prepareProducts($products, $website); $this->cron->run(); $this->cron->run(); @@ -165,7 +165,8 @@ public function test( if (!empty($advancedPricingAttributes)) { $products = [$products[0]]; } - + $this->cron->run(); + $this->cron->run(); return [ 'products' => $products ]; diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/NavigateMenuTest.xml index cdb73c5d36f25..8f7b07c8c14c4 100644 --- a/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/NavigateMenuTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest" summary="Navigate to menu chapter"> <variation name="NavigateMenuTestBIEssentials" summary="Navigate through BI Essentials admin menu to Sign Up page" ticketId="MAGETWO-63700"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="issue" xsi:type="string">MAGETWO-97261: Magento\Backend\Test\TestCase\NavigateMenuTest fails on Jenkins</data> <data name="menuItem" xsi:type="string">Reports > BI Essentials</data> <data name="waitMenuItemNotVisible" xsi:type="boolean">false</data> @@ -15,6 +16,7 @@ <constraint name="Magento\Analytics\Test\Constraint\AssertBIEssentialsLink" /> </variation> <variation name="NavigateMenuTestAdvancedReporting" summary="Navigate through Advanced Reporting admin menu to BI Reports page" ticketId="MAGETWO-65748"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > Advanced Reporting</data> <data name="waitMenuItemNotVisible" xsi:type="boolean">false</data> <data name="advancedReportingLink" xsi:type="string">https://advancedreporting.rjmetrics.com/report</data> diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/GlobalSearchEntityTest.xml b/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/GlobalSearchEntityTest.xml index dca6e1d15024f..6d9c50b4317c8 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/GlobalSearchEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/GlobalSearchEntityTest.xml @@ -8,31 +8,26 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\GlobalSearchEntityTest" summary="Global Search Backend " ticketId="MAGETWO-28457"> <variation name="GlobalSearchEntityTestVariation1"> - <data name="tag" xsi:type="string">stable:no</data> <data name="description" xsi:type="string">search shows admin preview</data> <data name="search/data/query" xsi:type="string">Some search term</data> <constraint name="Magento\Backend\Test\Constraint\AssertGlobalSearchPreview" /> </variation> <variation name="GlobalSearchEntityTestVariation2"> - <data name="tag" xsi:type="string">stable:no</data> <data name="description" xsi:type="string">search with 2 sign return no results</data> <data name="search/data/query" xsi:type="string">:)</data> <constraint name="Magento\Backend\Test\Constraint\AssertGlobalSearchNoRecordsFound" /> </variation> <variation name="GlobalSearchEntityTestVariation3"> - <data name="tag" xsi:type="string">stable:no</data> <data name="description" xsi:type="string">search product by sku</data> <data name="search/data/query" xsi:type="string">orderInjectable::default::product::sku</data> <constraint name="Magento\Backend\Test\Constraint\AssertGlobalSearchProductName" /> </variation> <variation name="GlobalSearchEntityTestVariation4"> - <data name="tag" xsi:type="string">stable:no</data> <data name="description" xsi:type="string">search existed customer</data> <data name="search/data/query" xsi:type="string">customer::johndoe_unique::lastname</data> <constraint name="Magento\Backend\Test\Constraint\AssertGlobalSearchCustomerName" /> </variation> <variation name="GlobalSearchEntityTestVariation5"> - <data name="tag" xsi:type="string">stable:no</data> <data name="description" xsi:type="string">search order (by order id)</data> <data name="search/data/query" xsi:type="string">orderInjectable::default::id</data> <constraint name="Magento\Backend\Test\Constraint\AssertGlobalSearchOrderId" /> diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/NavigateMenuTest.xml index 67842f62d7c92..afdf70704a984 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/NavigateMenuTest.xml @@ -8,26 +8,31 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest" summary="Navigate through admin menu" ticketId="MAGETWO-34874"> <variation name="NavigateMenuTest2"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Dashboard</data> <data name="pageTitle" xsi:type="string">Dashboard</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> <variation name="NavigateMenuTest3"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Content > Schedule</data> <data name="pageTitle" xsi:type="string">Store Design Schedule</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> <variation name="NavigateMenuTest4"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Stores > All Stores</data> <data name="pageTitle" xsi:type="string">Stores</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> <variation name="NavigateMenuTest5"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Stores > Configuration</data> <data name="pageTitle" xsi:type="string">Configuration</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> <variation name="NavigateMenuTest6"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">System > Cache Management</data> <data name="pageTitle" xsi:type="string">Cache Management</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/CreateCategoryEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/CreateCategoryEntityTest.xml index ea6808ee2a7f5..69093b8adb8db 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/CreateCategoryEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/CreateCategoryEntityTest.xml @@ -17,7 +17,6 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryForm" /> </variation> <variation name="CreateCategoryEntityTestVariation2_RootCategory_AllFields"> - <data name="issue" xsi:type="string">MAGETWO-60635: [CE][Categories] Design update dates are incorrect after save</data> <data name="description" xsi:type="string">Create root category with all fields</data> <data name="addCategory" xsi:type="string">addRootCategory</data> <data name="category/data/is_active" xsi:type="string">Yes</data> @@ -58,7 +57,6 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryPage" /> </variation> <variation name="CreateCategoryEntityTestVariation4_Subcategory_AllFields"> - <data name="issue" xsi:type="string">MAGETWO-60635: [CE][Categories] Design update dates are incorrect after save</data> <data name="description" xsi:type="string">Create not anchor subcategory specifying all fields</data> <data name="addCategory" xsi:type="string">addSubcategory</data> <data name="category/data/parent_id/dataset" xsi:type="string">default_category</data> @@ -146,7 +144,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryPage" /> </variation> <variation name="CreateCategoryEntityTestVariation8_WithProducts" summary="Assign Products at the Category Level" ticketId="MAGETWO-16351"> - <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, stable:no</data> + <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test</data> <data name="addCategory" xsi:type="string">addSubcategory</data> <data name="category/data/parent_id/dataset" xsi:type="string">default_category</data> <data name="category/data/is_active" xsi:type="string">Yes</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/NavigateMenuTest.xml index 260095048431e..08bff9f70708a 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/NavigateMenuTest.xml @@ -8,21 +8,25 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest9"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Catalog > Products</data> <data name="pageTitle" xsi:type="string">Products</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest10"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Catalog > Categories</data> <data name="pageTitle" xsi:type="string">Default Category (ID: 2)</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest11"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Stores > Product</data> <data name="pageTitle" xsi:type="string">Product Attributes</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest12"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Stores > Attribute Set</data> <data name="pageTitle" xsi:type="string">Attribute Sets</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityPartOneTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityPartOneTest.xml index 4a3d80b0b6090..f97735304baa5 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityPartOneTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityPartOneTest.xml @@ -25,6 +25,8 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductInventoryMaxAllowedQty" /> </variation> <variation name="CreateSimpleProductEntityTestVariation13"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> + <data name="issue" xsi:type="string">MAGETWO-48850: Filtering Category Products using scope selector</data> <data name="description" xsi:type="string">Create simple product and check search by sku</data> <data name="product/data/url_key" xsi:type="string">simple-product-%isolation%</data> <data name="product/data/name" xsi:type="string">Simple Product %isolation%</data> @@ -42,6 +44,8 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductPage" /> </variation> <variation name="CreateSimpleProductEntityTestVariation14"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> + <data name="issue" xsi:type="string">MC-6220: Add products to wishlist from Category page with multiple wishlist enabled</data> <data name="description" xsi:type="string">Create simple product and check visibility in category</data> <data name="product/data/url_key" xsi:type="string">simple-product-%isolation%</data> <data name="product/data/name" xsi:type="string">Simple Product %isolation%</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityPartTwoTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityPartTwoTest.xml index a40c715bb3ac3..d6b75e5c3a6e4 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityPartTwoTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityPartTwoTest.xml @@ -8,6 +8,8 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Product\CreateSimpleProductEntityPartTwoTest" summary="Create Simple Product" ticketId="MAGETWO-23414"> <variation name="CreateSimpleProductEntityTestVariation23" summary="Create Simple Product with Creating New Category (Required Fields Only)" ticketId="MAGETWO-27293"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> + <data name="issue" xsi:type="string">MC-234: Admin should be able to create category from the product page</data> <data name="product/data/category_ids/new_category" xsi:type="string">yes</data> <data name="product/data/category_ids/dataset" xsi:type="string">default_subcategory_without_url_key</data> <data name="product/data/website_ids/0/dataset" xsi:type="string">default</data> @@ -23,6 +25,8 @@ <constraint name="Magento\UrlRewrite\Test\Constraint\AssertUrlRewriteProductInGrid" /> </variation> <variation name="CreateSimpleProductEntityTestVariation24" summary="Create Simple Product and Assigning It to Category" ticketId="MAGETWO-12514"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> + <data name="issue" xsi:type="string">MAGETWO-23414: Create Simple Product</data> <data name="product/data/url_key" xsi:type="string">simple-product-%isolation%</data> <data name="product/data/name" xsi:type="string">Simple Product %isolation%</data> <data name="product/data/sku" xsi:type="string">simple_sku_%isolation%</data> @@ -79,6 +83,8 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> </variation> <variation name="CreateSimpleProductEntityTestVariation28" summary="Create product with tier price for not logged in customer"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> + <data name="issue" xsi:type="string">MAGETWO-68921: Apply Tier Price to a product</data> <data name="product/data/url_key" xsi:type="string">simple-product-%isolation%</data> <data name="product/data/name" xsi:type="string">Simple Product %isolation%</data> <data name="product/data/sku" xsi:type="string">simple_sku_%isolation%</data> @@ -118,6 +124,8 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductInCart" /> </variation> <variation name="CreateSimpleProductEntityWithTierPriceTestVariation1" summary="Create Simple Product with fixed tier price."> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> + <data name="issue" xsi:type="string">MAGETWO-68921: Apply Tier Price to a product</data> <data name="product/data/url_key" xsi:type="string">simple-product-%isolation%</data> <data name="product/data/name" xsi:type="string">Simple Product %isolation%</data> <data name="product/data/sku" xsi:type="string">simple_sku_%isolation%</data> @@ -127,6 +135,8 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductTierPriceInCart" /> </variation> <variation name="CreateSimpleProductEntityWithTierPriceTestVariation2" summary="Create Simple Product with percentage tier price."> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> + <data name="issue" xsi:type="string">MAGETWO-68921: Apply Tier Price to a product</data> <data name="product/data/url_key" xsi:type="string">simple-product-%isolation%</data> <data name="product/data/name" xsi:type="string">Simple Product %isolation%</data> <data name="product/data/sku" xsi:type="string">simple_sku_%isolation%</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.php index 7a677dbea983c..4d866f716d70c 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.php @@ -79,7 +79,6 @@ public function testCreate( $flushCache = false, $configData = null ) { - $this->markTestIncomplete('https://github.com/magento-engcom/msi/issues/1620'); $this->configData = $configData; $this->flushCache = $flushCache; diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml index 8732157e5c657..840dc0b0812b2 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml @@ -182,6 +182,8 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductPage" /> </variation> <variation name="CreateSimpleProductEntityTestVariation10" summary="Create in stock product and check threshold" ticketId="MAGETWO-43345"> + <data name="issue" xsi:type="string">https://github.com/magento-engcom/msi/issues/1620</data> + <data name="tag" xsi:type="string">to_maintain:yes</data> <data name="configData" xsi:type="string">inventory_threshold_5</data> <data name="product/data/url_key" xsi:type="string">simple-product-%isolation%</data> <data name="product/data/name" xsi:type="string">Simple Product %isolation%</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityTest.xml index 2287546aed102..49725d08b63e8 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityTest.xml @@ -8,7 +8,6 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\ProductAttribute\CreateProductAttributeEntityTest" summary="Create Product Attribute" ticketId="MAGETWO-24767"> <variation name="CreateProductAttributeEntityTestVariation1"> - <data name="tag" xsi:type="string">to_maintain:yes</data> <data name="attributeSet/dataset" xsi:type="string">custom_attribute_set</data> <data name="productAttribute/data/frontend_label" xsi:type="string">Text_Field_Admin_%isolation%</data> <data name="productAttribute/data/frontend_input" xsi:type="string">Text Field</data> @@ -60,7 +59,6 @@ <data name="productAttribute/data/used_in_product_listing" xsi:type="string">Yes</data> <data name="productAttribute/data/is_used_for_promo_rules" xsi:type="string">Yes</data> <data name="productAttribute/data/used_for_sort_by" xsi:type="string">Yes</data> - <data name="tag" xsi:type="string">to_maintain:yes</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeInGrid" /> <constraint name="Magento\Catalog\Test\Constraint\AssertAttributeForm" /> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertAdvancedSearchProductByAttribute" /> @@ -69,7 +67,6 @@ <constraint name="Magento\CatalogRule\Test\Constraint\AssertProductAttributeIsUsedPromoRules" /> </variation> <variation name="CreateProductAttributeEntityTestVariation4"> - <data name="tag" xsi:type="string">to_maintain:yes</data> <data name="attributeSet/dataset" xsi:type="string">custom_attribute_set</data> <data name="productAttribute/data/frontend_label" xsi:type="string">Yes/No_Admin_%isolation%</data> <data name="productAttribute/data/frontend_input" xsi:type="string">Yes/No</data> @@ -86,6 +83,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertAddedProductAttributeOnProductForm" /> </variation> <variation name="CreateProductAttributeEntityTestVariation5" summary="Create custom multiple select attribute product field" ticketId="MAGETWO-14862"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="attributeSet/dataset" xsi:type="string">custom_attribute_set</data> <data name="productAttribute/data/frontend_label" xsi:type="string">Multiple_Select_Admin_%isolation%</data> <data name="productAttribute/data/frontend_input" xsi:type="string">Multiple Select</data> @@ -102,7 +100,6 @@ <data name="productAttribute/data/is_html_allowed_on_front" xsi:type="string">Yes</data> <data name="productAttribute/data/is_visible_on_front" xsi:type="string">Yes</data> <data name="productAttribute/data/used_in_product_listing" xsi:type="string">Yes</data> - <data name="tag" xsi:type="string">to_maintain:yes</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeInGrid" /> <constraint name="Magento\Catalog\Test\Constraint\AssertAttributeForm" /> <constraint name="Magento\Catalog\Test\Constraint\AssertAddedProductAttributeOnProductForm" /> @@ -178,7 +175,6 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeIsFilterableInSearch" /> </variation> <variation name="CreateProductAttributeEntityTestVariation8"> - <data name="tag" xsi:type="string">to_maintain:yes</data> <data name="attributeSet/dataset" xsi:type="string">custom_attribute_set</data> <data name="productAttribute/data/frontend_label" xsi:type="string">Fixed_Product_Tax_Admin_%isolation%</data> <data name="productAttribute/data/frontend_input" xsi:type="string">Fixed Product Tax</data> @@ -195,7 +191,6 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertAddedProductAttributeOnProductForm" /> </variation> <variation name="CreateProductAttributeEntityTestVariation9"> - <data name="tag" xsi:type="string">to_maintain:yes</data> <data name="attributeSet/dataset" xsi:type="string">custom_attribute_set</data> <data name="productAttribute/data/frontend_label" xsi:type="string">Text_Field_Admin_%isolation%</data> <data name="productAttribute/data/frontend_input" xsi:type="string">Text Field</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteProductAttributeEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteProductAttributeEntityTest.xml index 11ba7266ce564..0dd47942c8fe0 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteProductAttributeEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteProductAttributeEntityTest.xml @@ -16,7 +16,6 @@ <constraint name="Magento\ImportExport\Test\Constraint\AssertProductAttributeAbsenceForExport" /> </variation> <variation name="DeleteProductAttributeEntityTestVariation2"> - <data name="tag" xsi:type="string">stable:no</data> <data name="attribute/dataset" xsi:type="string">attribute_type_dropdown</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeSuccessDeleteMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeAbsenceInGrid" /> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/UpdateAttributeSetTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/UpdateAttributeSetTest.xml index 18b9a199cac04..18dc5c983ad79 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/UpdateAttributeSetTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/UpdateAttributeSetTest.xml @@ -8,7 +8,6 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\ProductAttribute\UpdateAttributeSetTest" summary="Update Attribute Set" ticketId="MAGETWO-26251"> <variation name="UpdateAttributeSetTestVariation1"> - <data name="tag" xsi:type="string">stable:no</data> <data name="attributeSet/data/attribute_set_name" xsi:type="string">AttributeSetEdit1%isolation%</data> <data name="attributeSet/data/group" xsi:type="string">Custom-group%isolation%</data> <data name="attributeSetOriginal/dataset" xsi:type="string">custom_attribute_set</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/UpdateProductAttributeEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/UpdateProductAttributeEntityTest.xml index 40cf8e40ae33f..d6420ff431ce6 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/UpdateProductAttributeEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/UpdateProductAttributeEntityTest.xml @@ -8,7 +8,6 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\ProductAttribute\UpdateProductAttributeEntityTest" summary="Update Product Attribute" ticketId="MAGETWO-23459"> <variation name="UpdateProductAttributeEntityTestVariation1"> - <data name="tag" xsi:type="string">to_maintain:yes</data> <data name="attributeSet/dataset" xsi:type="string">custom_attribute_set</data> <data name="productAttributeOriginal/dataset" xsi:type="string">attribute_type_text_field</data> <data name="attribute/data/frontend_label" xsi:type="string">Text_Field_%isolation%</data> @@ -29,7 +28,6 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertAddedProductAttributeOnProductForm" /> </variation> <variation name="UpdateProductAttributeEntityTestVariation2"> - <data name="tag" xsi:type="string">to_maintain:yes</data> <data name="attributeSet/dataset" xsi:type="string">custom_attribute_set</data> <data name="productAttributeOriginal/dataset" xsi:type="string">attribute_type_dropdown</data> <data name="attribute/data/frontend_label" xsi:type="string">Dropdown_%isolation%</data> @@ -55,6 +53,8 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertAddedProductAttributeOnProductForm" /> </variation> <variation name="UpdateProductAttributeEntityTestVariation3"> + <data name="issue" xsi:type="string">MAGETWO-46494: [FT] UpdateProductAttributeEntityTestVariation3 does not actually create an attribute to check</data> + <data name="tag" xsi:type="string">to_maintain:yes</data> <data name="attributeSet/dataset" xsi:type="string">custom_attribute_set</data> <data name="productAttributeOriginal/dataset" xsi:type="string">tax_class_id</data> <data name="attribute/data/is_searchable" xsi:type="string">Yes</data> @@ -62,7 +62,6 @@ <data name="cacheTags" xsi:type="array"> <item name="0" xsi:type="string">FPC</item> </data> - <data name="tag" xsi:type="string">to_maintain:yes</data> <constraint name="Magento\PageCache\Test\Constraint\AssertCacheIsRefreshableAndInvalidated" /> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertAdvancedSearchProductByAttribute" /> </variation> diff --git a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/NavigateMenuTest.xml index 659d76eabccbe..4a965d5708947 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/NavigateMenuTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest14"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Marketing > Catalog Price Rule</data> <data name="pageTitle" xsi:type="string">Catalog Price Rule</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/NavigateMenuTest.xml index 493d427dd0ac2..d9bb0f65e704e 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/NavigateMenuTest.xml @@ -8,11 +8,13 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest15"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Marketing > Search Terms</data> <data name="pageTitle" xsi:type="string">Search Terms</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest16"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > Search Terms</data> <data name="pageTitle" xsi:type="string">Search Terms Report</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/SearchEntityResultsTest.xml b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/SearchEntityResultsTest.xml index dc91608f361c2..42dd7b6c96e2e 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/SearchEntityResultsTest.xml +++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/SearchEntityResultsTest.xml @@ -8,53 +8,64 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\CatalogSearch\Test\TestCase\SearchEntityResultsTest" summary="Use Quick Search to Find Product" ticketId="MAGETWO-25095"> <variation name="SearchEntityResultsTestVariation1" summary="Use Quick Search to Find the Product" ticketId="MAGETWO-12420"> - <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test</data> + <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, mftf_migrated:yes</data> <data name="catalogSearch/data/query_text/value" xsi:type="string">catalogProductSimple::default::sku</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertProductCanBeOpenedFromSearchResult" /> </variation> <variation name="SearchEntityResultsTestVariation2" summary="Search simple product and add to cart" ticketId="MAGETWO-43235"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="catalogSearch/data/query_text/value" xsi:type="string">catalogProductSimple::default::simple</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertProductAddedToCartFromSearchResults" /> </variation> <variation name="SearchEntityResultsTestVariation3" summary="Search virtual product and add to cart" ticketId="MAGETWO-43235"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="catalogSearch/data/query_text/value" xsi:type="string">catalogProductVirtual::default::virtual</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertProductAddedToCartFromSearchResults" /> </variation> <variation name="SearchEntityResultsTestVariation4" summary="Search configurable product and add to cart" ticketId="MAGETWO-43235"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="catalogSearch/data/query_text/value" xsi:type="string">configurableProduct::default::configurable</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertProductAddedToCartFromSearchResults" /> </variation> <variation name="SearchEntityResultsTestVariation5" summary="Search downloadable product and add to cart" ticketId="MAGETWO-43235"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="catalogSearch/data/query_text/value" xsi:type="string">downloadableProduct::default::downloadable</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertProductAddedToCartFromSearchResults" /> </variation> <variation name="SearchEntityResultsTestVariation6" summary="Search grouped product and add to cart" ticketId="MAGETWO-43235"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="catalogSearch/data/query_text/value" xsi:type="string">groupedProduct::withSimpleProducts_without_qty::grouped</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertProductAddedToCartFromSearchResults" /> </variation> <variation name="SearchEntityResultsTestVariation7" summary="Search bundle dynamic product and add to cart" ticketId="MAGETWO-43235"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="catalogSearch/data/query_text/value" xsi:type="string">bundleProduct::bundle_dynamic_product::bundle</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertProductAddedToCartFromSearchResults" /> </variation> <variation name="SearchEntityResultsTestVariation8" summary="Search fixed product"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="catalogSearch/data/query_text/value" xsi:type="string">bundleProduct::bundle_fixed_product::bundle</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertCatalogSearchResult" /> </variation> <variation name="SearchEntityResultsTestVariation9"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="catalogSearch/data/query_text/value" xsi:type="string">catalogProductSimple::default::name</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertProductCanBeOpenedFromSearchResult" /> </variation> <variation name="SearchEntityResultsTestVariation10"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="catalogSearch/data/query_text/value" xsi:type="string">catalogProductSimple::product_with_special_symbols_in_name::name</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertProductCanBeOpenedFromSearchResult" /> </variation> <variation name="SearchEntityResultsTestVariation11"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="catalogSearch/data/query_text/search_query" xsi:type="string">TryToFindMeAndI'llFindYOU</data> <data name="catalogSearch/data/query_text/value" xsi:type="string">catalogProductSimple::default</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertCatalogSearchNoResultMessage" /> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertCatalogSearchNoResult" /> </variation> <variation name="SearchEntityResultsTestVariation12" summary="Search for simple product name using 2 symbols query length" ticketId="MAGETWO-36542"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="catalogSearch/data/query_text/value" xsi:type="string">catalogProductSimple::default::name</data> <data name="queryLength" xsi:type="string">2</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertProductCanBeOpenedFromSearchResult" /> @@ -66,6 +77,7 @@ <constraint name="Magento\CatalogSearch\Test\Constraint\AssertProductCanBeOpenedFromSearchResult" /> </variation> <variation name="SearchEntityResultsTestVariation14" summary="Search for simple product name using 128 symbols query length" ticketId="MAGETWO-36542"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="catalogSearch/data/query_text/value" xsi:type="string">catalogProductSimple::product_with_long_name::name</data> <data name="queryLength" xsi:type="string">128</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertProductCanBeOpenedFromSearchResult" /> @@ -80,6 +92,7 @@ <variation name="SearchEntityResultsTestVariation16" summary="Search for two simple products for text in attributes with same search weight and check their sort order" ticketId="MAGETWO-64501"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="catalogSearch/data/query_text/search_query" xsi:type="string">alaska</data> <data name="catalogSearch/data/query_text/value" xsi:type="array"> <item name="product_2" xsi:type="string">catalogProductSimple::search_weight_term_twice_weight_1</item> @@ -90,6 +103,7 @@ <variation name="SearchEntityResultsTestVariation17" summary="Search for two simple products for text in attributes with different search weight and check their sort order" ticketId="MAGETWO-64502"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="catalogSearch/data/query_text/search_query" xsi:type="string">alaska</data> <data name="catalogSearch/data/query_text/value" xsi:type="array"> <item name="product_1" xsi:type="string">catalogProductSimple::search_weight_term_once_weight_5</item> @@ -98,6 +112,7 @@ <constraint name="Magento\CatalogSearch\Test\Constraint\AssertCatalogSearchResultOrder" /> </variation> <variation name="SearchEntityResultsTestVariation18" summary="Search Configurable Product with Enabled and Disabled Children." ticketId="MAGETWO-69181"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="catalogSearch/data/query_text/value" xsi:type="string">configurableProduct::one_simple_product_not_visible_individually::name</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertCatalogSearchResult" /> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertConfigurableWithDisabledOptionCatalogSearchNoResult" /> diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Totals.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Totals.php index f632cdc3d7464..1d3950091d064 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Totals.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Totals.php @@ -117,6 +117,7 @@ public function getGrandTotal() */ public function getGrandTotalIncludingTax() { + $this->waitForGrandTotal(); $priceElement = $this->_rootElement->find($this->grandTotalInclTax, Locator::SELECTOR_CSS); return $priceElement->isVisible() ? $this->escapeCurrency($priceElement->getText()) : null; } diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Shipping.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Shipping.php index 0bf9b37c75e12..1013404f42df1 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Shipping.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Shipping.php @@ -102,7 +102,7 @@ class Shipping extends Form * * @var string */ - private $emailError = '#customer-email-error'; + private $emailError = '#checkout-customer-email-error'; /** * Get email error. diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Shipping.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Shipping.xml index 71115e402880c..0973b968cba95 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Shipping.xml +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Shipping.xml @@ -8,7 +8,7 @@ <mapping strict="0"> <fields> <email> - <selector>#customer-email</selector> + <selector>#checkout-customer-email</selector> </email> <firstname /> <lastname /> diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/AddProductsToShoppingCartEntityTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/AddProductsToShoppingCartEntityTest.xml index 8d054c0230873..c0df4b16b92e6 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/AddProductsToShoppingCartEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/AddProductsToShoppingCartEntityTest.xml @@ -22,7 +22,6 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertSubtotalInMiniShoppingCart" /> </variation> <variation name="AddProductsToShoppingCartEntityTestVariation2"> - <data name="tag" xsi:type="string">to_maintain:yes, severity:S2</data> <data name="productsData/0" xsi:type="string">bundleProduct::bundle_fixed_product</data> <data name="cart/data/grand_total" xsi:type="string">761</data> <data name="cart/data/subtotal" xsi:type="string">756</data> @@ -35,7 +34,6 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertSubtotalInMiniShoppingCart" /> </variation> <variation name="AddProductsToShoppingCartEntityTestVariation3"> - <data name="tag" xsi:type="string">to_maintain:yes, severity:S0</data> <data name="productsData/0" xsi:type="string">catalogProductSimple::with_two_custom_option</data> <data name="cart/data/grand_total" xsi:type="string">345</data> <data name="cart/data/subtotal" xsi:type="string">340</data> @@ -48,7 +46,6 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertSubtotalInMiniShoppingCart" /> </variation> <variation name="AddProductsToShoppingCartEntityTestVariation4"> - <data name="tag" xsi:type="string">to_maintain:yes, severity:S1</data> <data name="productsData/0" xsi:type="string">catalogProductVirtual::product_50_dollar</data> <data name="cart/data/grand_total" xsi:type="string">50</data> <data name="cart/data/subtotal" xsi:type="string">50</data> @@ -100,8 +97,7 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertSubtotalInMiniShoppingCart" /> </variation> <variation name="AddProductsToShoppingCartEntityTestVariation8" summary="Enable https in backend and add products of different types to cart" ticketId="MAGETWO-42677"> - <data name="tag" xsi:type="string">to_maintain:yes, severity:S0</data> - <data name="issue" xsi:type="string">Errors on configuration step. Skipped.</data> + <data name="tag" xsi:type="string">severity:S0</data> <data name="productsData/0" xsi:type="string">catalogProductSimple::with_two_custom_option</data> <data name="productsData/1" xsi:type="string">catalogProductVirtual::product_50_dollar</data> <data name="productsData/2" xsi:type="string">downloadableProduct::with_two_separately_links</data> diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml index 0edd8f4183f30..daf9aaae50580 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml @@ -15,13 +15,11 @@ <data name="shippingAddressCustomer" xsi:type="array"> <item name="added" xsi:type="number">1</item> </data> - <data name="billingAddressCustomer" xsi:type="array"> - <item name="added" xsi:type="number">1</item> - </data> <data name="prices" xsi:type="array"> <item name="grandTotal" xsi:type="string">565.00</item> </data> <data name="shipping/shipping_service" xsi:type="string">Flat Rate</data> + <data name="editBillingInformation" xsi:type="boolean">false</data> <data name="shipping/shipping_method" xsi:type="string">Fixed</data> <data name="payment/method" xsi:type="string">checkmo</data> <data name="configData" xsi:type="string">checkmo</data> @@ -43,6 +41,7 @@ <item name="grandTotal" xsi:type="string">565.00</item> </data> <data name="shipping/shipping_service" xsi:type="string">Flat Rate</data> + <data name="editBillingInformation" xsi:type="boolean">false</data> <data name="shipping/shipping_method" xsi:type="string">Fixed</data> <data name="payment/method" xsi:type="string">checkmo</data> <data name="configData" xsi:type="string">checkmo</data> @@ -61,7 +60,7 @@ <data name="shippingAddress/dataset" xsi:type="string">UK_address_without_email</data> <data name="shipping/shipping_service" xsi:type="string">Flat Rate</data> <data name="shipping/shipping_method" xsi:type="string">Fixed</data> - <data name="billingCheckboxState" xsi:type="string">Yes</data> + <data name="editBillingInformation" xsi:type="boolean">false</data> <data name="billingAddress/dataset" xsi:type="string">US_address_1_without_email</data> <data name="payment/method" xsi:type="string">checkmo</data> <data name="configData" xsi:type="string">checkmo</data> diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateProductFromMiniShoppingCartEntityTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateProductFromMiniShoppingCartEntityTest.xml index b4c97a11b9145..4b99de09f2a7b 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateProductFromMiniShoppingCartEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateProductFromMiniShoppingCartEntityTest.xml @@ -26,7 +26,7 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertCustomerIsRedirectedToCheckoutFromCart" /> </variation> <variation name="UpdateProductFromMiniShoppingCartEntityTestVariation2" summary="Update Configurable and verify previous product was updated to new one in shopping cart and mini shopping cart"> - <data name="tag" xsi:type="string">test_type:extended_acceptance_test, to_maintain:yes, severity:S0</data> + <data name="tag" xsi:type="string">test_type:extended_acceptance_test, severity:S0</data> <data name="originalProduct/0" xsi:type="string">configurableProduct::default</data> <data name="checkoutData/dataset" xsi:type="string">configurable_update_mini_shopping_cart</data> <constraint name="Magento\Checkout\Test\Constraint\AssertCartItemsOptions" /> @@ -35,7 +35,7 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertProductOptionsAbsentInShoppingCart" /> </variation> <variation name="UpdateProductFromMiniShoppingCartEntityTestVariation3" summary="Update Bundle and verify previous product was updated to new one in shopping cart and mini shopping cart"> - <data name="tag" xsi:type="string">test_type:extended_acceptance_test, to_maintain:yes, severity:S0</data> + <data name="tag" xsi:type="string">test_type:extended_acceptance_test, severity:S0</data> <data name="originalProduct/0" xsi:type="string">bundleProduct::bundle_fixed_product</data> <data name="checkoutData/dataset" xsi:type="string">bundle_update_mini_shopping_cart</data> <constraint name="Magento\Checkout\Test\Constraint\AssertCartItemsOptions" /> @@ -44,7 +44,7 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertProductOptionsAbsentInShoppingCart" /> </variation> <variation name="UpdateProductFromMiniShoppingCartEntityTestVariation4" summary="Update Downloadable and check previous link was updated to new one in shopping cart and mini shopping cart"> - <data name="tag" xsi:type="string">test_type:extended_acceptance_test, to_maintain:yes, severity:S1</data> + <data name="tag" xsi:type="string">test_type:extended_acceptance_test, severity:S1</data> <data name="originalProduct/0" xsi:type="string">downloadableProduct::with_two_separately_links</data> <data name="checkoutData/dataset" xsi:type="string">downloadable_update_mini_shopping_cart</data> <constraint name="Magento\Checkout\Test\Constraint\AssertCartItemsOptions" /> @@ -53,7 +53,7 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertProductOptionsAbsentInShoppingCart" /> </variation> <variation name="UpdateProductFromMiniShoppingCartEntityTestVariation5" summary="Update Virtual product in mini shopping cart"> - <data name="tag" xsi:type="string">test_type:extended_acceptance_test, to_maintain:yes, severity:S1</data> + <data name="tag" xsi:type="string">test_type:extended_acceptance_test, severity:S1</data> <data name="originalProduct/0" xsi:type="string">catalogProductVirtual::default</data> <data name="checkoutData/dataset" xsi:type="string">virtual_update_mini_shopping_cart</data> <constraint name="Magento\Checkout\Test\Constraint\AssertProductDataInMiniShoppingCart" /> diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/ValidateEmailOnCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/ValidateEmailOnCheckoutTest.xml index 90c42505585a2..8290d825593af 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/ValidateEmailOnCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/ValidateEmailOnCheckoutTest.xml @@ -10,17 +10,20 @@ <variation name="ValidateEmailOnCheckoutTestVariation1"> <data name="customer/data/email" xsi:type="string">johndoe</data> <data name="customer/data/firstname" xsi:type="string">John</data> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <constraint name="Magento\Checkout\Test\Constraint\AssertEmailErrorValidationMessage" /> <constraint name="Magento\Checkout\Test\Constraint\AssertEmailToolTips" /> </variation> <variation name="ValidateEmailOnCheckoutTestVariation2"> <data name="customer/data/email" xsi:type="string">johndoe#example.com</data> <data name="customer/data/firstname" xsi:type="string">John</data> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <constraint name="Magento\Checkout\Test\Constraint\AssertEmailErrorValidationMessage" /> </variation> <variation name="ValidateEmailOnCheckoutTestVariation3"> <data name="customer/data/email" xsi:type="string">johndoe@example.c</data> <data name="customer/data/firstname" xsi:type="string">John</data> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <constraint name="Magento\Checkout\Test\Constraint\AssertEmailErrorValidationMessage" /> </variation> </testCase> diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/FillBillingInformationStep.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/FillBillingInformationStep.php index aa7eba634145f..52b296c2e01fa 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/FillBillingInformationStep.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/FillBillingInformationStep.php @@ -126,11 +126,15 @@ public function run() if ($this->billingCheckboxState) { $this->assertBillingAddressCheckbox->processAssert($this->checkoutOnepage, $this->billingCheckboxState); } - if ($this->billingCheckboxState === 'Yes' && !$this->editBillingInformation) { - return [ - 'billingAddress' => $this->shippingAddress - ]; + + if (!$this->editBillingInformation) { + $billingAddress = $this->billingCheckboxState === 'Yes' + ? $this->shippingAddress + : $this->getDefaultBillingAddress(); + + return ['billingAddress' => $billingAddress]; } + if ($this->billingAddress) { $selectedPaymentMethod = $this->checkoutOnepage->getPaymentBlock()->getSelectedPaymentMethodBlock(); if ($this->shippingAddress) { @@ -139,9 +143,11 @@ public function run() $selectedPaymentMethod->getBillingBlock()->fillBilling($this->billingAddress); $billingAddress = $this->billingAddress; } + if (isset($this->billingAddressCustomer['added'])) { $addressIndex = $this->billingAddressCustomer['added']; - $billingAddress = $this->customer->getDataFieldConfig('address')['source']->getAddresses()[$addressIndex]; + $billingAddress = $this->customer->getDataFieldConfig('address')['source'] + ->getAddresses()[$addressIndex]; $address = $this->objectManager->create( \Magento\Customer\Test\Block\Address\Renderer::class, ['address' => $billingAddress, 'type' => 'html_for_select_element'] @@ -156,4 +162,25 @@ public function run() 'billingAddress' => $billingAddress ]; } + + /** + * Get default billing address + * + * @return Address|null + */ + private function getDefaultBillingAddress() + { + $addresses = $this->customer->hasData('address') + ? $this->customer->getDataFieldConfig('address')['source']->getAddresses() + : []; + $defaultAddress = null; + foreach ($addresses as $address) { + if ($address->getDefaultBilling() === 'Yes') { + $defaultAddress = $address; + break; + } + } + + return $defaultAddress; + } } diff --git a/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/TestCase/NavigateMenuTest.xml index 18cbf32ded5c1..3458e2944a9ec 100644 --- a/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/TestCase/NavigateMenuTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest17"> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2, mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Stores > Terms and Conditions</data> <data name="pageTitle" xsi:type="string">Terms and Conditions</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/NavigateMenuTest.xml index f9f0a11c0a475..cff5f7f2a5622 100644 --- a/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/NavigateMenuTest.xml @@ -8,13 +8,13 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest18"> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2, mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Content > Pages</data> <data name="pageTitle" xsi:type="string">Pages</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest19"> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2, mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Content > Blocks</data> <data name="pageTitle" xsi:type="string">Blocks</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/UpdateConfigurableProductEntityTest.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/UpdateConfigurableProductEntityTest.php index c7d66781738b7..bb88bc854f756 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/UpdateConfigurableProductEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/UpdateConfigurableProductEntityTest.php @@ -32,7 +32,6 @@ class UpdateConfigurableProductEntityTest extends Scenario { /* tags */ const MVP = 'yes'; - const TO_MAINTAIN = 'yes'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/CurrencySymbol/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/CurrencySymbol/Test/TestCase/NavigateMenuTest.xml index 0a061eb4be6c7..fc031a77ff53f 100644 --- a/dev/tests/functional/tests/app/Magento/CurrencySymbol/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/CurrencySymbol/Test/TestCase/NavigateMenuTest.xml @@ -8,11 +8,13 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest20"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Stores > Currency Rates</data> <data name="pageTitle" xsi:type="string">Currency Rates</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest21"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Stores > Currency Symbols</data> <data name="pageTitle" xsi:type="string">Currency Symbols</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/DeleteCustomerGroupEntityTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/DeleteCustomerGroupEntityTest.xml index cffbbca8ad5cb..4251a1c5ee9c5 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/DeleteCustomerGroupEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/DeleteCustomerGroupEntityTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Customer\Test\TestCase\DeleteCustomerGroupEntityTest" summary="Delete Customer Group" ticketId="MAGETWO-25243"> <variation name="DeleteCustomerGroupEntityTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="customer/dataset" xsi:type="string">customer_with_new_customer_group</data> <data name="customer/data/group_id/dataset" xsi:type="string">default</data> <data name="defaultCustomerGroup/dataset" xsi:type="string">General</data> diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/NavigateMenuTest.xml index 404e62dcad648..a4a3aa34f9f1c 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/NavigateMenuTest.xml @@ -8,16 +8,19 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest22"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Customers > All Customers</data> <data name="pageTitle" xsi:type="string">Customers</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest23"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Customers > Now Online</data> <data name="pageTitle" xsi:type="string">Customers Now Online</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest24"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Customers > Customer Groups</data> <data name="pageTitle" xsi:type="string">Customer Groups</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/CustomerImportExport/Test/TestCase/ExportCustomerAddressesTest.php b/dev/tests/functional/tests/app/Magento/CustomerImportExport/Test/TestCase/ExportCustomerAddressesTest.php index 6b92891ada2b4..17dfb4fb8cdaf 100644 --- a/dev/tests/functional/tests/app/Magento/CustomerImportExport/Test/TestCase/ExportCustomerAddressesTest.php +++ b/dev/tests/functional/tests/app/Magento/CustomerImportExport/Test/TestCase/ExportCustomerAddressesTest.php @@ -87,7 +87,8 @@ public function test( $exportData->persist(); $this->adminExportIndex->getExportForm()->fill($exportData); $this->adminExportIndex->getFilterExport()->clickContinue(); - + $this->cron->run(); + $this->cron->run(); return [ 'customer' => $customer ]; diff --git a/dev/tests/functional/tests/app/Magento/Email/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Email/Test/TestCase/NavigateMenuTest.xml index f6dcdd5b65d02..4e98019c25317 100644 --- a/dev/tests/functional/tests/app/Magento/Email/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Email/Test/TestCase/NavigateMenuTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest29"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Marketing > Email Templates</data> <data name="pageTitle" xsi:type="string">Email Templates</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/CreateGroupedProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/CreateGroupedProductEntityTest.xml index 39f4fd08bb922..f397c1b99e3b2 100644 --- a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/CreateGroupedProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/CreateGroupedProductEntityTest.xml @@ -57,7 +57,6 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductOutOfStock" /> </variation> <variation name="CreateGroupedProductEntityTestVariation5" summary="Create with no required products"> - <data name="tag" xsi:type="string">stable:no</data> <data name="product/data/url_key" xsi:type="string">test-grouped-product-%isolation%</data> <data name="product/data/name" xsi:type="string">GroupedProduct %isolation%</data> <data name="product/data/sku" xsi:type="string">GroupedProduct_sku%isolation%</data> @@ -110,7 +109,6 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductPage" /> </variation> <variation name="CreateGroupedProductEntityTestVariation10" summary="Create Grouped Product and Assign it on Custom Website"> - <data name="tag" xsi:type="string">stable:no</data> <data name="product/data/url_key" xsi:type="string">test-grouped-product-%isolation%</data> <data name="product/data/name" xsi:type="string">GroupedProduct %isolation%</data> <data name="product/data/sku" xsi:type="string">GroupedProduct_sku%isolation%</data> diff --git a/dev/tests/functional/tests/app/Magento/ImportExport/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/ImportExport/Test/TestCase/NavigateMenuTest.xml index 6ce4d01d177db..d396a364a3f42 100644 --- a/dev/tests/functional/tests/app/Magento/ImportExport/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/ImportExport/Test/TestCase/NavigateMenuTest.xml @@ -8,11 +8,13 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest35"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">System > Import</data> <data name="pageTitle" xsi:type="string">Import</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest36"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">System > Export</data> <data name="pageTitle" xsi:type="string">Export</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/Indexer/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Indexer/Test/TestCase/NavigateMenuTest.xml index 883e9bde47bf8..16ae092e62cad 100644 --- a/dev/tests/functional/tests/app/Magento/Indexer/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Indexer/Test/TestCase/NavigateMenuTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest37"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">System > Index Management</data> <data name="pageTitle" xsi:type="string">Index Management</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/NavigateMenuTest.xml index 22def36751f53..265790ed4b763 100644 --- a/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/NavigateMenuTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest38"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">System > Integrations</data> <data name="pageTitle" xsi:type="string">Integrations</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/Newsletter/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Newsletter/Test/TestCase/NavigateMenuTest.xml index 13843a50868e7..b1a6b3e0c4386 100644 --- a/dev/tests/functional/tests/app/Magento/Newsletter/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Newsletter/Test/TestCase/NavigateMenuTest.xml @@ -8,21 +8,25 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest46"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Marketing > Newsletter Template</data> <data name="pageTitle" xsi:type="string">Newsletter Templates</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest47"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Marketing > Newsletter Queue</data> <data name="pageTitle" xsi:type="string">Newsletter Queue</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest48"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Marketing > Newsletter Subscribers</data> <data name="pageTitle" xsi:type="string">Newsletter Subscribers</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest49"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > Newsletter Problem Reports</data> <data name="pageTitle" xsi:type="string">Newsletter Problems Report</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/NavigateMenuTest.xml index d7d031f559f82..114235e75524f 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/NavigateMenuTest.xml @@ -8,13 +8,13 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest" summary="Navigate through admin menu" ticketId="MAGETWO-34874"> <variation name="NavigateMenuTest50"> - <data name="tag" xsi:type="string">severity:S0</data> + <data name="tag" xsi:type="string">severity:S0, mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > PayPal Settlement</data> <data name="pageTitle" xsi:type="string">PayPal Settlement Reports</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest51"> - <data name="tag" xsi:type="string">severity:S0</data> + <data name="tag" xsi:type="string">severity:S0, mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Sales > Billing Agreements</data> <data name="pageTitle" xsi:type="string">Billing Agreements</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/Reports/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Reports/Test/TestCase/NavigateMenuTest.xml index 08cce1ffe7d23..4e3cd1824767a 100644 --- a/dev/tests/functional/tests/app/Magento/Reports/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Reports/Test/TestCase/NavigateMenuTest.xml @@ -8,76 +8,91 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest" summary="Navigate through Reports admin menu"> <variation name="NavigateMenuTest55"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > Products in Cart</data> <data name="pageTitle" xsi:type="string">Products in Carts</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> <variation name="NavigateMenuTest56"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > Abandoned Carts</data> <data name="pageTitle" xsi:type="string">Abandoned Carts</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> <variation name="NavigateMenuTest57"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > Orders</data> <data name="pageTitle" xsi:type="string">Orders Report</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> <variation name="NavigateMenuTest58"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > Tax</data> <data name="pageTitle" xsi:type="string">Tax Report</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> <variation name="NavigateMenuTest59"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > Invoiced</data> <data name="pageTitle" xsi:type="string">Invoice Report</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> <variation name="NavigateMenuTest60"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > Coupons</data> <data name="pageTitle" xsi:type="string">Coupons Report</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> <variation name="NavigateMenuTest61"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > Order Total</data> <data name="pageTitle" xsi:type="string">Order Total Report</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> <variation name="NavigateMenuTest62"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > Order Count</data> <data name="pageTitle" xsi:type="string">Order Count Report</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> <variation name="NavigateMenuTest63"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > New</data> <data name="pageTitle" xsi:type="string">New Accounts Report</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> <variation name="NavigateMenuTest64"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > Views</data> <data name="pageTitle" xsi:type="string">Product Views Report</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> <variation name="NavigateMenuTest65"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > Bestsellers</data> <data name="pageTitle" xsi:type="string">Bestsellers Report</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> <variation name="NavigateMenuTest66"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > Low Stock</data> <data name="pageTitle" xsi:type="string">Low Stock Report</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> <variation name="NavigateMenuTest67"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > Ordered</data> <data name="pageTitle" xsi:type="string">Ordered Products Report</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> <variation name="NavigateMenuTest68"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > Downloads</data> <data name="pageTitle" xsi:type="string">Downloads Report</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> <variation name="NavigateMenuTest69"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > Refresh Statistics</data> <data name="pageTitle" xsi:type="string">Refresh Statistics</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> diff --git a/dev/tests/functional/tests/app/Magento/Review/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Review/Test/TestCase/NavigateMenuTest.xml index 4d96ebd2edbe3..334497cc2f77e 100644 --- a/dev/tests/functional/tests/app/Magento/Review/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Review/Test/TestCase/NavigateMenuTest.xml @@ -8,21 +8,25 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest70"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Marketing > All Reviews</data> <data name="pageTitle" xsi:type="string">Reviews</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest71"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > By Customers</data> <data name="pageTitle" xsi:type="string">Customer Reviews Report</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest72"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Reports > By Products</data> <data name="pageTitle" xsi:type="string">Product Reviews Report</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest73"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Stores > Rating</data> <data name="pageTitle" xsi:type="string">Ratings</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create.php index 14bc04cfed70c..f460cd91167de 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create.php @@ -105,7 +105,7 @@ class Create extends Block * * @var string */ - protected $orderMethodsSelector = '#shipping-methods'; + protected $orderMethodsSelector = '#order-methods'; /** * Page header. diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CancelCreatedOrderTest.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CancelCreatedOrderTest.xml index e45609bb90c83..fdb396bbbd052 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CancelCreatedOrderTest.xml +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CancelCreatedOrderTest.xml @@ -18,7 +18,6 @@ <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGridOnFrontend" /> </variation> <variation name="CancelCreatedOrderTestVariationWithZeroSubtotalCheckout" summary="Cancel order with zero subtotal checkout payment method and check status on storefront"> - <data name="tag" xsi:type="string">stable:no</data> <data name="order/dataset" xsi:type="string">default</data> <data name="order/data/payment_auth_expiration/method" xsi:type="string">free</data> <data name="order/data/shipping_method" xsi:type="string">freeshipping_freeshipping</data> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateOrderBackendPartOneTest.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateOrderBackendPartOneTest.xml index 3e7b8fad1f04d..880c66483d5f2 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateOrderBackendPartOneTest.xml +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateOrderBackendPartOneTest.xml @@ -8,7 +8,6 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Sales\Test\TestCase\CreateOrderBackendPartOneTest" summary="Create Order from Admin within Offline Payment Methods" ticketId="MAGETWO-28696"> <variation name="CreateOrderBackendTestVariation1" ticketId="MAGETWO-17063"> - <data name="issue" xsi:type="string">https://github.com/magento-engcom/msi/issues/1624</data> <data name="description" xsi:type="string">Create order with simple product for registered US customer using Fixed shipping method and Cash on Delivery payment method</data> <data name="products/0" xsi:type="string">catalogProductSimple::with_one_custom_option</data> <data name="customer/dataset" xsi:type="string">default</data> @@ -70,7 +69,6 @@ <constraint name="Magento\Sales\Test\Constraint\AssertOrderByDateInOrdersGrid" /> </variation> <variation name="CreateOrderBackendTestVariation4"> - <data name="tag" xsi:type="string">to_maintain:yes</data> <data name="description" xsi:type="string">Create order with virtual product for registered UK customer using Bank Transfer payment method</data> <data name="products/0" xsi:type="string">catalogProductVirtual::default</data> <data name="customer/dataset" xsi:type="string">default</data> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/NavigateMenuTest.xml index 316ba33f19fdb..5cc673f4b4fa5 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/NavigateMenuTest.xml @@ -8,31 +8,37 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest77"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Sales > Orders</data> <data name="pageTitle" xsi:type="string">Orders</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest78"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Sales > Invoices</data> <data name="pageTitle" xsi:type="string">Invoices</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest79"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Sales > Shipments</data> <data name="pageTitle" xsi:type="string">Shipments</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest80"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Sales > Credit Memos</data> <data name="pageTitle" xsi:type="string">Credit Memos</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest81"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Sales > Transactions</data> <data name="pageTitle" xsi:type="string">Transactions</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest82"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Stores > Order Status</data> <data name="pageTitle" xsi:type="string">Order Status</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/NavigateMenuTest.xml index 0a100eabcff46..1eeaeaaa483c0 100644 --- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/NavigateMenuTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest83"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Marketing > Cart Price Rules</data> <data name="pageTitle" xsi:type="string">Cart Price Rules</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenCreatingNewUserTest.xml b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenCreatingNewUserTest.xml index c4cfe3f7f274c..3bb370a15e977 100644 --- a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenCreatingNewUserTest.xml +++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenCreatingNewUserTest.xml @@ -9,7 +9,7 @@ <testCase name="Magento\Security\Test\TestCase\LockAdminUserWhenCreatingNewUserTest" summary="Lock admin user after entering incorrect password while creating new User"> <variation name="LockAdminUserWhenCreatingNewUserTestVariation1"> <data name="configData" xsi:type="string">user_lockout_failures</data> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2,mftf_migrated:yes</data> <data name="customAdmin/dataset" xsi:type="string">custom_admin_with_default_role</data> <data name="user/data/username" xsi:type="string">AdminUser%isolation%</data> <data name="user/data/firstname" xsi:type="string">FirstName%isolation%</data> diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/ResetUserPasswordFailedTest.xml b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/ResetUserPasswordFailedTest.xml index 55f1b69504363..f43469358aa9c 100644 --- a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/ResetUserPasswordFailedTest.xml +++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/ResetUserPasswordFailedTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Security\Test\TestCase\ResetUserPasswordFailedTest" summary="Prevent Locked Admin User to Log In into the Backend"> <variation name="ResetUserPasswordTestVariation1"> - <data name="tag" xsi:type="string">severity:S1</data> + <data name="tag" xsi:type="string">severity:S1,mftf_migrated:yes</data> <data name="customAdmin/dataset" xsi:type="string">custom_admin_with_default_role</data> <data name="attempts" xsi:type="string">2</data> <constraint name="Magento\Security\Test\Constraint\AssertUserPasswordResetFailed" /> diff --git a/dev/tests/functional/tests/app/Magento/Sitemap/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Sitemap/Test/TestCase/NavigateMenuTest.xml index cbef8fd52fd80..0f9514ffc7a00 100644 --- a/dev/tests/functional/tests/app/Magento/Sitemap/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Sitemap/Test/TestCase/NavigateMenuTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest85"> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2, mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Marketing > Site Map</data> <data name="pageTitle" xsi:type="string">Site Map</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/AccessAdminWithStoreCodeInUrlTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/AccessAdminWithStoreCodeInUrlTest.xml index a4abfc5daf29e..4d3677076d303 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/AccessAdminWithStoreCodeInUrlTest.xml +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/AccessAdminWithStoreCodeInUrlTest.xml @@ -11,6 +11,7 @@ <data name="configData" xsi:type="string">add_store_code_to_urls</data> <data name="user/dataset" xsi:type="string">default</data> <data name="storeCode" xsi:type="string">default</data> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <constraint name="Magento\User\Test\Constraint\AssertUserSuccessLogin" /> <constraint name="Magento\Store\Test\Constraint\AssertStoreCodeInUrl" /> </variation> diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreGroupEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreGroupEntityTest.xml index 8cdeb48bb1c98..dcfe22eb0d5f8 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreGroupEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreGroupEntityTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Store\Test\TestCase\CreateStoreGroupEntityTest" summary="Create Store Group" ticketId="MAGETWO-27345"> <variation name="CreateStoreGroupEntityTestVariation1"> - <data name="tag" xsi:type="string">severity:S1</data> + <data name="tag" xsi:type="string">severity:S1, mftf_migrated:yes</data> <data name="storeGroup/data/website_id/dataset" xsi:type="string">main_website</data> <data name="storeGroup/data/name" xsi:type="string">store_name_%isolation%</data> <data name="storeGroup/data/code" xsi:type="string">store_code_%isolation%</data> @@ -18,7 +18,7 @@ <constraint name="Magento\Store\Test\Constraint\AssertStoreGroupOnStoreViewForm" /> </variation> <variation name="CreateStoreGroupEntityTestVariation2"> - <data name="tag" xsi:type="string">severity:S1</data> + <data name="tag" xsi:type="string">severity:S1, mftf_migrated:yes</data> <data name="storeGroup/data/website_id/dataset" xsi:type="string">custom_website</data> <data name="storeGroup/data/name" xsi:type="string">store_name_%isolation%</data> <data name="storeGroup/data/code" xsi:type="string">store_code_%isolation%</data> @@ -29,6 +29,7 @@ <constraint name="Magento\Store\Test\Constraint\AssertStoreGroupOnStoreViewForm" /> </variation> <variation name="CreateStoreGroupEntityTestVariation3" summary="Check the absence of delete button" ticketId="MAGETWO-17475"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="storeGroup/dataset" xsi:type="string">custom_new_group</data> <constraint name="Magento\Store\Test\Constraint\AssertStoreGroupSuccessSaveMessage" /> <constraint name="Magento\Store\Test\Constraint\AssertStoreGroupForm" /> diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/MoveStoreToOtherGroupSameWebsiteTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/MoveStoreToOtherGroupSameWebsiteTest.xml index 8da208bc3f0f1..ab702dfdd4b78 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/MoveStoreToOtherGroupSameWebsiteTest.xml +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/MoveStoreToOtherGroupSameWebsiteTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Store\Test\TestCase\MoveStoreToOtherGroupSameWebsiteTest" summary="Move Store View" ticketId="MAGETWO-58361"> <variation name="MoveStoreTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="storeInitialA/dataset" xsi:type="string">custom_group_custom_store</data> <data name="storeInitialB/dataset" xsi:type="string">custom_group_custom_store</data> <constraint name="Magento\Store\Test\Constraint\AssertStoreSuccessSaveMessage" /> diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreEntityTest.xml index 4a73986673c60..6053352386203 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreEntityTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Store\Test\TestCase\UpdateStoreEntityTest" summary="Update Store View" ticketId="MAGETWO-27786"> <variation name="UpdateStoreEntityTestVariation1"> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2, mftf_migrated:yes</data> <data name="storeInitial/dataset" xsi:type="string">custom</data> <data name="store/data/group_id/dataset" xsi:type="string">default</data> <data name="store/data/name" xsi:type="string">storename_updated%isolation%</data> @@ -21,7 +21,7 @@ <constraint name="Magento\Store\Test\Constraint\AssertStoreFrontend" /> </variation> <variation name="UpdateStoreEntityTestVariation2"> - <data name="tag" xsi:type="string">severity:S1</data> + <data name="tag" xsi:type="string">severity:S1, mftf_migrated:yes</data> <data name="storeInitial/dataset" xsi:type="string">default</data> <data name="store/data/name" xsi:type="string">storename_updated%isolation%</data> <constraint name="Magento\Store\Test\Constraint\AssertStoreSuccessSaveMessage" /> diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreGroupEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreGroupEntityTest.xml index 593d9c365a92e..9ff67a9ba9fec 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreGroupEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreGroupEntityTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Store\Test\TestCase\UpdateStoreGroupEntityTest" summary="Update Store Group" ticketId="MAGETWO-27568"> <variation name="UpdateStoreGroupEntityTestVariation1"> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2, mftf_migrated:yes</data> <data name="storeGroupOrigin/dataset" xsi:type="string">custom</data> <data name="storeGroup/data/website_id/dataset" xsi:type="string">main_website</data> <data name="storeGroup/data/name" xsi:type="string">store_name_updated_%isolation%</data> @@ -20,7 +20,7 @@ <constraint name="Magento\Store\Test\Constraint\AssertStoreGroupOnStoreViewForm" /> </variation> <variation name="UpdateStoreGroupEntityTestVariation2"> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2, mftf_migrated:yes</data> <data name="storeGroupOrigin/dataset" xsi:type="string">custom</data> <data name="storeGroup/data/website_id/dataset" xsi:type="string">custom_website</data> <data name="storeGroup/data/name" xsi:type="string">store_name_updated_%isolation%</data> diff --git a/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/NavigateMenuTest.xml index ee225f21462ea..ecf202ddae3a8 100644 --- a/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/NavigateMenuTest.xml @@ -8,16 +8,19 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest87"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Stores > Tax Rules</data> <data name="pageTitle" xsi:type="string">Tax Rules</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest88"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Stores > Tax Zones and Rates</data> <data name="pageTitle" xsi:type="string">Tax Zones and Rates</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest89"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">System > Import/Export Tax Rates</data> <data name="pageTitle" xsi:type="string">Import and Export Tax Rates</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/TaxWithCrossBorderTest.php b/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/TaxWithCrossBorderTest.php index 0f163933d260c..40d3401a207a4 100644 --- a/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/TaxWithCrossBorderTest.php +++ b/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/TaxWithCrossBorderTest.php @@ -37,7 +37,6 @@ class TaxWithCrossBorderTest extends Injectable { /* tags */ const MVP = 'yes'; - const STABLE = 'no'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Theme/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Theme/Test/TestCase/NavigateMenuTest.xml index 71964fc926499..ee38659122301 100644 --- a/dev/tests/functional/tests/app/Magento/Theme/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Theme/Test/TestCase/NavigateMenuTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest90"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Content > Themes</data> <data name="pageTitle" xsi:type="string">Themes</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/TestCase/NavigateMenuTest.xml index cdf1d9e3617d8..30d9e57602af1 100644 --- a/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/TestCase/NavigateMenuTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest91"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Marketing > URL Rewrites</data> <data name="pageTitle" xsi:type="string">URL Rewrites</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertAccessTokensErrorRevokeMessage.php b/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertAccessTokensErrorRevokeMessage.php deleted file mode 100644 index b1a64c7c7e713..0000000000000 --- a/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertAccessTokensErrorRevokeMessage.php +++ /dev/null @@ -1,46 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\User\Test\Constraint; - -use Magento\User\Test\Page\Adminhtml\UserEdit; -use Magento\Mtf\Constraint\AbstractConstraint; - -/** - * Class AssertAccessTokensErrorRevokeMessage - * Assert that error message appears after click on 'Force Sing-In' button for user without tokens. - */ -class AssertAccessTokensErrorRevokeMessage extends AbstractConstraint -{ - /** - * User revoke tokens error message. - */ - const ERROR_MESSAGE = 'This user has no tokens.'; - - /** - * Assert that error message appears after click on 'Force Sing-In' button for user without tokens. - * - * @param UserEdit $userEdit - * @return void - */ - public function processAssert(UserEdit $userEdit) - { - \PHPUnit\Framework\Assert::assertEquals( - self::ERROR_MESSAGE, - $userEdit->getMessagesBlock()->getErrorMessage() - ); - } - - /** - * Return string representation of object - * - * @return string - */ - public function toString() - { - return self::ERROR_MESSAGE . ' error message is present on UserEdit page.'; - } -} diff --git a/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertAccessTokensSuccessfullyRevoked.php b/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertAccessTokensSuccessfullyRevoked.php new file mode 100644 index 0000000000000..b2e52f6a15a10 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertAccessTokensSuccessfullyRevoked.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\User\Test\Constraint; + +use Magento\User\Test\Page\Adminhtml\UserEdit; +use Magento\Mtf\Constraint\AbstractConstraint; + +/** + * Assert that success message appears after click on 'Force Sing-In' button. + */ +class AssertAccessTokensSuccessfullyRevoked extends AbstractConstraint +{ + /** + * User revoke tokens success message. + */ + const SUCCESS_MESSAGE = 'You have revoked the user\'s tokens.'; + + /** + * Assert that success message appears after click on 'Force Sing-In' button. + * + * @param UserEdit $userEdit + * @return void + */ + public function processAssert(UserEdit $userEdit): void + { + \PHPUnit\Framework\Assert::assertEquals( + self::SUCCESS_MESSAGE, + $userEdit->getMessagesBlock()->getSuccessMessage() + ); + } + + /** + * Return string representation of object + * + * @return string + */ + public function toString() + { + return self::SUCCESS_MESSAGE . ' message is present on UserEdit page.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/NavigateMenuTest.xml index d196bebca0e9a..4572738b6cb48 100644 --- a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/NavigateMenuTest.xml @@ -8,21 +8,25 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest52"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">System > Locked Users</data> <data name="pageTitle" xsi:type="string">Locked Users</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest53"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">System > Manage Encryption Key</data> <data name="pageTitle" xsi:type="string">Encryption Key</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest92"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">System > All Users</data> <data name="pageTitle" xsi:type="string">Users</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> </variation> <variation name="NavigateMenuTest93"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">System > User Roles</data> <data name="pageTitle" xsi:type="string">Roles</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/RevokeAllAccessTokensForAdminWithoutTokensTest.xml b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/RevokeAllAccessTokensForAdminWithoutTokensTest.xml index e5fcba9b72c25..afdb72d8c561e 100644 --- a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/RevokeAllAccessTokensForAdminWithoutTokensTest.xml +++ b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/RevokeAllAccessTokensForAdminWithoutTokensTest.xml @@ -9,7 +9,7 @@ <testCase name="Magento\User\Test\TestCase\RevokeAllAccessTokensForAdminWithoutTokensTest" summary="Revoke All Access Tokens for Admin without Tokens" ticketId="MAGETWO-29675"> <variation name="RevokeAllAccessTokensForAdminWithoutTokensTestVariation1"> <data name="user/dataset" xsi:type="string">custom_admin</data> - <constraint name="Magento\User\Test\Constraint\AssertAccessTokensErrorRevokeMessage" /> + <constraint name="Magento\User\Test\Constraint\AssertAccessTokensSuccessfullyRevoked" /> </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Variable/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Variable/Test/TestCase/NavigateMenuTest.xml index 3fceddf1a807a..0a9c74cd92bc6 100644 --- a/dev/tests/functional/tests/app/Magento/Variable/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Variable/Test/TestCase/NavigateMenuTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest94"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">System > Custom Variables</data> <data name="pageTitle" xsi:type="string">Custom Variables</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> diff --git a/dev/tests/functional/tests/app/Magento/Widget/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Widget/Test/TestCase/NavigateMenuTest.xml index 6b3215dd30d16..6a2a533c59df2 100644 --- a/dev/tests/functional/tests/app/Magento/Widget/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Widget/Test/TestCase/NavigateMenuTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest"> <variation name="NavigateMenuTest96"> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2, mftf_migrated:yes</data> <data name="menuItem" xsi:type="string">Content > Widgets</data> <data name="pageTitle" xsi:type="string">Widgets</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> diff --git a/dev/tests/functional/utils/authenticate.php b/dev/tests/functional/utils/authenticate.php deleted file mode 100644 index 15851f6e8000a..0000000000000 --- a/dev/tests/functional/utils/authenticate.php +++ /dev/null @@ -1,29 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -/** - * Check if token passed in is a valid auth token. - * - * @param string $token - * @return bool - */ -function authenticate($token) -{ - require_once __DIR__ . '/../../../../app/bootstrap.php'; - - $magentoObjectManagerFactory = \Magento\Framework\App\Bootstrap::createObjectManagerFactory(BP, $_SERVER); - $magentoObjectManager = $magentoObjectManagerFactory->create($_SERVER); - $tokenModel = $magentoObjectManager->get(\Magento\Integration\Model\Oauth\Token::class); - - $tokenPassedIn = $token; - // Token returned will be null if the token we passed in is invalid - $tokenFromMagento = $tokenModel->loadByToken($tokenPassedIn)->getToken(); - if (!empty($tokenFromMagento) && ($tokenFromMagento == $tokenPassedIn)) { - return true; - } else { - return false; - } -} diff --git a/dev/tests/functional/utils/command.php b/dev/tests/functional/utils/command.php index 4e18598a935ad..99025dd1cffcc 100644 --- a/dev/tests/functional/utils/command.php +++ b/dev/tests/functional/utils/command.php @@ -3,25 +3,26 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -include __DIR__ . '/authenticate.php'; + +// phpcs:ignore Magento2.Security.IncludeFile require_once __DIR__ . '/../../../../app/bootstrap.php'; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\NullOutput; -if (!empty($_POST['token']) && !empty($_POST['command'])) { - if (authenticate(urldecode($_POST['token']))) { - $command = urldecode($_POST['command']); - $magentoObjectManagerFactory = \Magento\Framework\App\Bootstrap::createObjectManagerFactory(BP, $_SERVER); - $magentoObjectManager = $magentoObjectManagerFactory->create($_SERVER); - $cli = $magentoObjectManager->create(\Magento\Framework\Console\Cli::class); - $input = new StringInput(escapeshellcmd($command)); - $input->setInteractive(false); - $output = new NullOutput(); - $cli->doRun($input, $output); - } else { - echo "Command not unauthorized."; - } +// phpcs:ignore Magento2.Security.Superglobal +if (isset($_GET['command'])) { + // phpcs:ignore Magento2.Security.Superglobal + $command = urldecode($_GET['command']); + // phpcs:ignore Magento2.Security.Superglobal + $magentoObjectManagerFactory = \Magento\Framework\App\Bootstrap::createObjectManagerFactory(BP, $_SERVER); + // phpcs:ignore Magento2.Security.Superglobal + $magentoObjectManager = $magentoObjectManagerFactory->create($_SERVER); + $cli = $magentoObjectManager->create(\Magento\Framework\Console\Cli::class); + $input = new StringInput($command); + $input->setInteractive(false); + $output = new NullOutput(); + $cli->doRun($input, $output); } else { - echo "'token' or 'command' parameter is not set."; + throw new \InvalidArgumentException("Command GET parameter is not set."); } diff --git a/dev/tests/functional/utils/deleteMagentoGeneratedCode.php b/dev/tests/functional/utils/deleteMagentoGeneratedCode.php index 17e3575c87686..17260bd1da635 100644 --- a/dev/tests/functional/utils/deleteMagentoGeneratedCode.php +++ b/dev/tests/functional/utils/deleteMagentoGeneratedCode.php @@ -3,14 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -include __DIR__ . '/authenticate.php'; -if (!empty($_POST['token']) && !empty($_POST['path'])) { - if (authenticate(urldecode($_POST['token']))) { - exec('rm -rf ../../../../generated/*'); - } else { - echo "Command not unauthorized."; - } -} else { - echo "'token' parameter is not set."; -} +// phpcs:ignore Magento2.Security.InsecureFunction +exec('rm -rf ../../../../generated/*'); diff --git a/dev/tests/functional/utils/export.php b/dev/tests/functional/utils/export.php index e3eff6e3fec17..fa50bc729d0f6 100644 --- a/dev/tests/functional/utils/export.php +++ b/dev/tests/functional/utils/export.php @@ -3,30 +3,32 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -include __DIR__ . '/authenticate.php'; -if (!empty($_POST['token']) && !empty($_POST['template'])) { - if (authenticate(urldecode($_POST['token']))) { - $varDir = '../../../../var/export/'; - $template = urldecode($_POST['template']); - $fileList = scandir($varDir, SCANDIR_SORT_NONE); - $files = []; +// phpcs:ignore Magento2.Security.Superglobal +if (!isset($_GET['template'])) { + // phpcs:ignore Magento2.Exceptions.DirectThrow + throw new \InvalidArgumentException('Argument "template" must be set.'); +} - foreach ($fileList as $fileName) { - if (preg_match("`$template`", $fileName) === 1) { - $filePath = $varDir . $fileName; - $files[] = [ - 'content' => file_get_contents($filePath), - 'name' => $fileName, - 'date' => filectime($filePath), - ]; - } - } +$varDir = '../../../../var/export/'; +// phpcs:ignore Magento2.Security.Superglobal +$template = urldecode($_GET['template']); +// phpcs:ignore Magento2.Functions.DiscouragedFunction +$fileList = scandir($varDir, SCANDIR_SORT_NONE); +$files = []; - echo serialize($files); - } else { - echo "Command not unauthorized."; +foreach ($fileList as $fileName) { + if (preg_match("`$template`", $fileName) === 1) { + $filePath = $varDir . $fileName; + $files[] = [ + // phpcs:ignore Magento2.Functions.DiscouragedFunction + 'content' => file_get_contents($filePath), + 'name' => $fileName, + // phpcs:ignore Magento2.Functions.DiscouragedFunction + 'date' => filectime($filePath), + ]; } -} else { - echo "'token' or 'template' parameter is not set."; } + +// phpcs:ignore Magento2.Security.LanguageConstruct, Magento2.Security.InsecureFunction +echo serialize($files); diff --git a/dev/tests/functional/utils/locales.php b/dev/tests/functional/utils/locales.php index a3b4ec05eed65..11e1e2b70fa50 100644 --- a/dev/tests/functional/utils/locales.php +++ b/dev/tests/functional/utils/locales.php @@ -3,23 +3,20 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -include __DIR__ . '/authenticate.php'; -if (!empty($_POST['token'])) { - if (authenticate(urldecode($_POST['token']))) { - if ($_POST['type'] == 'deployed') { - $themePath = isset($_POST['theme_path']) ? $_POST['theme_path'] : 'adminhtml/Magento/backend'; - $directory = __DIR__ . '/../../../../pub/static/' . $themePath; - $locales = array_diff(scandir($directory), ['..', '.']); - } else { - require_once __DIR__ . DIRECTORY_SEPARATOR . 'bootstrap.php'; - $localeConfig = $magentoObjectManager->create(\Magento\Framework\Locale\Config::class); - $locales = $localeConfig->getAllowedLocales(); - } - echo implode('|', $locales); - } else { - echo "Command not unauthorized."; - } +// phpcs:ignore Magento2.Security.Superglobal +if (isset($_GET['type']) && $_GET['type'] == 'deployed') { + // phpcs:ignore Magento2.Security.Superglobal + $themePath = isset($_GET['theme_path']) ? $_GET['theme_path'] : 'adminhtml/Magento/backend'; + $directory = __DIR__ . '/../../../../pub/static/' . $themePath; + // phpcs:ignore Magento2.Functions.DiscouragedFunction + $locales = array_diff(scandir($directory), ['..', '.']); } else { - echo "'token' parameter is not set."; + // phpcs:ignore Magento2.Security.IncludeFile + require_once __DIR__ . DIRECTORY_SEPARATOR . 'bootstrap.php'; + $localeConfig = $magentoObjectManager->create(\Magento\Framework\Locale\Config::class); + $locales = $localeConfig->getAllowedLocales(); } + +// phpcs:ignore Magento2.Security.LanguageConstruct +echo implode('|', $locales); diff --git a/dev/tests/functional/utils/log.php b/dev/tests/functional/utils/log.php index 889056bfbdd63..30783ae8e1d28 100644 --- a/dev/tests/functional/utils/log.php +++ b/dev/tests/functional/utils/log.php @@ -3,20 +3,21 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -declare(strict_types=1); -include __DIR__ . '/authenticate.php'; -if (!empty($_POST['token']) && !empty($_POST['name'])) { - if (authenticate(urldecode($_POST['token']))) { - $name = urldecode($_POST['name']); - if (preg_match('/\.\.(\\\|\/)/', $name)) { - throw new \InvalidArgumentException('Invalid log file name'); - } +declare(strict_types=1); +// phpcs:ignore Magento2.Security.Superglobal +if (!isset($_GET['name'])) { + // phpcs:ignore Magento2.Exceptions.DirectThrow + throw new \InvalidArgumentException( + 'The name of log file is required for getting logs.' + ); +} - echo serialize(file_get_contents('../../../../var/log' . '/' . $name)); - } else { - echo "Command not unauthorized."; - } -} else { - echo "'token' or 'name' parameter is not set."; +// phpcs:ignore Magento2.Security.Superglobal +$name = urldecode($_GET['name']); +if (preg_match('/\.\.(\\\|\/)/', $name)) { + throw new \InvalidArgumentException('Invalid log file name'); } + +// phpcs:ignore Magento2.Security.InsecureFunction, Magento2.Functions.DiscouragedFunction, Magento2.Security.LanguageConstruct +echo serialize(file_get_contents('../../../../var/log' .'/' .$name)); diff --git a/dev/tests/functional/utils/pathChecker.php b/dev/tests/functional/utils/pathChecker.php index b5a2ddb405bde..217cf90af0a56 100644 --- a/dev/tests/functional/utils/pathChecker.php +++ b/dev/tests/functional/utils/pathChecker.php @@ -3,20 +3,20 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -include __DIR__ . '/authenticate.php'; -if (!empty($_POST['token']) && !empty($_POST['path'])) { - if (authenticate(urldecode($_POST['token']))) { - $path = urldecode($_POST['path']); - - if (file_exists('../../../../' . $path)) { - echo 'path exists: true'; - } else { - echo 'path exists: false'; - } +// phpcs:ignore Magento2.Security.Superglobal +if (isset($_GET['path'])) { + // phpcs:ignore Magento2.Security.Superglobal + $path = urldecode($_GET['path']); + // phpcs:ignore Magento2.Functions.DiscouragedFunction + if (file_exists('../../../../' . $path)) { + // phpcs:ignore Magento2.Security.LanguageConstruct + echo 'path exists: true'; } else { - echo "Command not unauthorized."; + // phpcs:ignore Magento2.Security.LanguageConstruct + echo 'path exists: false'; } } else { - echo "'token' or 'path' parameter is not set."; + // phpcs:ignore Magento2.Exceptions.DirectThrow + throw new \InvalidArgumentException("GET parameter 'path' is not set."); } diff --git a/dev/tests/functional/utils/website.php b/dev/tests/functional/utils/website.php index ab8e3742f55ae..720b4962aedd4 100644 --- a/dev/tests/functional/utils/website.php +++ b/dev/tests/functional/utils/website.php @@ -3,35 +3,36 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -include __DIR__ . '/authenticate.php'; -if (!empty($_POST['token']) && !empty($_POST['website_code'])) { - if (authenticate(urldecode($_POST['token']))) { - $websiteCode = urldecode($_POST['website_code']); - $rootDir = '../../../../'; - $websiteDir = $rootDir . 'websites/' . $websiteCode . '/'; - $contents = file_get_contents($rootDir . 'index.php'); +// phpcs:ignore Magento2.Security.Superglobal +if (!isset($_GET['website_code'])) { + // phpcs:ignore Magento2.Exceptions.DirectThrow + throw new \Exception("website_code GET parameter is not set."); +} + +// phpcs:ignore Magento2.Security.Superglobal +$websiteCode = urldecode($_GET['website_code']); +$rootDir = '../../../../'; +$websiteDir = $rootDir . 'websites/' . $websiteCode . '/'; +// phpcs:ignore Magento2.Functions.DiscouragedFunction +$contents = file_get_contents($rootDir . 'index.php'); - $websiteParam = <<<EOD +$websiteParam = <<<EOD \$params = \$_SERVER; \$params[\Magento\Store\Model\StoreManager::PARAM_RUN_CODE] = '$websiteCode'; \$params[\Magento\Store\Model\StoreManager::PARAM_RUN_TYPE] = 'website'; EOD; - $pattern = '`(try {.*?)(\/app\/bootstrap.*?}\n)(.*?)\$_SERVER`mis'; - $replacement = "$1/../..$2\n$websiteParam$3\$params"; +$pattern = '`(try {.*?)(\/app\/bootstrap.*?}\n)(.*?)\$_SERVER`mis'; +$replacement = "$1/../..$2\n$websiteParam$3\$params"; - $contents = preg_replace($pattern, $replacement, $contents); +$contents = preg_replace($pattern, $replacement, $contents); - $old = umask(0); - mkdir($websiteDir, 0760, true); - umask($old); - - copy($rootDir . '.htaccess', $websiteDir . '.htaccess'); - file_put_contents($websiteDir . 'index.php', $contents); - } else { - echo "Command not unauthorized."; - } -} else { - echo "'token' or 'website_code' parameter is not set."; -} +$old = umask(0); +// phpcs:ignore Magento2.Functions.DiscouragedFunction +mkdir($websiteDir, 0760, true); +umask($old); +// phpcs:ignore Magento2.Functions.DiscouragedFunction +copy($rootDir . '.htaccess', $websiteDir . '.htaccess'); +// phpcs:ignore Magento2.Functions.DiscouragedFunction +file_put_contents($websiteDir . 'index.php', $contents); diff --git a/dev/tests/integration/testsuite/Magento/MysqlMq/Model/DataObject.php b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/Model/DataObject.php similarity index 68% rename from dev/tests/integration/testsuite/Magento/MysqlMq/Model/DataObject.php rename to dev/tests/integration/_files/Magento/TestModuleMysqlMq/Model/DataObject.php index 31843be00a54b..ad3033cf7eeaa 100644 --- a/dev/tests/integration/testsuite/Magento/MysqlMq/Model/DataObject.php +++ b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/Model/DataObject.php @@ -3,7 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\MysqlMq\Model; +namespace Magento\TestModuleMysqlMq\Model; class DataObject extends \Magento\Framework\Api\AbstractExtensibleObject { @@ -40,4 +40,21 @@ public function setEntityId($entityId) { return $this->setData('entity_id', $entityId); } + + /** + * @return string + */ + public function getOutputPath() + { + return $this->_get('outputPath'); + } + + /** + * @param string $path + * @return $this + */ + public function setOutputPath($path) + { + return $this->setData('outputPath', $path); + } } diff --git a/dev/tests/integration/testsuite/Magento/MysqlMq/Model/DataObjectRepository.php b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/Model/DataObjectRepository.php similarity index 62% rename from dev/tests/integration/testsuite/Magento/MysqlMq/Model/DataObjectRepository.php rename to dev/tests/integration/_files/Magento/TestModuleMysqlMq/Model/DataObjectRepository.php index 879872315ec51..942298e49972f 100644 --- a/dev/tests/integration/testsuite/Magento/MysqlMq/Model/DataObjectRepository.php +++ b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/Model/DataObjectRepository.php @@ -3,7 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\MysqlMq\Model; +namespace Magento\TestModuleMysqlMq\Model; class DataObjectRepository { @@ -11,15 +11,17 @@ class DataObjectRepository * @param DataObject $dataObject * @param string $requiredParam * @param int|null $optionalParam - * @return bool + * @return null */ public function delayedOperation( - \Magento\MysqlMq\Model\DataObject $dataObject, + \Magento\TestModuleMysqlMq\Model\DataObject $dataObject, $requiredParam, $optionalParam = null ) { - echo "Processed '{$dataObject->getEntityId()}'; " + $output = "Processed '{$dataObject->getEntityId()}'; " . "Required param '{$requiredParam}'; Optional param '{$optionalParam}'\n"; - return true; + file_put_contents($dataObject->getOutputPath(), $output); + + return null; } } diff --git a/dev/tests/integration/_files/Magento/TestModuleMysqlMq/Model/Processor.php b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/Model/Processor.php new file mode 100644 index 0000000000000..fb6fd4c5c2802 --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/Model/Processor.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\TestModuleMysqlMq\Model; + +/** + * Test message processor is used by \Magento\MysqlMq\Model\PublisherConsumerTest + */ +class Processor +{ + /** + * @param \Magento\TestModuleMysqlMq\Model\DataObject $message + */ + public function processMessage($message) + { + file_put_contents( + $message->getOutputPath(), + "Processed {$message->getEntityId()}" . PHP_EOL, + FILE_APPEND + ); + } + + /** + * @param \Magento\TestModuleMysqlMq\Model\DataObject $message + */ + public function processObjectCreated($message) + { + file_put_contents( + $message->getOutputPath(), + "Processed object created {$message->getEntityId()}" . PHP_EOL, + FILE_APPEND + ); + } + + /** + * @param \Magento\TestModuleMysqlMq\Model\DataObject $message + */ + public function processCustomObjectCreated($message) + { + file_put_contents( + $message->getOutputPath(), + "Processed custom object created {$message->getEntityId()}" . PHP_EOL, + FILE_APPEND + ); + } + + /** + * @param \Magento\TestModuleMysqlMq\Model\DataObject $message + */ + public function processObjectUpdated($message) + { + file_put_contents( + $message->getOutputPath(), + "Processed object updated {$message->getEntityId()}" . PHP_EOL, + FILE_APPEND + ); + } + + /** + * @param \Magento\TestModuleMysqlMq\Model\DataObject $message + */ + public function processMessageWithException($message) + { + file_put_contents($message->getOutputPath(), "Exception processing {$message->getEntityId()}"); + throw new \LogicException( + "Exception during message processing happened. Entity: {{$message->getEntityId()}}" + ); + } +} diff --git a/dev/tests/integration/_files/Magento/TestModuleMysqlMq/etc/communication.xml b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/etc/communication.xml new file mode 100644 index 0000000000000..4d6269dbb7920 --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/etc/communication.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Communication/etc/communication.xsd"> + <topic name="demo.exception" request="Magento\TestModuleMysqlMq\Model\DataObject"/> + <topic name="test.schema.defined.by.method" schema="Magento\TestModuleMysqlMq\Model\DataObjectRepository::delayedOperation" is_synchronous="false"/> + <topic name="demo.object.created" request="Magento\TestModuleMysqlMq\Model\DataObject"/> + <topic name="demo.object.updated" request="Magento\TestModuleMysqlMq\Model\DataObject"/> + <topic name="demo.object.custom.created" request="Magento\TestModuleMysqlMq\Model\DataObject"/> +</config> diff --git a/dev/tests/integration/_files/Magento/TestModuleMysqlMq/etc/module.xml b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/etc/module.xml new file mode 100644 index 0000000000000..8b6ea0f44ce9c --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/etc/module.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_TestModuleMysqlMq" active="true"> + </module> +</config> diff --git a/dev/tests/integration/_files/Magento/TestModuleMysqlMq/etc/queue.xml b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/etc/queue.xml new file mode 100644 index 0000000000000..362237c0c5e62 --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/etc/queue.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/queue.xsd"> + <broker topic="demo.exception" type="db" exchange="magento"> + <queue consumer="demoConsumerWithException" name="queue-exception" handler="Magento\TestModuleMysqlMq\Model\Processor::processMessageWithException"/> + </broker> + <broker topic="test.schema.defined.by.method" type="db" exchange="magento"> + <queue consumer="delayedOperationConsumer" name="demo-queue-6" handler="Magento\TestModuleMysqlMq\Model\DataObjectRepository::delayedOperation"/> + </broker> + <broker topic="demo.object.created" type="db" exchange="magento"> + <queue consumer="demoConsumerQueueOne" name="queue-created" handler="\Magento\TestModuleMysqlMq\Model\Processor::processObjectCreated"/> + </broker> + <broker topic="demo.object.updated" exchange="magento" type="db"> + <queue consumer="demoConsumerQueueTwo" name="queue-updated" handler="\Magento\TestModuleMysqlMq\Model\Processor::processObjectUpdated"/> + </broker> + <broker topic="demo.object.custom.created" exchange="magento" type="db"> + <queue consumer="demoConsumerQueueThree" name="queue-custom-created" handler="\Magento\TestModuleMysqlMq\Model\Processor::processCustomObjectCreated"/> + </broker> +</config> diff --git a/dev/tests/integration/_files/Magento/TestModuleMysqlMq/etc/queue_consumer.xml b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/etc/queue_consumer.xml new file mode 100644 index 0000000000000..bb495a123a05d --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/etc/queue_consumer.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/consumer.xsd"> + <consumer name="demoConsumerQueueOne" queue="queue-created" connection="db" handler="Magento\TestModuleMysqlMq\Model\Processor::processObjectCreated"/> + <consumer name="demoConsumerQueueTwo" queue="queue-updated" connection="db" handler="Magento\TestModuleMysqlMq\Model\Processor::processObjectUpdated"/> + <consumer name="demoConsumerQueueThree" queue="queue-custom-created" connection="db" handler="Magento\TestModuleMysqlMq\Model\Processor::processCustomObjectCreated"/> + <consumer name="demoConsumerWithException" queue="queue-exception" connection="db" handler="Magento\TestModuleMysqlMq\Model\Processor::processMessageWithException"/> + <consumer name="delayedOperationConsumer" queue="demo-queue-6" connection="db" handler="Magento\TestModuleMysqlMq\Model\DataObjectRepository::delayedOperation"/> +</config> diff --git a/dev/tests/integration/_files/Magento/TestModuleMysqlMq/etc/queue_publisher.xml b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/etc/queue_publisher.xml new file mode 100644 index 0000000000000..a665e10ef5f14 --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/etc/queue_publisher.xml @@ -0,0 +1,24 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/publisher.xsd"> + <publisher topic="demo.exception"> + <connection name="db" exchange="magento"/> + </publisher> + <publisher topic="test.schema.defined.by.method"> + <connection name="db" exchange="magento"/> + </publisher> + <publisher topic="demo.object.created"> + <connection name="db" exchange="magento"/> + </publisher> + <publisher topic="demo.object.updated"> + <connection name="db" exchange="magento"/> + </publisher> + <publisher topic="demo.object.custom.created"> + <connection name="db" exchange="magento"/> + </publisher> +</config> diff --git a/dev/tests/integration/_files/Magento/TestModuleMysqlMq/etc/queue_topology.xml b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/etc/queue_topology.xml new file mode 100644 index 0000000000000..2df5485ee3447 --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/etc/queue_topology.xml @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/topology.xsd"> + + <exchange name="magento" type="topic" connection="db"> + <binding id="demo.exception.consumer" topic="demo.exception" destination="queue-exception" destinationType="queue"/> + <binding id="test.schema.defined.by.method" topic="test.schema.defined.by.method" destination="demo-queue-6" destinationType="queue"/> + <binding id="demo.object.created" topic="demo.object.created" destination="queue-created" destinationType="queue"/> + <binding id="demo.object.updated" topic="demo.object.updated" destination="queue-updated" destinationType="queue"/> + <binding id="demo.object.all" topic="demo.object.*" destination="queue-custom-created" destinationType="queue"/> + </exchange> +</config> diff --git a/dev/tests/integration/_files/Magento/TestModuleMysqlMq/registration.php b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/registration.php new file mode 100644 index 0000000000000..4250e95bd7cc3 --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleMysqlMq/registration.php @@ -0,0 +1,12 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Framework\Component\ComponentRegistrar; + +$registrar = new ComponentRegistrar(); +if ($registrar->getPath(ComponentRegistrar::MODULE, 'Magento_TestModuleMysqlMq') === null) { + ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_TestModuleMysqlMq', __DIR__); +} diff --git a/dev/tests/integration/testsuite/Magento/Backend/Block/Dashboard/GraphTest.php b/dev/tests/integration/testsuite/Magento/Backend/Block/Dashboard/GraphTest.php index 17863cd709580..497deb2c99110 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Block/Dashboard/GraphTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Block/Dashboard/GraphTest.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Backend\Block\Dashboard; /** @@ -27,6 +29,6 @@ protected function setUp() public function testGetChartUrl() { - $this->assertStringStartsWith('http://chart.apis.google.com/chart', $this->_block->getChartUrl()); + $this->assertStringStartsWith('https://image-charts.com/chart', $this->_block->getChartUrl()); } } diff --git a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/Dashboard/ProductsViewedTest.php b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/Dashboard/ProductsViewedTest.php index 595a33344c7e8..bd4dd0c8daf0c 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/Dashboard/ProductsViewedTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/Dashboard/ProductsViewedTest.php @@ -4,8 +4,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Backend\Controller\Adminhtml\Dashboard; +/** + * Test product viewed backend controller. + */ class ProductsViewedTest extends \Magento\TestFramework\TestCase\AbstractBackendController { /** @@ -14,6 +18,7 @@ class ProductsViewedTest extends \Magento\TestFramework\TestCase\AbstractBackend */ public function testExecute() { + $this->getRequest()->setMethod("POST"); $this->dispatch('backend/admin/dashboard/productsViewed/'); $this->assertEquals(200, $this->getResponse()->getHttpResponseCode()); diff --git a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/DashboardTest.php b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/DashboardTest.php index 89f1e5e5d53d6..0eb98379b4571 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/DashboardTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/DashboardTest.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Backend\Controller\Adminhtml; /** @@ -19,10 +21,15 @@ public function testAjaxBlockAction() $this->assertContains('dashboard-diagram', $actual); } + /** + * Tests tunnelAction + * + * @throws \Exception + * @return void + */ public function testTunnelAction() { - $this->markTestSkipped('MAGETWO-98800: TunnelAction fails when Google Chart API is not available'); - + // phpcs:disable Magento2.Functions.DiscouragedFunction $testUrl = \Magento\Backend\Block\Dashboard\Graph::API_URL . '?cht=p3&chd=t:60,40&chs=250x100&chl=Hello|World'; $handle = curl_init(); curl_setopt($handle, CURLOPT_URL, $testUrl); @@ -36,6 +43,7 @@ public function testTunnelAction() curl_close($handle); throw $e; } + // phpcs:enable $gaData = [ 'cht' => 'lc', diff --git a/dev/tests/integration/testsuite/Magento/Backend/Model/Auth/SessionTest.php b/dev/tests/integration/testsuite/Magento/Backend/Model/Auth/SessionTest.php index 5ca2bf1f73175..f1e7a10737604 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Model/Auth/SessionTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Model/Auth/SessionTest.php @@ -3,8 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Backend\Model\Auth; +use Magento\TestFramework\Bootstrap as TestHelper; +use Magento\TestFramework\Helper\Bootstrap; + /** * @magentoAppArea adminhtml * @magentoAppIsolation enabled @@ -18,10 +22,15 @@ class SessionTest extends \PHPUnit\Framework\TestCase private $auth; /** - * @var \Magento\Backend\Model\Auth\Session + * @var Session */ private $authSession; + /** + * @var SessionFactory + */ + private $authSessionFactory; + /** * @var \Magento\Framework\ObjectManagerInterface */ @@ -30,11 +39,12 @@ class SessionTest extends \PHPUnit\Framework\TestCase protected function setUp() { parent::setUp(); - $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->objectManager = Bootstrap::getObjectManager(); $this->objectManager->get(\Magento\Framework\Config\ScopeInterface::class) ->setCurrentScope(\Magento\Backend\App\Area\FrontNameResolver::AREA_CODE); $this->auth = $this->objectManager->create(\Magento\Backend\Model\Auth::class); - $this->authSession = $this->objectManager->create(\Magento\Backend\Model\Auth\Session::class); + $this->authSession = $this->objectManager->create(Session::class); + $this->authSessionFactory = $this->objectManager->get(SessionFactory::class); $this->auth->setAuthStorage($this->authSession); $this->auth->logout(); } @@ -52,8 +62,8 @@ public function testIsLoggedIn($loggedIn) { if ($loggedIn) { $this->auth->login( - \Magento\TestFramework\Bootstrap::ADMIN_NAME, - \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD + TestHelper::ADMIN_NAME, + TestHelper::ADMIN_PASSWORD ); } $this->assertEquals($loggedIn, $this->authSession->isLoggedIn()); @@ -63,4 +73,23 @@ public function loginDataProvider() { return [[false], [true]]; } + + /** + * Check that persisting user data is working. + */ + public function testStorage() + { + $this->auth->login(TestHelper::ADMIN_NAME, TestHelper::ADMIN_PASSWORD); + $user = $this->authSession->getUser(); + $acl = $this->authSession->getAcl(); + /** @var Session $session */ + $session = $this->authSessionFactory->create(); + $persistedUser = $session->getUser(); + $persistedAcl = $session->getAcl(); + + $this->assertEquals($user->getData(), $persistedUser->getData()); + $this->assertEquals($user->getAclRole(), $persistedUser->getAclRole()); + $this->assertEquals($acl->getRoles(), $persistedAcl->getRoles()); + $this->assertEquals($acl->getResources(), $persistedAcl->getResources()); + } } diff --git a/dev/tests/integration/testsuite/Magento/Backend/Model/Locale/ResolverTest.php b/dev/tests/integration/testsuite/Magento/Backend/Model/Locale/ResolverTest.php index d1252be2c4b53..88662a65c7428 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Model/Locale/ResolverTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Model/Locale/ResolverTest.php @@ -3,9 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Backend\Model\Locale; use Magento\Framework\Locale\Resolver; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\User\Model\User; /** * @magentoAppArea adminhtml @@ -20,7 +23,7 @@ class ResolverTest extends \PHPUnit\Framework\TestCase protected function setUp() { parent::setUp(); - $this->_model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + $this->_model = Bootstrap::getObjectManager()->create( \Magento\Backend\Model\Locale\Resolver::class ); } @@ -38,12 +41,12 @@ public function testSetLocaleWithDefaultLocale() */ public function testSetLocaleWithBaseInterfaceLocale() { - $user = new \Magento\Framework\DataObject(); - $session = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + $user = Bootstrap::getObjectManager()->create(User::class); + $session = Bootstrap::getObjectManager()->get( \Magento\Backend\Model\Auth\Session::class ); $session->setUser($user); - \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + Bootstrap::getObjectManager()->get( \Magento\Backend\Model\Auth\Session::class )->getUser()->setInterfaceLocale( 'fr_FR' @@ -56,7 +59,7 @@ public function testSetLocaleWithBaseInterfaceLocale() */ public function testSetLocaleWithSessionLocale() { - \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + Bootstrap::getObjectManager()->get( \Magento\Backend\Model\Session::class )->setSessionLocale( 'es_ES' @@ -69,7 +72,7 @@ public function testSetLocaleWithSessionLocale() */ public function testSetLocaleWithRequestLocale() { - $request = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + $request = Bootstrap::getObjectManager() ->get(\Magento\Framework\App\RequestInterface::class); $request->setPostValue(['locale' => 'de_DE']); $this->_checkSetLocale('de_DE'); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/PriceTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/PriceTest.php index 2a68ff48e5f9a..4a5757aae3134 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/PriceTest.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/PriceTest.php @@ -6,7 +6,7 @@ namespace Magento\Bundle\Model\Product; /** - * @magentoDataFixture Magento/Bundle/_files/product_with_tier_pricing.php + * Class to test bundle prices */ class PriceTest extends \PHPUnit\Framework\TestCase { @@ -22,6 +22,9 @@ protected function setUp() ); } + /** + * @magentoDataFixture Magento/Bundle/_files/product_with_tier_pricing.php + */ public function testGetTierPrice() { /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ @@ -37,4 +40,50 @@ public function testGetTierPrice() $this->assertEquals(20.0, $this->_model->getTierPrice(4, $product)); $this->assertEquals(30.0, $this->_model->getTierPrice(5, $product)); } + + /** + * Test calculation final price for bundle product with tire price in simple product + * + * @param float $bundleQty + * @param float $selectionQty + * @param float $finalPrice + * @magentoDataFixture Magento/Bundle/_files/product_with_simple_tier_pricing.php + * @dataProvider getSelectionFinalTotalPriceWithSimpleTierPriceDataProvider + */ + public function testGetSelectionFinalTotalPriceWithSimpleTierPrice( + float $bundleQty, + float $selectionQty, + float $finalPrice + ) { + /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ + $productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); + $bundleProduct = $productRepository->get('bundle-product'); + $simpleProduct = $productRepository->get('simple'); + $simpleProduct->setCustomerGroupId(\Magento\Customer\Model\Group::CUST_GROUP_ALL); + + $this->assertEquals( + $finalPrice, + $this->_model->getSelectionFinalTotalPrice( + $bundleProduct, + $simpleProduct, + $bundleQty, + $selectionQty, + false + ), + 'Tier price calculation for Simple product is wrong' + ); + } + + /** + * @return array + */ + public function getSelectionFinalTotalPriceWithSimpleTierPriceDataProvider(): array + { + return [ + [1, 1, 10], + [2, 1, 8], + [5, 1, 5], + ]; + } } diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_simple_tier_pricing.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_simple_tier_pricing.php new file mode 100644 index 0000000000000..30f0978480701 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_simple_tier_pricing.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/../../../Magento/Catalog/_files/product_simple.php'; + +/** @var $productFactory Magento\Catalog\Model\ProductFactory */ +$productFactory = $objectManager->create(\Magento\Catalog\Model\ProductFactory::class); +/** @var $bundleProduct \Magento\Catalog\Model\Product */ +$bundleProduct = $productFactory->create(); +$bundleProduct->setTypeId('bundle') + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([1]) + ->setPriceType(\Magento\Bundle\Model\Product\Price::PRICE_TYPE_DYNAMIC) + ->setPriceView(1) + ->setName('Bundle Product') + ->setSku('bundle-product') + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStockData([ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + ]) + ->setBundleOptionsData( + [ + [ + 'title' => 'Bundle Product Items', + 'default_title' => 'Bundle Product Items', + 'type' => 'checkbox', + 'required' => 1, + 'delete' => '', + ], + ] + ) + ->setBundleSelectionsData( + [[['product_id' => $product->getId(), 'selection_qty' => 1, 'delete' => '']]] + ); +$productRepository->save($bundleProduct); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_simple_tier_pricing_rollback.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_simple_tier_pricing_rollback.php new file mode 100644 index 0000000000000..aa661c7412d42 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_simple_tier_pricing_rollback.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/../../../Magento/Catalog/_files/product_simple_rollback.php'; + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +try { + $product = $productRepository->get('bundle-product'); + $productRepository->delete($product); +} catch (\Magento\Framework\Exception\NoSuchEntityException $exception) { + //Product already removed +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_options.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_options.php new file mode 100644 index 0000000000000..a401db8eb2bf7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_options.php @@ -0,0 +1,107 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = $objectManager->create(\Magento\Catalog\Model\Product::class); + +$product->setTypeId( + 'simple' +)->setAttributeSetId( + 4 +)->setWebsiteIds( + [1] +)->setName( + 'Virtual Product With Custom Options' +)->setSku( + 'simple' +)->setPrice( + 10 +)->setMetaTitle( + 'meta title' +)->setMetaKeyword( + 'meta keyword' +)->setMetaDescription( + 'meta description' +)->setVisibility( + \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH +)->setStatus( + \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED +)->setCanSaveCustomOptions( + true +)->setStockData( + [ + 'qty' => 100, + 'is_in_stock' => 1, + 'manage_stock' => 1, + ] +)->setHasOptions(true); + +$options = [ + [ + 'title' => 'test_option_code_1', + 'type' => 'field', + 'is_require' => true, + 'sort_order' => 1, + 'price' => -10.0, + 'price_type' => 'fixed', + 'sku' => 'sku1', + 'max_characters' => 10, + ], + [ + 'title' => 'area option', + 'type' => 'area', + 'is_require' => true, + 'sort_order' => 2, + 'price' => 20.0, + 'price_type' => 'percent', + 'sku' => 'sku2', + 'max_characters' => 20 + ], + [ + 'title' => 'drop_down option', + 'type' => 'drop_down', + 'is_require' => false, + 'sort_order' => 4, + 'values' => [ + [ + 'title' => 'drop_down option 1', + 'price' => 10, + 'price_type' => 'fixed', + 'sku' => 'drop_down option 1 sku', + 'sort_order' => 1, + ], + [ + 'title' => 'drop_down option 2', + 'price' => 20, + 'price_type' => 'fixed', + 'sku' => 'drop_down option 2 sku', + 'sort_order' => 2, + ], + ], + ] +]; + +$customOptions = []; + +/** @var \Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory $customOptionFactory */ +$customOptionFactory = $objectManager->get(\Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory::class); + +foreach ($options as $option) { + /** @var \Magento\Catalog\Api\Data\ProductCustomOptionInterface $customOption */ + $customOption = $customOptionFactory->create(['data' => $option]); + $customOption->setProductSku($product->getSku()); + + $customOptions[] = $customOption; +} + +$product->setOptions($customOptions); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepositoryFactory */ +$productRepository = $objectManager->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_options_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_options_rollback.php new file mode 100644 index 0000000000000..8863da1cd2782 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_options_rollback.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +$repository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\ProductRepository::class +); +try { + $product = $repository->get('simple', false, null, true); + $repository->delete($product); +} catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + //Entity already deleted +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual.php index 38e8c404dc002..838ae2b9a2aa6 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual.php @@ -3,9 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); -/** @var $product \Magento\Catalog\Model\Product */ -$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); +use Magento\Catalog\Api\Data\ProductInterfaceFactory; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Catalog\Model\ResourceModel\Product as ProductResource; + +$productFactory = Bootstrap::getObjectManager()->get(ProductInterfaceFactory::class); +$product = $productFactory->create(); $product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL) ->setId(21) ->setAttributeSetId(4) @@ -22,4 +27,7 @@ 'is_in_stock' => 1, 'manage_stock' => 1, ] - )->save(); + ); +/** @var ProductResource $productResource */ +$productResource = Bootstrap::getObjectManager()->get(ProductResource::class); +$productResource->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_rollback.php index 7fdeca846885a..f5568ced2c96a 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_rollback.php @@ -3,23 +3,27 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); -/** @var \Magento\Framework\Registry $registry */ -$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\StateException; +use Magento\TestFramework\Helper\Bootstrap; + +$registry = Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); $registry->unregister('isSecureArea'); $registry->register('isSecureArea', true); -/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ -$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); +$productRepository = Bootstrap::getObjectManager() + ->get(ProductRepositoryInterface::class); try { $product = $productRepository->get('virtual-product', false, null, true); $productRepository->delete($product); -} catch (\Magento\Framework\Exception\NoSuchEntityException $exception) { +} catch (NoSuchEntityException $exception) { //Product already removed -} catch (\Magento\Framework\Exception\StateException $exception) { +} catch (StateException $exception) { } $registry->unregister('isSecureArea'); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_with_options.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_with_options.php new file mode 100644 index 0000000000000..c1f981cefa646 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_with_options.php @@ -0,0 +1,107 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = $objectManager->create(\Magento\Catalog\Model\Product::class); + +$product->setTypeId( + 'virtual' +)->setAttributeSetId( + 4 +)->setWebsiteIds( + [1] +)->setName( + 'Virtual Product With Custom Options' +)->setSku( + 'virtual' +)->setPrice( + 10 +)->setMetaTitle( + 'meta title' +)->setMetaKeyword( + 'meta keyword' +)->setMetaDescription( + 'meta description' +)->setVisibility( + \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH +)->setStatus( + \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED +)->setCanSaveCustomOptions( + true +)->setStockData( + [ + 'qty' => 100, + 'is_in_stock' => 1, + 'manage_stock' => 1, + ] +)->setHasOptions(true); + +$options = [ + [ + 'title' => 'test_option_code_1', + 'type' => 'field', + 'is_require' => true, + 'sort_order' => 1, + 'price' => -10.0, + 'price_type' => 'fixed', + 'sku' => 'sku1', + 'max_characters' => 10, + ], + [ + 'title' => 'area option', + 'type' => 'area', + 'is_require' => true, + 'sort_order' => 2, + 'price' => 20.0, + 'price_type' => 'percent', + 'sku' => 'sku2', + 'max_characters' => 20 + ], + [ + 'title' => 'drop_down option', + 'type' => 'drop_down', + 'is_require' => false, + 'sort_order' => 4, + 'values' => [ + [ + 'title' => 'drop_down option 1', + 'price' => 10, + 'price_type' => 'fixed', + 'sku' => 'drop_down option 1 sku', + 'sort_order' => 1, + ], + [ + 'title' => 'drop_down option 2', + 'price' => 20, + 'price_type' => 'fixed', + 'sku' => 'drop_down option 2 sku', + 'sort_order' => 2, + ], + ], + ] +]; + +$customOptions = []; + +/** @var \Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory $customOptionFactory */ +$customOptionFactory = $objectManager->get(\Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory::class); + +foreach ($options as $option) { + /** @var \Magento\Catalog\Api\Data\ProductCustomOptionInterface $customOption */ + $customOption = $customOptionFactory->create(['data' => $option]); + $customOption->setProductSku($product->getSku()); + + $customOptions[] = $customOption; +} + +$product->setOptions($customOptions); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepositoryFactory */ +$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_with_options_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_with_options_rollback.php new file mode 100644 index 0000000000000..f46cdc13d3263 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_with_options_rollback.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +$repository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\ProductRepository::class +); +try { + $product = $repository->get('virtual', false, null, true); + $repository->delete($product); +} catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + //Entity already deleted +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php index c4c6d3ba2d1d2..67446960e15dc 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -34,6 +34,7 @@ * @magentoDataFixtureBeforeTransaction Magento/Catalog/_files/enable_catalog_product_reindex_schedule.php * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + * phpcs:disable Generic.PHP.NoSilencedErrors, Generic.Metrics.NestingLevel, Magento2.Functions.StaticFunction */ class ProductTest extends \Magento\TestFramework\Indexer\TestCase { @@ -567,6 +568,7 @@ public function testSaveDatetimeAttribute() */ protected function getExpectedOptionsData(string $pathToFile, string $storeCode = ''): array { + // phpcs:disable Magento2.Functions.DiscouragedFunction $productData = $this->csvToArray(file_get_contents($pathToFile)); $expectedOptionId = 0; $expectedOptions = []; @@ -1590,6 +1592,28 @@ public function testAddUpdateProductWithInvalidUrlKeys() : void } } + /** + * Make sure the non existing image in the csv file won't erase the qty key of the existing products. + * + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testImportWithNonExistingImage() + { + $products = [ + 'simple_new' => 100, + ]; + + $this->importFile('products_to_import_with_non_existing_image.csv'); + + $productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); + foreach ($products as $productSku => $productQty) { + $product = $productRepository->get($productSku); + $stockItem = $product->getExtensionAttributes()->getStockItem(); + $this->assertEquals($productQty, $stockItem->getQty()); + } + } + /** * @magentoDataFixture Magento/Catalog/_files/product_simple_with_url_key.php * @magentoDbIsolation disabled @@ -1781,6 +1805,7 @@ function (ProductInterface $item) { if ($product->getId()) { $productRepository->delete($product); } + // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { //Product already removed } diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_non_existing_image.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_non_existing_image.csv new file mode 100644 index 0000000000000..8122433a8c9e1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_non_existing_image.csv @@ -0,0 +1,2 @@ +sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,swatch_image,swatch_image_label1,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,crosssell_skus,upsell_skus,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,associated_skus +simple_new,,Default,simple,,base,New Product,,,,1,Taxable Goods,"Catalog, Search",10,,,,new-product,New Product,New Product,New Product ,/no/exists/image/magento_image.jpg,Image Label,magento_small_image.jpg,Small Image Label,magento_thumbnail.jpg,Thumbnail Label,magento_image.jpg,Image Label,10/20/15 07:05,10/20/15 07:05,,,Block after Info Column,,,,,,,,,,,,,"has_options=1,quantity_and_stock_status=In Stock,required_options=1",100,0,1,0,0,1,1,1,10000,1,1,1,1,1,0,1,1,0,0,0,1,,,,"magento_additional_image_one.jpg, magento_additional_image_two.jpg","Additional Image Label One,Additional Image Label Two",,,,,,,, diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_virtual_product_saved.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_virtual_product_saved.php index 835b2ab812856..833e5a57ac34f 100644 --- a/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_virtual_product_saved.php +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_virtual_product_saved.php @@ -13,6 +13,7 @@ ->setIsMultiShipping(false) ->setReservedOrderId('test_order_with_virtual_product_without_address') ->setEmail('store@example.com') + ->setCustomerEmail('store@example.com') ->addProduct( $product->load($product->getId()), 1 diff --git a/dev/tests/integration/testsuite/Magento/Cms/_files/pages.php b/dev/tests/integration/testsuite/Magento/Cms/_files/pages.php index a0b7b99a877a2..b2742ecd380f3 100644 --- a/dev/tests/integration/testsuite/Magento/Cms/_files/pages.php +++ b/dev/tests/integration/testsuite/Magento/Cms/_files/pages.php @@ -11,6 +11,10 @@ ->setStores([0]) ->setIsActive(1) ->setContent('<h1>Cms Page 100 Title</h1>') + ->setContentHeading('<h2>Cms Page 100 Title</h2>') + ->setMetaTitle('Cms Meta title for page100') + ->setMetaKeywords('Cms Meta Keywords for page100') + ->setMetaDescription('Cms Meta Description for page100') ->setPageLayout('1column') ->save(); @@ -20,6 +24,10 @@ ->setStores([0]) ->setIsActive(1) ->setContent('<h1>Cms Page Design Blank Title</h1>') + ->setContentHeading('<h2>Cms Page Blank Title</h2>') + ->setMetaTitle('Cms Meta title for Blank page') + ->setMetaKeywords('Cms Meta Keywords for Blank page') + ->setMetaDescription('Cms Meta Description for Blank page') ->setPageLayout('1column') ->setCustomTheme('Magento/blank') ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/CartTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/CartTest.php index 3bb10baff6572..3ade17d90fe99 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/CartTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/CartTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Customer\Block\Adminhtml\Edit\Tab\View; use Magento\Customer\Controller\RegistryConstants; @@ -100,15 +101,4 @@ public function testToHtmlCartItem() $this->assertContains('$10.00', $html); $this->assertContains('catalog/product/edit/id/1', $html); } - - /** - * Verify that the customer has a single item in his cart. - * - * @magentoDataFixture Magento/Customer/_files/customer.php - * @magentoDataFixture Magento/Customer/_files/quote.php - */ - public function testGetCollection() - { - $this->assertEquals(1, $this->block->getCollection()->getSize()); - } } diff --git a/dev/tests/integration/testsuite/Magento/Email/Model/TemplateTest.php b/dev/tests/integration/testsuite/Magento/Email/Model/TemplateTest.php index 7789a79794f39..2d12eefc286c6 100644 --- a/dev/tests/integration/testsuite/Magento/Email/Model/TemplateTest.php +++ b/dev/tests/integration/testsuite/Magento/Email/Model/TemplateTest.php @@ -703,8 +703,8 @@ public function testGetVariablesOptionArrayInGroup() $testTemplateVariables = '{"var data.name":"Sender Name","var data.email":"Sender Email"}'; $this->model->setOrigTemplateVariables($testTemplateVariables); $variablesOptionArray = $this->model->getVariablesOptionArray(true); - $this->assertEquals('Template Variables', $variablesOptionArray['label']->getText()); - $this->assertEquals($this->model->getVariablesOptionArray(), $variablesOptionArray['value']); + $this->assertEquals('Template Variables', $variablesOptionArray[0]['label']->getText()); + $this->assertEquals($this->model->getVariablesOptionArray(), $variablesOptionArray[0]['value']); } /** diff --git a/dev/tests/integration/testsuite/Magento/Framework/Data/Form/Element/DateTest.php b/dev/tests/integration/testsuite/Magento/Framework/Data/Form/Element/DateTest.php index a934372bfd907..9980f40239f8c 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Data/Form/Element/DateTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Data/Form/Element/DateTest.php @@ -4,41 +4,51 @@ * See COPYING.txt for license details. */ +namespace Magento\Framework\Data\Form\Element; + +use Magento\Framework\Data\Form\ElementFactory; +use Magento\TestFramework\Helper\Bootstrap; + /** * Tests for \Magento\Framework\Data\Form\Element\Date */ -namespace Magento\Framework\Data\Form\Element; - class DateTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Framework\Data\Form\ElementFactory + * @var ElementFactory */ - protected $_elementFactory; + private $elementFactory; /** - * SetUp method + * @inheritdoc */ protected function setUp() { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $this->_elementFactory = $objectManager->create(\Magento\Framework\Data\Form\ElementFactory::class); + $objectManager = Bootstrap::getObjectManager(); + $this->elementFactory = $objectManager->create(ElementFactory::class); } /** + * Test get value + * + * @param array $data + * @param string $expect + * @return void * @dataProvider getValueDataProvider */ - public function testGetValue(array $data, $expect) + public function testGetValue(array $data, string $expect): void { - /** @var $date \Magento\Framework\Data\Form\Element\Date */ - $date = $this->_elementFactory->create(\Magento\Framework\Data\Form\Element\Date::class, $data); + /** @var $date Date */ + $date = $this->elementFactory->create(Date::class, $data); $this->assertEquals($expect, $date->getValue()); } /** + * Get value test data provider + * * @return array */ - public function getValueDataProvider() + public function getValueDataProvider(): array { $testTimestamp = strtotime('2014-05-18 12:08:16'); $date = new \DateTime('@' . $testTimestamp); @@ -56,15 +66,22 @@ public function getValueDataProvider() 'time_format' => 'h:mm a', 'value' => $testTimestamp, ], - $date->format('g:i A') + $date->format('g:i A'), ], [ [ 'date_format' => 'MM/d/yy', 'value' => $testTimestamp, ], - $date->format('m/j/y') - ] + $date->format('m/j/y'), + ], + [ + [ + 'date_format' => 'd-MM-Y', + 'value' => $date->format('d-m-Y'), + ], + $date->format('d-m-Y'), + ], ]; } } diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php index 7f8996daa6e97..af8d1c7af134b 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php @@ -8,6 +8,7 @@ namespace Magento\Framework\GraphQl\Config; use Magento\Framework\App\Cache; +use Magento\Framework\App\Request\Http; use Magento\Framework\GraphQl\Config; use Magento\Framework\GraphQl\Schema\SchemaGenerator; use Magento\Framework\ObjectManagerInterface; @@ -34,6 +35,9 @@ class GraphQlReaderTest extends \PHPUnit\Framework\TestCase /** @var SerializerInterface */ private $jsonSerializer; + /** + * @inheritdoc + */ protected function setUp() { $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); @@ -175,12 +179,14 @@ enumValues(includeDeprecated: true) { 'operationName' => 'IntrospectionQuery' ]; /** @var Http $request */ - $request = $this->objectManager->get(\Magento\Framework\App\Request\Http::class); + $request = $this->objectManager->get(Http::class); $request->setPathInfo('/graphql'); + $request->setMethod('POST'); $request->setContent(json_encode($postData)); $headers = $this->objectManager->create(\Zend\Http\Headers::class) ->addHeaders(['Content-Type' => 'application/json']); $request->setHeaders($headers); + $response = $this->graphQlController->dispatch($request); $output = $this->jsonSerializer->unserialize($response->getContent()); $expectedOutput = require __DIR__ . '/../_files/schema_response_sdl_description.php'; diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/GraphQlConfigTest.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/GraphQlConfigTest.php index ef4612ea357ea..5668c1bef6687 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/GraphQlConfigTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/GraphQlConfigTest.php @@ -7,11 +7,8 @@ namespace Magento\Framework\GraphQl; -use Magento\Framework\App\Bootstrap; use Magento\Framework\App\Cache; -use Magento\Framework\Config\FileResolverInterface; use Magento\Framework\GraphQl\Config\Config; -use Magento\Framework\GraphQl\Config\ConfigElementInterface; use Magento\Framework\GraphQl\Config\Data\Argument; use Magento\Framework\GraphQl\Config\Data\Enum; use Magento\Framework\GraphQl\Config\Data\Field; @@ -19,9 +16,11 @@ use Magento\Framework\GraphQl\Config\Data\Type; use Magento\Framework\GraphQl\Config\Element\EnumValue; use Magento\Framework\GraphQl\Config\Element\InterfaceType; -use Magento\Framework\GraphQl\Config\Element\TypeFactory; use Magento\Framework\ObjectManagerInterface; +/** + * Test of schema configuration reading and parsing + */ class GraphQlConfigTest extends \PHPUnit\Framework\TestCase { /** @var \Magento\Framework\GraphQl\Config */ @@ -76,7 +75,12 @@ public function testGraphQlTypeAndFieldConfigStructure() ['response_field' => 'required', 'expected_value' => $queryFields[$fieldKey]->isRequired()], ['response_field' => 'isList', 'expected_value' => $queryFields[$fieldKey]->isList()], ['response_field' => 'resolver', 'expected_value' => $queryFields[$fieldKey]->getResolver()], - ['response_field' => 'description', 'expected_value' => $queryFields[$fieldKey]->getDescription()] + ['response_field' => 'description', 'expected_value' => $queryFields[$fieldKey]->getDescription()], + [ + 'response_field' => 'cache', + 'expected_value' => $queryFields[$fieldKey]->getCache(), + 'optional' => true + ] ]; $this->assertResponseFields($expectedOutputArray['Query']['fields'][$fieldKey], $fieldAssertionMap); /** @var \Magento\Framework\GraphQl\Config\Element\Argument $queryFieldArguments */ @@ -212,12 +216,15 @@ private function assertResponseFields($actualResponse, $assertionMap) $expectedValue, "Value of '{$responseField}' field must not be NULL" ); - $this->assertEquals( - $expectedValue, - $actualResponse[$responseField], - "Value of '{$responseField}' field in response does not match expected value: " - . var_export($expectedValue, true) - ); + $optionalField = isset($assertionData['optional']) ? $assertionData['optional'] : false; + if (!$optionalField || isset($actualResponse[$responseField])) { + $this->assertEquals( + $expectedValue, + $actualResponse[$responseField], + "Value of '{$responseField}' field in response does not match expected value: " + . var_export($expectedValue, true) + ); + } } } diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/query_array_output.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/query_array_output.php index 9ebd0160240a8..c37632fe3e218 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/query_array_output.php +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/query_array_output.php @@ -31,8 +31,13 @@ ], 'required' => false, 'isList' => false, - 'resolver' => 'Magento\\EavGraphQl\\Model\\Resolver\\CustomAttributeMetadata', - 'description' => 'Returns the attribute type, given an attribute code and entity type' + 'resolver' => Magento\EavGraphQl\Model\Resolver\CustomAttributeMetadata::class, + 'description' => 'Returns the attribute type, given an attribute code and entity type', + 'cache' => [ + 'cacheTag' => 'cat_test', + 'cacheIdentity' => + Magento\EavGraphQl\Model\Resolver\CustomAttributeMetadata::class + ] ], 'products' => [ 'name' => 'products', @@ -95,7 +100,7 @@ ], 'required' => false, 'isList' => false, - 'resolver' => 'Magento\\CatalogGraphQl\\Model\\Resolver\\Products', + 'resolver' => Magento\CatalogGraphQl\Model\Resolver\Products::class, 'description' => 'comment for products fields' ] ] @@ -274,7 +279,7 @@ ] ], - 'typeResolver' => 'Magento\\CatalogGraphQl\\Model\\ProductLinkTypeResolverComposite', + 'typeResolver' => Magento\CatalogGraphQl\Model\ProductLinkTypeResolverComposite::class, 'description' => 'description for ProductLinksInterface' ] ]; diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaC.graphqls b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaC.graphqls index 84609293f31a8..92682468c42be 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaC.graphqls +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaC.graphqls @@ -1,6 +1,6 @@ type Query { customAttributeMetadata(attributes: [AttributeInput!]!): CustomAttributeMetadata @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\CustomAttributeMetadata") - @doc(description: "Returns the attribute type, given an attribute code and entity type") + @doc(description: "Returns the attribute type, given an attribute code and entity type") @cache(cacheTag: "cat_test", cacheIdentity: "Magento\\EavGraphQl\\Model\\Resolver\\CustomAttributeMetadata") } type CustomAttributeMetadata { diff --git a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/_files/communication.xml b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/_files/communication.xml index 0fc50f0432b93..1cc5d6cd3b714 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/_files/communication.xml +++ b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/_files/communication.xml @@ -7,6 +7,6 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Communication/etc/communication.xsd"> <topic name="topic.broker.test" request="string" response="string"> - <handler name="topicBrokerHandler" type="Magento\MysqlMq\Model\Processor" method="processMessage"/> + <handler name="topicBrokerHandler" type="Magento\TestModuleMysqlMq\Model\Processor" method="processMessage"/> </topic> </config> diff --git a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/_files/valid_expected_queue.php b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/_files/valid_expected_queue.php index b5f5145c32c72..9a813b4424eaa 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/_files/valid_expected_queue.php +++ b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/_files/valid_expected_queue.php @@ -40,7 +40,7 @@ "name" => "publisher5.topic", "schema" => [ "schema_type" => "object", - "schema_value" => '\\' . \Magento\MysqlMq\Model\DataObject::class + "schema_value" => '\\' . \Magento\TestModuleMysqlMq\Model\DataObject::class ], "response_schema" => [ "schema_type" => "object", @@ -58,7 +58,7 @@ "handlers" => [ "topic.broker.test" => [ "0" => [ - "type" => \Magento\MysqlMq\Model\Processor::class, + "type" => \Magento\TestModuleMysqlMq\Model\Processor::class, "method" => "processMessage" ] ] diff --git a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/_files/valid_queue_input.php b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/_files/valid_queue_input.php index fdd4a7d3007a7..ed6e13cfe9fae 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/_files/valid_queue_input.php +++ b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/_files/valid_queue_input.php @@ -23,11 +23,11 @@ "name" => "publisher5.topic", "schema" => [ "schema_type" => "object", - "schema_value" => "Magento\\MysqlMq\\Model\\DataObject" + "schema_value" => \Magento\TestModuleMysqlMq\Model\DataObject::class ], "response_schema" => [ "schema_type" => "object", - "schema_value" => "Magento\\Customer\\Api\\Data\\CustomerInterface" + "schema_value" => \Magento\Customer\Api\Data\CustomerInterface::class ], "publisher" => "test-publisher-5" ] diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/apply_tax_for_simple_product.php b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/apply_tax_for_simple_product.php new file mode 100644 index 0000000000000..9968704517ecd --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/apply_tax_for_simple_product.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Tax\Model\ClassModel as TaxClassModel; +use Magento\Tax\Model\ResourceModel\TaxClass\CollectionFactory as TaxClassCollectionFactory; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$product = $productRepository->get('simple_product'); + +/** @var TaxClassCollectionFactory $taxClassCollectionFactory */ +$taxClassCollectionFactory = $objectManager->get(TaxClassCollectionFactory::class); +$taxClassCollection = $taxClassCollectionFactory->create(); + +/** @var TaxClassModel $taxClass */ +$taxClassCollection->addFieldToFilter('class_type', TaxClassModel::TAX_CLASS_TYPE_PRODUCT); +$taxClass = $taxClassCollection->getFirstItem(); + +$product->setCustomAttribute('tax_class_id', $taxClass->getClassId()); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/virtual_product.php b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/virtual_product.php new file mode 100644 index 0000000000000..e4472464e17ae --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/virtual_product.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\Data\ProductInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Framework\Api\DataObjectHelper; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductInterfaceFactory $productFactory */ +$productFactory = $objectManager->get(ProductInterfaceFactory::class); +/** @var DataObjectHelper $dataObjectHelper */ +$dataObjectHelper = Bootstrap::getObjectManager()->get(DataObjectHelper::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); + +$product = $productFactory->create(); +$productData = [ + ProductInterface::TYPE_ID => Type::TYPE_VIRTUAL, + ProductInterface::ATTRIBUTE_SET_ID => 4, + ProductInterface::SKU => 'virtual_product', + ProductInterface::NAME => 'Virtual Product', + ProductInterface::PRICE => 10, + ProductInterface::VISIBILITY => Visibility::VISIBILITY_BOTH, + ProductInterface::STATUS => Status::STATUS_ENABLED, +]; +$dataObjectHelper->populateWithArray($product, $productData, ProductInterface::class); +/** Out of interface */ +$product + ->setWebsiteIds([1]) + ->setStockData([ + 'qty' => 85.5, + 'is_in_stock' => true, + 'manage_stock' => true, + 'is_qty_decimal' => true + ]); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/virtual_product_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/virtual_product_rollback.php new file mode 100644 index 0000000000000..f8d329f574626 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/virtual_product_rollback.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); + +$currentArea = $registry->registry('isSecureArea'); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +try { + $productRepository->deleteById('virtual_product'); +} catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + /** + * Tests which are wrapped with MySQL transaction clear all data by transaction rollback. + */ +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', $currentArea); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php b/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php index 384892d6fd5d2..d0d746812ec44 100644 --- a/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php @@ -38,6 +38,9 @@ class GraphQlControllerTest extends \Magento\TestFramework\Indexer\TestCase /** @var MetadataPool */ private $metadataPool; + /** @var Http */ + private $request; + public static function setUpBeforeClass() { $db = Bootstrap::getInstance()->getBootstrap() @@ -57,6 +60,7 @@ protected function setUp() : void $this->graphql = $this->objectManager->get(\Magento\GraphQl\Controller\GraphQl::class); $this->jsonSerializer = $this->objectManager->get(SerializerInterface::class); $this->metadataPool = $this->objectManager->get(MetadataPool::class); + $this->request = $this->objectManager->get(Http::class); } /** @@ -86,27 +90,120 @@ public function testDispatch() : void } QUERY; $postData = [ - 'query' => $query, - 'variables' => null, + 'query' => $query, + 'variables' => null, 'operationName' => null ]; - /** @var Http $request */ - $request = $this->objectManager->get(\Magento\Framework\App\Request\Http::class); - $request->setPathInfo('/graphql'); - $request->setContent(json_encode($postData)); + + $this->request->setPathInfo('/graphql'); + $this->request->setMethod('POST'); + $this->request->setContent(json_encode($postData)); $headers = $this->objectManager->create(\Zend\Http\Headers::class) ->addHeaders(['Content-Type' => 'application/json']); - $request->setHeaders($headers); - $response = $this->graphql->dispatch($request); + $this->request->setHeaders($headers); + $response = $this->graphql->dispatch($this->request); $output = $this->jsonSerializer->unserialize($response->getContent()); $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); $this->assertArrayNotHasKey('errors', $output, 'Response has errors'); - $this->assertTrue(!empty($output['data']['products']['items']), 'Products array has items'); - $this->assertTrue(!empty($output['data']['products']['items'][0]), 'Products array has items'); - $this->assertEquals($output['data']['products']['items'][0]['id'], $product->getData($linkField)); - $this->assertEquals($output['data']['products']['items'][0]['sku'], $product->getSku()); - $this->assertEquals($output['data']['products']['items'][0]['name'], $product->getName()); + $this->assertNotEmpty($output['data']['products']['items'], 'Products array has items'); + $this->assertNotEmpty($output['data']['products']['items'][0], 'Products array has items'); + $this->assertEquals($product->getData($linkField), $output['data']['products']['items'][0]['id']); + $this->assertEquals($product->getSku(), $output['data']['products']['items'][0]['sku']); + $this->assertEquals($product->getName(), $output['data']['products']['items'][0]['name']); + } + + /** + * Test request is dispatched and response generated when using GET request with query string + * + * @return void + */ + public function testDispatchWithGet() : void + { + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + + /** @var ProductInterface $product */ + $product = $productRepository->get('simple1'); + + $query + = <<<QUERY + { + products(filter: {sku: {eq: "simple1"}}) + { + items { + id + name + sku + } + } + } +QUERY; + + $this->request->setPathInfo('/graphql'); + $this->request->setMethod('GET'); + $this->request->setQueryValue('query', $query); + $response = $this->graphql->dispatch($this->request); + $output = $this->jsonSerializer->unserialize($response->getContent()); + $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); + + $this->assertArrayNotHasKey('errors', $output, 'Response has errors'); + $this->assertNotEmpty($output['data']['products']['items'], 'Products array has items'); + $this->assertNotEmpty($output['data']['products']['items'][0], 'Products array has items'); + $this->assertEquals($product->getData($linkField), $output['data']['products']['items'][0]['id']); + $this->assertEquals($product->getSku(), $output['data']['products']['items'][0]['sku']); + $this->assertEquals($product->getName(), $output['data']['products']['items'][0]['name']); + } + + /** Test request is dispatched and response generated when using GET request with parameterized query string + * + * @return void + */ + public function testDispatchGetWithParameterizedVariables() : void + { + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + + /** @var ProductInterface $product */ + $product = $productRepository->get('simple1'); + $query = <<<QUERY +query GetProducts(\$filterInput:ProductFilterInput){ + products( + filter:\$filterInput + ){ + items{ + id + name + sku + } + } +} +QUERY; + + $variables = [ + 'filterInput' => [ + 'sku' => ['eq' => 'simple1'] + ] + ]; + $queryParams = [ + 'query' => $query, + 'variables' => json_encode($variables), + 'operationName' => 'GetProducts' + ]; + + $this->request->setPathInfo('/graphql'); + $this->request->setMethod('GET'); + $this->request->setParams($queryParams); + $response = $this->graphql->dispatch($this->request); + $output = $this->jsonSerializer->unserialize($response->getContent()); + $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); + + $this->assertArrayNotHasKey('errors', $output, 'Response has errors'); + $this->assertNotEmpty($output['data']['products']['items'], 'Products array has items'); + $this->assertNotEmpty($output['data']['products']['items'][0], 'Products array has items'); + $this->assertEquals($product->getData($linkField), $output['data']['products']['items'][0]['id']); + $this->assertEquals($product->getSku(), $output['data']['products']['items'][0]['sku']); + $this->assertEquals($product->getName(), $output['data']['products']['items'][0]['name']); } /** @@ -136,25 +233,25 @@ public function testError() : void QUERY; $postData = [ - 'query' => $query, - 'variables' => null, + 'query' => $query, + 'variables' => null, 'operationName' => null ]; - /** @var Http $request */ - $request = $this->objectManager->get(\Magento\Framework\App\Request\Http::class); - $request->setPathInfo('/graphql'); - $request->setContent(json_encode($postData)); + + $this->request->setPathInfo('/graphql'); + $this->request->setMethod('POST'); + $this->request->setContent(json_encode($postData)); $headers = $this->objectManager->create(\Zend\Http\Headers::class) ->addHeaders(['Content-Type' => 'application/json']); - $request->setHeaders($headers); - $response = $this->graphql->dispatch($request); + $this->request->setHeaders($headers); + $response = $this->graphql->dispatch($this->request); $outputResponse = $this->jsonSerializer->unserialize($response->getContent()); if (isset($outputResponse['errors'][0])) { if (is_array($outputResponse['errors'][0])) { foreach ($outputResponse['errors'] as $error) { $this->assertEquals( - $error['category'], - \Magento\Framework\GraphQl\Exception\GraphQlInputException::EXCEPTION_CATEGORY + \Magento\Framework\GraphQl\Exception\GraphQlInputException::EXCEPTION_CATEGORY, + $error['category'] ); if (isset($error['message'])) { $this->assertEquals($error['message'], 'Invalid entity_type specified: invalid'); @@ -168,12 +265,4 @@ public function testError() : void } } } - - /** - * teardown - */ - public function tearDown() - { - parent::tearDown(); - } } diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/guest/set_guest_email.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/guest/set_guest_email.php new file mode 100644 index 0000000000000..c8084b2552395 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/guest/set_guest_email.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var QuoteFactory $quoteFactory */ +$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class); +/** @var CartRepositoryInterface $cartRepository */ +$cartRepository = Bootstrap::getObjectManager()->get(CartRepositoryInterface::class); +/** @var QuoteResource $quoteResource */ +$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class); + +$quote = $quoteFactory->create(); +$quoteResource->load($quote, 'test_quote', 'reserved_order_id'); + +$quote->setCustomerEmail('guest@example.com'); +$cartRepository->save($quote); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_new_shipping_address.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_new_shipping_address.php index e17b9e61f82db..54f4d8d0c6e75 100644 --- a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_new_shipping_address.php +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -26,7 +26,7 @@ $quoteAddressData = [ AddressInterface::KEY_TELEPHONE => 3468676, - AddressInterface::KEY_POSTCODE => 75477, + AddressInterface::KEY_POSTCODE => '75477', AddressInterface::KEY_COUNTRY_ID => 'US', AddressInterface::KEY_CITY => 'CityM', AddressInterface::KEY_COMPANY => 'CompanyName', diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_new_shipping_canada_address.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_new_shipping_canada_address.php new file mode 100644 index 0000000000000..8e60dc904bd4e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_new_shipping_canada_address.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Api\DataObjectHelper; +use Magento\Quote\Api\Data\AddressInterface; +use Magento\Quote\Api\Data\AddressInterfaceFactory; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\Quote\Model\ShippingAddressManagementInterface; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var QuoteFactory $quoteFactory */ +$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class); +/** @var QuoteResource $quoteResource */ +$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class); +/** @var AddressInterfaceFactory $quoteAddressFactory */ +$quoteAddressFactory = Bootstrap::getObjectManager()->get(AddressInterfaceFactory::class); +/** @var DataObjectHelper $dataObjectHelper */ +$dataObjectHelper = Bootstrap::getObjectManager()->get(DataObjectHelper::class); +/** @var ShippingAddressManagementInterface $shippingAddressManagement */ +$shippingAddressManagement = Bootstrap::getObjectManager()->get(ShippingAddressManagementInterface::class); + +$quoteAddressData = [ + AddressInterface::KEY_TELEPHONE => 3468676, + AddressInterface::KEY_POSTCODE => 'M4L 1V3', + AddressInterface::KEY_COUNTRY_ID => 'CA', + AddressInterface::KEY_CITY => 'Toronto', + AddressInterface::KEY_COMPANY => 'CompanyName', + AddressInterface::KEY_STREET => '500 Kingston Rd', + AddressInterface::KEY_LASTNAME => 'Smith', + AddressInterface::KEY_FIRSTNAME => 'John', + AddressInterface::KEY_REGION_CODE => 'ON', +]; +$quoteAddress = $quoteAddressFactory->create(); +$dataObjectHelper->populateWithArray($quoteAddress, $quoteAddressData, AddressInterfaceFactory::class); + +$quote = $quoteFactory->create(); +$quoteResource->load($quote, 'test_quote', 'reserved_order_id'); +$shippingAddressManagement->assign($quote->getId(), $quoteAddress); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Tax/_files/tax_rule_for_region_1.php b/dev/tests/integration/testsuite/Magento/GraphQl/Tax/_files/tax_rule_for_region_1.php new file mode 100644 index 0000000000000..aca55bd8414f6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Tax/_files/tax_rule_for_region_1.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Tax\Api\Data\TaxRateInterface; +use Magento\Tax\Api\Data\TaxRuleInterface; +use Magento\Tax\Api\TaxRateRepositoryInterface; +use Magento\Tax\Api\TaxRuleRepositoryInterface; +use Magento\Tax\Model\Calculation\Rate; +use Magento\Tax\Model\Calculation\RateFactory; +use Magento\Tax\Model\Calculation\RateRepository; +use Magento\Tax\Model\Calculation\Rule; +use Magento\Tax\Model\Calculation\RuleFactory; +use Magento\Tax\Model\TaxRuleRepository; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\Api\DataObjectHelper; + +$objectManager = Bootstrap::getObjectManager(); +/** @var DataObjectHelper $dataObjectHelper */ +$dataObjectHelper = Bootstrap::getObjectManager()->get(DataObjectHelper::class); +/** @var RateFactory $rateFactory */ +$rateFactory = $objectManager->get(RateFactory::class); +/** @var RuleFactory $ruleFactory */ +$ruleFactory = $objectManager->get(RuleFactory::class); +/** @var RateRepository $rateRepository */ +$rateRepository = $objectManager->get(TaxRateRepositoryInterface::class); +/** @var TaxRuleRepository $ruleRepository */ +$ruleRepository = $objectManager->get(TaxRuleRepositoryInterface::class); +/** @var Rate $rate */ +$rate = $rateFactory->create(); +$rateData = [ + Rate::KEY_COUNTRY_ID => 'US', + Rate::KEY_REGION_ID => '1', + Rate::KEY_POSTCODE => '*', + Rate::KEY_CODE => 'US-TEST-*-Rate-1', + Rate::KEY_PERCENTAGE_RATE => '7.5', +]; +$dataObjectHelper->populateWithArray($rate, $rateData, TaxRateInterface::class); +$rateRepository->save($rate); + +$rule = $ruleFactory->create(); +$ruleData = [ + Rule::KEY_CODE=> 'GraphQl Test Rule', + Rule::KEY_PRIORITY => '0', + Rule::KEY_POSITION => '0', + Rule::KEY_CUSTOMER_TAX_CLASS_IDS => [3], + Rule::KEY_PRODUCT_TAX_CLASS_IDS => [2], + Rule::KEY_TAX_RATE_IDS => [$rate->getId()], +]; +$dataObjectHelper->populateWithArray($rule, $ruleData, TaxRuleInterface::class); +$ruleRepository->save($rule); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Tax/_files/tax_rule_for_region_1_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Tax/_files/tax_rule_for_region_1_rollback.php new file mode 100644 index 0000000000000..aba1960624ed4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Tax/_files/tax_rule_for_region_1_rollback.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Tax\Api\TaxRateRepositoryInterface; +use Magento\Tax\Api\TaxRuleRepositoryInterface; +use Magento\Tax\Model\Calculation\Rate; +use Magento\Tax\Model\Calculation\RateFactory; +use Magento\Tax\Model\Calculation\RateRepository; +use Magento\Tax\Model\Calculation\Rule; +use Magento\Tax\Model\Calculation\RuleFactory; +use Magento\Tax\Model\TaxRuleRepository; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Tax\Model\ResourceModel\Calculation\Rate as RateResource; +use Magento\Tax\Model\ResourceModel\Calculation\Rule as RuleResource; + +$objectManager = Bootstrap::getObjectManager(); +/** @var RateFactory $rateFactory */ +$rateFactory = $objectManager->get(RateFactory::class); +/** @var RuleFactory $ruleFactory */ +$ruleFactory = $objectManager->get(RuleFactory::class); +/** @var RateRepository $rateRepository */ +$rateRepository = $objectManager->get(TaxRateRepositoryInterface::class); +/** @var TaxRuleRepository $ruleRepository */ +$ruleRepository = $objectManager->get(TaxRuleRepositoryInterface::class); +/** @var RateResource $rateResource */ +$rateResource = $objectManager->get(RateResource::class); +/** @var RuleResource $ruleResource */ +$ruleResource = $objectManager->get(RuleResource::class); + +$rate = $rateFactory->create(); +$rateResource->load($rate, 'US-TEST-*-Rate-1', Rate::KEY_CODE); +$rule = $ruleFactory->create(); +$ruleResource->load($rule, 'GraphQl Test Rule', Rule::KEY_CODE); +$ruleRepository->delete($rule); +$rateRepository->delete($rate); diff --git a/dev/tests/integration/testsuite/Magento/Ups/_files/enable_ups_shipping_method.php b/dev/tests/integration/testsuite/Magento/GraphQl/Ups/_files/enable_ups_shipping_method.php similarity index 81% rename from dev/tests/integration/testsuite/Magento/Ups/_files/enable_ups_shipping_method.php rename to dev/tests/integration/testsuite/Magento/GraphQl/Ups/_files/enable_ups_shipping_method.php index 5c6c60866fafb..42931db75a433 100644 --- a/dev/tests/integration/testsuite/Magento/Ups/_files/enable_ups_shipping_method.php +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Ups/_files/enable_ups_shipping_method.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +// TODO: Should be removed in scope of https://github.com/magento/graphql-ce/issues/167 declare(strict_types=1); use Magento\Framework\App\Config\Storage\Writer; @@ -15,6 +16,7 @@ $configWriter = $objectManager->get(WriterInterface::class); $configWriter->save('carriers/ups/active', 1); +$configWriter->save('carriers/ups/type', "UPS"); $scopeConfig = $objectManager->get(ScopeConfigInterface::class); $scopeConfig->clean(); diff --git a/dev/tests/integration/testsuite/Magento/Ups/_files/enable_ups_shipping_method_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Ups/_files/enable_ups_shipping_method_rollback.php similarity index 78% rename from dev/tests/integration/testsuite/Magento/Ups/_files/enable_ups_shipping_method_rollback.php rename to dev/tests/integration/testsuite/Magento/GraphQl/Ups/_files/enable_ups_shipping_method_rollback.php index 6d7894879f97b..cf6dc08dd91a4 100644 --- a/dev/tests/integration/testsuite/Magento/Ups/_files/enable_ups_shipping_method_rollback.php +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Ups/_files/enable_ups_shipping_method_rollback.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +// TODO: Should be removed in scope of https://github.com/magento/graphql-ce/issues/167 declare(strict_types=1); use Magento\Framework\App\Config\Storage\Writer; @@ -14,3 +15,4 @@ $configWriter = $objectManager->create(WriterInterface::class); $configWriter->delete('carriers/ups/active'); +$configWriter->delete('carriers/ups/type'); diff --git a/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/AbstractGraphqlCacheTest.php b/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/AbstractGraphqlCacheTest.php new file mode 100644 index 0000000000000..4cc46a8e745e8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/AbstractGraphqlCacheTest.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQlCache\Controller; + +use PHPUnit\Framework\TestCase; +use Magento\TestFramework\ObjectManager; +use Magento\TestFramework\Helper\Bootstrap; + +/** + * Abstract test class for Graphql cache tests + */ +abstract class AbstractGraphqlCacheTest extends TestCase +{ + /** + * @var ObjectManager + */ + protected $objectManager; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->usePageCachePlugin(); + } + + /** + * Enable full page cache plugin + */ + protected function usePageCachePlugin(): void + { + /** @var $registry \Magento\Framework\Registry */ + $registry = $this->objectManager->get(\Magento\Framework\Registry::class); + $registry->register('use_page_cache_plugin', true, true); + } +} diff --git a/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Catalog/CategoriesWithProductsCacheTest.php b/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Catalog/CategoriesWithProductsCacheTest.php new file mode 100644 index 0000000000000..62cda28a4493a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Catalog/CategoriesWithProductsCacheTest.php @@ -0,0 +1,103 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQlCache\Controller\Catalog; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\App\Request\Http; +use Magento\GraphQl\Controller\GraphQl; +use Magento\GraphQlCache\Controller\AbstractGraphqlCacheTest; + +/** + * Tests cache debug headers and cache tag validation for a category with product query + * + * @magentoAppArea graphql + * @magentoCache full_page enabled + * @magentoDbIsolation disabled + */ +class CategoriesWithProductsCacheTest extends AbstractGraphqlCacheTest +{ + /** + * @var GraphQl + */ + private $graphqlController; + + /** + * @var Http + */ + private $request; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + $this->graphqlController = $this->objectManager->get(\Magento\GraphQl\Controller\GraphQl::class); + $this->request = $this->objectManager->create(Http::class); + } + /** + * Test cache tags and debug header for category with products querying for products and category + * + * @magentoDataFixture Magento/Catalog/_files/category_product.php + */ + public function testToCheckRequestCacheTagsForCategoryWithProducts(): void + { + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + /** @var ProductInterface $product */ + $product = $productRepository->get('simple333'); + $categoryId ='333'; + $query + = <<<QUERY +query GetCategoryWithProducts(\$id: Int!, \$pageSize: Int!, \$currentPage: Int!) { + category(id: \$id) { + id + description + name + product_count + products( + pageSize: \$pageSize, + currentPage: \$currentPage) { + items { + id + name + attribute_set_id + url_key + sku + type_id + updated_at + url_key + url_path + } + total_count + } + } + } +QUERY; + $variables =[ + 'id' => $categoryId, + 'pageSize'=> 10, + 'currentPage' => 1 + ]; + $queryParams = [ + 'query' => $query, + 'variables' => json_encode($variables), + 'operationName' => 'GetCategoryWithProducts' + ]; + + $this->request->setPathInfo('/graphql'); + $this->request->setMethod('GET'); + $this->request->setParams($queryParams); + $response = $this->graphqlController->dispatch($this->request); + $this->assertEquals('MISS', $response->getHeader('X-Magento-Cache-Debug')->getFieldValue()); + $expectedCacheTags = ['cat_c','cat_c_' . $categoryId,'cat_p','cat_p_' . $product->getId(),'FPC']; + $actualCacheTags = explode(',', $response->getHeader('X-Magento-Tags')->getFieldValue()); + $this->assertEquals($expectedCacheTags, $actualCacheTags); + } +} diff --git a/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Catalog/CategoryCacheTest.php b/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Catalog/CategoryCacheTest.php new file mode 100644 index 0000000000000..96f6685233f2c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Catalog/CategoryCacheTest.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQlCache\Controller\Catalog; + +use Magento\Framework\App\Request\Http; +use Magento\GraphQl\Controller\GraphQl; +use Magento\GraphQlCache\Controller\AbstractGraphqlCacheTest; + +/** + * Tests cache debug headers and cache tag validation for a simple category query + * + * @magentoAppArea graphql + * @magentoCache full_page enabled + * @magentoDbIsolation disabled + */ +class CategoryCacheTest extends AbstractGraphqlCacheTest +{ + /** + * @var GraphQl + */ + private $graphqlController; + + /** + * @var Http + */ + private $request; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + $this->graphqlController = $this->objectManager->get(\Magento\GraphQl\Controller\GraphQl::class); + $this->request = $this->objectManager->create(Http::class); + } + /** + * Test cache tags and debug header for category and querying only for category + * + * @magentoDataFixture Magento/Catalog/_files/category_product.php + */ + public function testToCheckRequestCacheTagsForForCategory(): void + { + $categoryId ='333'; + $query + = <<<QUERY + { + category(id: $categoryId) { + id + name + url_key + description + product_count + } + } +QUERY; + $this->request->setPathInfo('/graphql'); + $this->request->setMethod('GET'); + $this->request->setQueryValue('query', $query); + $response = $this->graphqlController->dispatch($this->request); + $this->assertEquals('MISS', $response->getHeader('X-Magento-Cache-Debug')->getFieldValue()); + $actualCacheTags = explode(',', $response->getHeader('X-Magento-Tags')->getFieldValue()); + $expectedCacheTags = ['cat_c','cat_c_' . $categoryId,'FPC']; + $this->assertEquals($expectedCacheTags, $actualCacheTags); + } +} diff --git a/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Catalog/DeepNestedCategoriesAndProductsTest.php b/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Catalog/DeepNestedCategoriesAndProductsTest.php new file mode 100644 index 0000000000000..7f992a0843f7c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Catalog/DeepNestedCategoriesAndProductsTest.php @@ -0,0 +1,128 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQlCache\Controller\Catalog; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Framework\App\Request\Http; +use Magento\GraphQlCache\Controller\AbstractGraphqlCacheTest; + +/** + * Tests cache debug headers and cache tag validation for a deep nested category and product query + * + * @magentoAppArea graphql + * @magentoDbIsolation disabled + */ +class DeepNestedCategoriesAndProductsTest extends AbstractGraphqlCacheTest +{ + /** @var \Magento\GraphQl\Controller\GraphQl */ + private $graphql; + + /** @var Http */ + private $request; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + $this->graphql = $this->objectManager->get(\Magento\GraphQl\Controller\GraphQl::class); + $this->request = $this->objectManager->get(Http::class); + } + + /** + * Test cache tags and debug header for deep nested queries involving category and products + * + * @magentoCache all enabled + * @magentoDataFixture Magento/Catalog/_files/product_in_multiple_categories.php + * + */ + public function testDispatchForCacheHeadersOnDeepNestedQueries(): void + { + $baseCategoryId ='333'; + $query + = <<<QUERY + { + category(id: $baseCategoryId) { + products { + items { + attribute_set_id + country_of_manufacture + created_at + description { + html + } + gift_message_available + id + categories { + name + url_path + available_sort_by + level + products { + items { + name + id + } + } + } + } + } + } +} +QUERY; + /** @var CategoryRepositoryInterface $categoryRepository */ + $categoryRepository = $this->objectManager->get(CategoryRepositoryInterface::class); + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + + $resolvedCategoryIds = []; + $category = $categoryRepository->get($baseCategoryId); + + $productIdsFromCategory = $category->getProductCollection()->getAllIds(); + foreach ($productIdsFromCategory as $productId) { + $resolvedCategoryIds = array_merge( + $resolvedCategoryIds, + $productRepository->getById($productId)->getCategoryIds() + ); + } + + $resolvedCategoryIds = array_merge($resolvedCategoryIds, [$baseCategoryId]); + foreach ($resolvedCategoryIds as $categoryId) { + $category = $categoryRepository->get($categoryId); + $productIdsFromCategory= array_merge( + $productIdsFromCategory, + $category->getProductCollection()->getAllIds() + ); + } + + $uniqueProductIds = array_unique($productIdsFromCategory); + $uniqueCategoryIds = array_unique($resolvedCategoryIds); + $expectedCacheTags = ['cat_c', 'cat_p', 'FPC']; + foreach ($uniqueProductIds as $uniqueProductId) { + $expectedCacheTags = array_merge($expectedCacheTags, ['cat_p_'.$uniqueProductId]); + } + foreach ($uniqueCategoryIds as $uniqueCategoryId) { + $expectedCacheTags = array_merge($expectedCacheTags, ['cat_c_'.$uniqueCategoryId]); + } + + $this->request->setPathInfo('/graphql'); + $this->request->setMethod('GET'); + $this->request->setQueryValue('query', $query); + $response = $this->graphql->dispatch($this->request); + $this->assertEquals('MISS', $response->getHeader('X-Magento-Cache-Debug')->getFieldValue()); + $actualCacheTags = explode(',', $response->getHeader('X-Magento-Tags')->getFieldValue()); + $this->assertEmpty( + array_merge( + array_diff($expectedCacheTags, $actualCacheTags), + array_diff($actualCacheTags, $expectedCacheTags) + ) + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Catalog/ProductsCacheTest.php b/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Catalog/ProductsCacheTest.php new file mode 100644 index 0000000000000..78534176a3525 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Catalog/ProductsCacheTest.php @@ -0,0 +1,115 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQlCache\Controller\Catalog; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\App\Request\Http; +use Magento\GraphQl\Controller\GraphQl; +use Magento\GraphQlCache\Controller\AbstractGraphqlCacheTest; + +/** + * Tests cache debug headers and cache tag validation for a simple product query + * + * @magentoAppArea graphql + * @magentoCache full_page enabled + * @magentoDbIsolation disabled + */ +class ProductsCacheTest extends AbstractGraphqlCacheTest +{ + /** + * @var GraphQl + */ + private $graphqlController; + + /** + * @var Http + */ + private $request; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + $this->graphqlController = $this->objectManager->get(\Magento\GraphQl\Controller\GraphQl::class); + $this->request = $this->objectManager->create(Http::class); + } + + /** + * Test request is dispatched and response is checked for debug headers and cache tags + * + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_url_key.php + */ + public function testToCheckRequestCacheTagsForProducts(): void + { + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + + /** @var ProductInterface $product */ + $product = $productRepository->get('simple1'); + + $query + = <<<QUERY + { + products(filter: {sku: {eq: "simple1"}}) + { + items { + id + name + sku + description { + html + } + } + } + } +QUERY; + + $this->request->setPathInfo('/graphql'); + $this->request->setMethod('GET'); + $this->request->setQueryValue('query', $query); + $response = $this->graphqlController->dispatch($this->request); + $this->assertEquals('MISS', $response->getHeader('X-Magento-Cache-Debug')->getFieldValue()); + $actualCacheTags = explode(',', $response->getHeader('X-Magento-Tags')->getFieldValue()); + $expectedCacheTags = ['cat_p', 'cat_p_' . $product->getId(), 'FPC']; + $this->assertEquals($expectedCacheTags, $actualCacheTags); + } + + /** + * Test request is checked for debug headers and no cache tags for not existing product + */ + public function testToCheckRequestNoTagsForProducts(): void + { + $query + = <<<QUERY + { + products(filter: {sku: {eq: "simple10"}}) + { + items { + id + name + sku + description { + html + } + } + } + } + +QUERY; + $this->request->setPathInfo('/graphql'); + $this->request->setMethod('GET'); + $this->request->setQueryValue('query', $query); + $response = $this->graphqlController->dispatch($this->request); + $this->assertEquals('MISS', $response->getHeader('X-Magento-Cache-Debug')->getFieldValue()); + $actualCacheTags = explode(',', $response->getHeader('X-Magento-Tags')->getFieldValue()); + $expectedCacheTags = ['FPC']; + $this->assertEquals($expectedCacheTags, $actualCacheTags); + } +} diff --git a/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Cms/BlockCacheTest.php b/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Cms/BlockCacheTest.php new file mode 100644 index 0000000000000..160f5f9109f30 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Cms/BlockCacheTest.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQlCache\Controller\Cms; + +use Magento\Cms\Model\BlockRepository; +use Magento\Framework\App\Request\Http; +use Magento\GraphQl\Controller\GraphQl; +use Magento\GraphQlCache\Controller\AbstractGraphqlCacheTest; + +/** + * Test caching works for CMS blocks + * + * @magentoAppArea graphql + * @magentoCache full_page enabled + * @magentoDbIsolation disabled + */ +class BlockCacheTest extends AbstractGraphqlCacheTest +{ + /** + * @var GraphQl + */ + private $graphqlController; + + /** + * @var Http + */ + private $request; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + $this->graphqlController = $this->objectManager->get(\Magento\GraphQl\Controller\GraphQl::class); + $this->request = $this->objectManager->create(Http::class); + } + + /** + * Test that the correct cache tags get added to request for cmsBlocks + * + * @magentoDataFixture Magento/Cms/_files/block.php + */ + public function testCmsBlocksRequestHasCorrectTags(): void + { + $blockIdentifier = 'fixture_block'; + $blockRepository = $this->objectManager->get(BlockRepository::class); + $block = $blockRepository->getById($blockIdentifier); + + $query + = <<<QUERY + { + cmsBlocks(identifiers: ["$blockIdentifier"]) { + items { + title + identifier + content + } + } +} +QUERY; + + $this->request->setPathInfo('/graphql'); + $this->request->setMethod('GET'); + $this->request->setQueryValue('query', $query); + $response = $this->graphqlController->dispatch($this->request); + $this->assertEquals('MISS', $response->getHeader('X-Magento-Cache-Debug')->getFieldValue()); + $expectedCacheTags = ['cms_b', 'cms_b_' . $block->getId(), 'cms_b_' . $block->getIdentifier(), 'FPC']; + $rawActualCacheTags = $response->getHeader('X-Magento-Tags')->getFieldValue(); + $actualCacheTags = explode(',', $rawActualCacheTags); + $this->assertEquals($expectedCacheTags, $actualCacheTags); + } +} diff --git a/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Cms/CmsPageCacheTest.php b/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Cms/CmsPageCacheTest.php new file mode 100644 index 0000000000000..8d4bbfc0f2b17 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Cms/CmsPageCacheTest.php @@ -0,0 +1,80 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQlCache\Controller\Cms; + +use Magento\Cms\Model\GetPageByIdentifier; +use Magento\Framework\App\Request\Http; +use Magento\GraphQl\Controller\GraphQl; +use Magento\GraphQlCache\Controller\AbstractGraphqlCacheTest; + +/** + * Test caching works for CMS page + * + * @magentoAppArea graphql + * @magentoCache full_page enabled + * @magentoDbIsolation disabled + * + */ +class CmsPageCacheTest extends AbstractGraphqlCacheTest +{ + /** + * @var GraphQl + */ + private $graphqlController; + + /** + * @var Http + */ + private $request; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + $this->graphqlController = $this->objectManager->get(\Magento\GraphQl\Controller\GraphQl::class); + $this->request = $this->objectManager->create(Http::class); + } + + /** + * Test that the correct cache tags get added to request for cmsPage query + * + * @magentoDataFixture Magento/Cms/_files/pages.php + */ + public function testToCheckCmsPageRequestCacheTags(): void + { + $cmsPage = $this->objectManager->get(GetPageByIdentifier::class)->execute('page100', 0); + $pageId = $cmsPage->getId(); + + $query = + <<<QUERY + { + cmsPage(id: $pageId) { + url_key + title + content + content_heading + page_layout + meta_title + meta_description + meta_keywords + } + } +QUERY; + + $this->request->setPathInfo('/graphql'); + $this->request->setMethod('GET'); + $this->request->setQueryValue('query', $query); + $response = $this->graphqlController->dispatch($this->request); + $this->assertEquals('MISS', $response->getHeader('X-Magento-Cache-Debug')->getFieldValue()); + $requestedCacheTags = explode(',', $response->getHeader('X-Magento-Tags')->getFieldValue()); + $expectedCacheTags = ['cms_p', 'cms_p_' .$pageId , 'FPC']; + $this->assertEquals($expectedCacheTags, $requestedCacheTags); + } +} diff --git a/dev/tests/integration/testsuite/Magento/MysqlMq/Model/Processor.php b/dev/tests/integration/testsuite/Magento/MysqlMq/Model/Processor.php deleted file mode 100644 index 3b2a76104a2cd..0000000000000 --- a/dev/tests/integration/testsuite/Magento/MysqlMq/Model/Processor.php +++ /dev/null @@ -1,28 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\MysqlMq\Model; - -/** - * Test message processor is used by \Magento\MysqlMq\Model\PublisherTest - */ -class Processor -{ - /** - * @param \Magento\MysqlMq\Model\DataObject $message - */ - public function processMessage($message) - { - echo "Processed {$message->getEntityId()}\n"; - } - - /** - * @param \Magento\MysqlMq\Model\DataObject $message - */ - public function processMessageWithException($message) - { - throw new \LogicException("Exception during message processing happened. Entity: {{$message->getEntityId()}}"); - } -} diff --git a/dev/tests/integration/testsuite/Magento/MysqlMq/Model/PublisherConsumerTest.php b/dev/tests/integration/testsuite/Magento/MysqlMq/Model/PublisherConsumerTest.php index f03d03d3a25fd..f911165bd27fb 100644 --- a/dev/tests/integration/testsuite/Magento/MysqlMq/Model/PublisherConsumerTest.php +++ b/dev/tests/integration/testsuite/Magento/MysqlMq/Model/PublisherConsumerTest.php @@ -3,87 +3,45 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\MysqlMq\Model; -use Magento\Framework\MessageQueue\PublisherInterface; +use Magento\Framework\MessageQueue\UseCase\QueueTestCaseAbstract; +use Magento\MysqlMq\Model\ResourceModel\MessageCollection; +use Magento\MysqlMq\Model\ResourceModel\MessageStatusCollection; /** * Test for MySQL publisher class. * * @magentoDbIsolation disabled */ -class PublisherConsumerTest extends \PHPUnit\Framework\TestCase +class PublisherConsumerTest extends QueueTestCaseAbstract { const MAX_NUMBER_OF_TRIALS = 3; /** - * @var \Magento\Framework\MessageQueue\PublisherInterface + * @var string[] */ - protected $publisher; - - /** - * @var \Magento\Framework\ObjectManagerInterface - */ - protected $objectManager; - - protected function setUp() - { - $this->markTestIncomplete('Should be converted to queue config v2.'); - $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - - $configPath = __DIR__ . '/../etc/queue.xml'; - $fileResolverMock = $this->createMock(\Magento\Framework\Config\FileResolverInterface::class); - $fileResolverMock->expects($this->any()) - ->method('get') - ->willReturn([$configPath => file_get_contents(($configPath))]); - - /** @var \Magento\Framework\MessageQueue\Config\Reader\Xml $xmlReader */ - $xmlReader = $this->objectManager->create( - \Magento\Framework\MessageQueue\Config\Reader\Xml::class, - ['fileResolver' => $fileResolverMock] - ); - - $newData = $xmlReader->read(); - - /** @var \Magento\Framework\MessageQueue\Config\Data $configData */ - $configData = $this->objectManager->get(\Magento\Framework\MessageQueue\Config\Data::class); - $configData->reset(); - $configData->merge($newData); - - $this->publisher = $this->objectManager->create(\Magento\Framework\MessageQueue\PublisherInterface::class); - } - - protected function tearDown() - { - $this->markTestIncomplete('Should be converted to queue config v2.'); - $this->consumeMessages('demoConsumerQueueOne', PHP_INT_MAX); - $this->consumeMessages('demoConsumerQueueTwo', PHP_INT_MAX); - $this->consumeMessages('demoConsumerQueueThree', PHP_INT_MAX); - $this->consumeMessages('demoConsumerQueueFour', PHP_INT_MAX); - $this->consumeMessages('demoConsumerQueueFive', PHP_INT_MAX); - $this->consumeMessages('demoConsumerQueueOneWithException', PHP_INT_MAX); - - $objectManagerConfiguration = [\Magento\Framework\MessageQueue\Config\Reader\Xml::class => [ - 'arguments' => [ - 'fileResolver' => ['instance' => \Magento\Framework\Config\FileResolverInterface::class], - ], - ], - ]; - $this->objectManager->configure($objectManagerConfiguration); - /** @var \Magento\Framework\MessageQueue\Config\Data $queueConfig */ - $queueConfig = $this->objectManager->get(\Magento\Framework\MessageQueue\Config\Data::class); - $queueConfig->reset(); - } + protected $consumers = [ + 'demoConsumerQueueOne', + 'demoConsumerQueueTwo', + 'demoConsumerQueueThree', + 'delayedOperationConsumer', + 'demoConsumerWithException' + ]; /** * @magentoDataFixture Magento/MysqlMq/_files/queues.php */ public function testPublishConsumeFlow() { - /** @var \Magento\MysqlMq\Model\DataObjectFactory $objectFactory */ - $objectFactory = $this->objectManager->create(\Magento\MysqlMq\Model\DataObjectFactory::class); - /** @var \Magento\MysqlMq\Model\DataObject $object */ + /** @var \Magento\TestModuleMysqlMq\Model\DataObjectFactory $objectFactory */ + $objectFactory = $this->objectManager->create(\Magento\TestModuleMysqlMq\Model\DataObjectFactory::class); + /** @var \Magento\TestModuleMysqlMq\Model\DataObject $object */ $object = $objectFactory->create(); + $object->setOutputPath($this->logFilePath); + file_put_contents($this->logFilePath, ''); for ($i = 0; $i < 10; $i++) { $object->setName('Object name ' . $i)->setEntityId($i); $this->publisher->publish('demo.object.created', $object); @@ -96,105 +54,87 @@ public function testPublishConsumeFlow() $object->setName('Object name ' . $i)->setEntityId($i); $this->publisher->publish('demo.object.custom.created', $object); } - - $outputPattern = '/(Processed \d+\s)/'; - /** There are total of 10 messages in the first queue, total expected consumption is 7, 3 then 0 */ - $this->consumeMessages('demoConsumerQueueOne', 7, 7, $outputPattern); - /** Consumer all messages which left in this queue */ - $this->consumeMessages('demoConsumerQueueOne', PHP_INT_MAX, 3, $outputPattern); - $this->consumeMessages('demoConsumerQueueOne', 7, 0, $outputPattern); - - /** Verify that messages were added correctly to second queue for update and create topics */ - $this->consumeMessages('demoConsumerQueueTwo', 20, 15, $outputPattern); - - /** Verify that messages were NOT added to fourth queue */ - $this->consumeMessages('demoConsumerQueueFour', 11, 0, $outputPattern); - - /** Verify that messages were added correctly by '*' pattern in bind config to third queue */ - $this->consumeMessages('demoConsumerQueueThree', 20, 15, $outputPattern); - - /** Verify that messages were added correctly by '#' pattern in bind config to fifth queue */ - $this->consumeMessages('demoConsumerQueueFive', 20, 18, $outputPattern); + $this->waitForAsynchronousResult(18, $this->logFilePath); + + //Check lines in file + $createdPattern = '/Processed object created \d+/'; + $updatedPattern = '/Processed object updated \d+/'; + $customCreatedPattern = '/Processed custom object created \d+/'; + $logFileContents = file_get_contents($this->logFilePath); + + preg_match_all($createdPattern, $logFileContents, $createdMatches); + $this->assertEquals(10, count($createdMatches[0])); + preg_match_all($updatedPattern, $logFileContents, $updatedMatches); + $this->assertEquals(5, count($updatedMatches[0])); + preg_match_all($customCreatedPattern, $logFileContents, $customCreatedMatches); + $this->assertEquals(3, count($customCreatedMatches[0])); } /** * @magentoDataFixture Magento/MysqlMq/_files/queues.php */ - public function testPublishAndConsumeWithFailedJobs() + public function testPublishAndConsumeSchemaDefinedByMethod() { - /** @var \Magento\MysqlMq\Model\DataObjectFactory $objectFactory */ - $objectFactory = $this->objectManager->create(\Magento\MysqlMq\Model\DataObjectFactory::class); - /** @var \Magento\MysqlMq\Model\DataObject $object */ - /** Try consume messages for MAX_NUMBER_OF_TRIALS and then consumer them without exception */ + $topic = 'test.schema.defined.by.method'; + /** @var \Magento\TestModuleMysqlMq\Model\DataObjectFactory $objectFactory */ + $objectFactory = $this->objectManager->create(\Magento\TestModuleMysqlMq\Model\DataObjectFactory::class); + /** @var \Magento\TestModuleMysqlMq\Model\DataObject $object */ $object = $objectFactory->create(); - for ($i = 0; $i < 5; $i++) { - $object->setName('Object name ' . $i)->setEntityId($i); - $this->publisher->publish('demo.object.created', $object); - } - $outputPattern = '/(Processed \d+\s)/'; - for ($i = 0; $i < self::MAX_NUMBER_OF_TRIALS; $i++) { - $this->consumeMessages('demoConsumerQueueOneWithException', PHP_INT_MAX, 0, $outputPattern); - } - $this->consumeMessages('demoConsumerQueueOne', PHP_INT_MAX, 0, $outputPattern); + $id = 33; + $object->setName('Object name ' . $id)->setEntityId($id); + $object->setOutputPath($this->logFilePath); + $requiredStringParam = 'Required value'; + $optionalIntParam = 44; + $this->publisher->publish($topic, [$object, $requiredStringParam, $optionalIntParam]); - /** Try consume messages for MAX_NUMBER_OF_TRIALS+1 and then consumer them without exception */ - for ($i = 0; $i < 5; $i++) { - $object->setName('Object name ' . $i)->setEntityId($i); - $this->publisher->publish('demo.object.created', $object); - } - /** Try consume messages for MAX_NUMBER_OF_TRIALS and then consumer them without exception */ - for ($i = 0; $i < self::MAX_NUMBER_OF_TRIALS + 1; $i++) { - $this->consumeMessages('demoConsumerQueueOneWithException', PHP_INT_MAX, 0, $outputPattern); - } - /** Make sure that messages are not accessible anymore after number of trials is exceeded */ - $this->consumeMessages('demoConsumerQueueOne', PHP_INT_MAX, 0, $outputPattern); + $expectedOutput = "Processed '{$object->getEntityId()}'; " + . "Required param '{$requiredStringParam}'; Optional param '{$optionalIntParam}'"; + + $this->waitForAsynchronousResult(1, $this->logFilePath); + + $this->assertEquals($expectedOutput, trim(file_get_contents($this->logFilePath))); } /** * @magentoDataFixture Magento/MysqlMq/_files/queues.php */ - public function testPublishAndConsumeSchemaDefinedByMethod() + public function testConsumeWithException() { - /** @var \Magento\MysqlMq\Model\DataObjectFactory $objectFactory */ - $objectFactory = $this->objectManager->create(\Magento\MysqlMq\Model\DataObjectFactory::class); - /** @var \Magento\MysqlMq\Model\DataObject $object */ + $topic = 'demo.exception'; + /** @var \Magento\TestModuleMysqlMq\Model\DataObjectFactory $objectFactory */ + $objectFactory = $this->objectManager->create(\Magento\TestModuleMysqlMq\Model\DataObjectFactory::class); + /** @var \Magento\TestModuleMysqlMq\Model\DataObject $object */ $object = $objectFactory->create(); - $id = 33; + $id = 99; + $object->setName('Object name ' . $id)->setEntityId($id); - $requiredStringParam = 'Required value'; - $optionalIntParam = 44; - $this->publisher->publish('test.schema.defined.by.method', [$object, $requiredStringParam, $optionalIntParam]); - $outputPattern = "/Processed '{$object->getEntityId()}'; " - . "Required param '{$requiredStringParam}'; Optional param '{$optionalIntParam}'/"; - $this->consumeMessages('delayedOperationConsumer', PHP_INT_MAX, 1, $outputPattern); + $object->setOutputPath($this->logFilePath); + $this->publisher->publish($topic, $object); + $expectedOutput = "Exception processing {$id}"; + $this->waitForAsynchronousResult(1, $this->logFilePath); + $message = $this->getTopicLatestMessage($topic); + $this->assertEquals($expectedOutput, trim(file_get_contents($this->logFilePath))); + $this->assertEquals(QueueManagement::MESSAGE_STATUS_ERROR, $message->getStatus()); } /** - * Make sure that consumers consume correct number of messages. - * - * @param string $consumerName - * @param int|null $messagesToProcess - * @param int|null $expectedNumberOfProcessedMessages - * @param string|null $outputPattern + * @param string $topic + * @return Message */ - protected function consumeMessages( - $consumerName, - $messagesToProcess, - $expectedNumberOfProcessedMessages = null, - $outputPattern = null - ) { - /** @var \Magento\Framework\MessageQueue\ConsumerFactory $consumerFactory */ - $consumerFactory = $this->objectManager->create(\Magento\Framework\MessageQueue\ConsumerFactory::class); - $consumer = $consumerFactory->get($consumerName); - ob_start(); - $consumer->process($messagesToProcess); - $consumersOutput = ob_get_contents(); - ob_end_clean(); - if ($outputPattern) { - $this->assertEquals( - $expectedNumberOfProcessedMessages, - preg_match_all($outputPattern, $consumersOutput) - ); - } + private function getTopicLatestMessage(string $topic) : Message + { + // Assert message status is error + $messageCollection = $this->objectManager->create(MessageCollection::class); + $messageStatusCollection = $this->objectManager->create(MessageStatusCollection::class); + + $messageCollection->addFilter('topic_name', $topic); + $messageCollection->join( + ['status' => $messageStatusCollection->getMainTable()], + "status.message_id = main_table.id" + ); + $messageCollection->addOrder('updated_at', MessageCollection::SORT_ORDER_DESC); + + $message = $messageCollection->getFirstItem(); + return $message; } } diff --git a/dev/tests/integration/testsuite/Magento/MysqlMq/Model/QueueManagementTest.php b/dev/tests/integration/testsuite/Magento/MysqlMq/Model/QueueManagementTest.php index 197df29233297..56dd77d3da17c 100644 --- a/dev/tests/integration/testsuite/Magento/MysqlMq/Model/QueueManagementTest.php +++ b/dev/tests/integration/testsuite/Magento/MysqlMq/Model/QueueManagementTest.php @@ -5,8 +5,6 @@ */ namespace Magento\MysqlMq\Model; -use Magento\MysqlMq\Model\QueueManagement; - /** * Test for Queue Management class. */ diff --git a/dev/tests/integration/testsuite/Magento/MysqlMq/etc/queue.xml b/dev/tests/integration/testsuite/Magento/MysqlMq/etc/queue.xml deleted file mode 100644 index fd618d504df07..0000000000000 --- a/dev/tests/integration/testsuite/Magento/MysqlMq/etc/queue.xml +++ /dev/null @@ -1,67 +0,0 @@ -<?xml version="1.0"?> -<!-- -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/queue.xsd"> - <publisher name="demo-publisher-1" connection="db" exchange="magento"/> - <publisher name="demo-publisher-2" connection="db" exchange="magento"/> - - <publisher name="test-publisher-1" connection="amqp" exchange="magento"/> - <publisher name="test-publisher-3" connection="amqp" exchange="test-exchange-1"/> - - <topic name="demo.object.created" schema="Magento\MysqlMq\Model\DataObject" publisher="demo-publisher-1"/> - <topic name="demo.object.updated" schema="Magento\MysqlMq\Model\DataObject" publisher="demo-publisher-2"/> - <topic name="demo.object.custom.created" schema="Magento\MysqlMq\Model\DataObject" publisher="demo-publisher-2"/> - - <topic name="test.schema.defined.by.method" schema="Magento\MysqlMq\Model\DataObjectRepository::delayedOperation" publisher="demo-publisher-2"/> - - <topic name="customer.created" schema="Magento\Customer\Api\Data\CustomerInterface" publisher="test-publisher-1"/> - <topic name="customer.created.one" schema="Magento\Customer\Api\Data\CustomerInterface" publisher="test-publisher-1"/> - <topic name="customer.created.one.two" schema="Magento\Customer\Api\Data\CustomerInterface" publisher="test-publisher-1"/> - <topic name="customer.created.two" schema="Magento\Customer\Api\Data\CustomerInterface" publisher="test-publisher-1"/> - <topic name="customer.updated" schema="Magento\Customer\Api\Data\CustomerInterface" publisher="demo-publisher-2"/> - <topic name="customer.deleted" schema="Magento\Customer\Api\Data\CustomerInterface" publisher="demo-publisher-2"/> - <topic name="cart.created" schema="Magento\Quote\Api\Data\CartInterface" publisher="test-publisher-3"/> - <topic name="cart.created.one" schema="Magento\Quote\Api\Data\CartInterface" publisher="test-publisher-3"/> - - <consumer name="demoConsumerQueueOne" queue="demo-queue-1" connection="db" class="\Magento\MysqlMq\Model\Processor" method="processMessage"/> - <consumer name="demoConsumerQueueOneWithException" queue="demo-queue-1" connection="db" class="\Magento\MysqlMq\Model\Processor" method="processMessageWithException"/> - <consumer name="demoConsumerQueueTwo" queue="demo-queue-2" connection="db" class="\Magento\MysqlMq\Model\Processor" method="processMessage"/> - <consumer name="demoConsumerQueueThree" queue="demo-queue-3" connection="db" class="\Magento\MysqlMq\Model\Processor" method="processMessage"/> - <consumer name="demoConsumerQueueFour" queue="demo-queue-4" connection="db" class="\Magento\MysqlMq\Model\Processor" method="processMessage"/> - <consumer name="demoConsumerQueueFive" queue="demo-queue-5" connection="db" class="\Magento\MysqlMq\Model\Processor" method="processMessage"/> - - <consumer name="customerCreatedListener" queue="test-queue-1" connection="amqp" class="Magento\MysqlMq\Model\Processor" method="processMessage"/> - <consumer name="customerDeletedListener" queue="test-queue-2" connection="db" class="Magento\MysqlMq\Model\Processor" method="processMessage" max_messages="98765"/> - <consumer name="cartCreatedListener" queue="test-queue-3" connection="amqp" class="Magento\MysqlMq\Model\Processor" method="processMessage"/> - - <consumer name="delayedOperationConsumer" queue="demo-queue-6" connection="db" class="Magento\MysqlMq\Model\DataObjectRepository" method="delayedOperation"/> - - <bind queue="demo-queue-1" exchange="magento" topic="demo.object.created"/> - <bind queue="demo-queue-2" exchange="magento" topic="demo.object.created"/> - <bind queue="demo-queue-2" exchange="magento" topic="demo.object.updated"/> - <bind queue="demo-queue-3" exchange="magento" topic="demo.object.*"/> - <bind queue="demo-queue-5" exchange="magento" topic="demo.object.#"/> - - <bind queue="demo-queue-6" exchange="magento" topic="test.schema.defined.by.method"/> - - <bind queue="test-queue-1" exchange="magento" topic="customer.created"/> - <bind queue="test-queue-1" exchange="magento" topic="customer.created.one"/> - <bind queue="test-queue-1" exchange="magento" topic="customer.created.one.two"/> - <bind queue="test-queue-1" exchange="magento" topic="customer.created.two"/> - <bind queue="test-queue-1" exchange="magento" topic="customer.updated"/> - <bind queue="test-queue-1" exchange="test-exchange-1" topic="cart.created"/> - <bind queue="test-queue-2" exchange="magento" topic="customer.created"/> - <bind queue="test-queue-2" exchange="magento" topic="customer.deleted"/> - <bind queue="test-queue-3" exchange="magento" topic="cart.created"/> - <bind queue="test-queue-3" exchange="magento" topic="cart.created.one"/> - <bind queue="test-queue-3" exchange="test-exchange-1" topic="cart.created"/> - <bind queue="test-queue-4" exchange="magento" topic="customer.*"/> - <bind queue="test-queue-5" exchange="magento" topic="customer.#"/> - <bind queue="test-queue-7" exchange="magento" topic="*.created.*"/> - <bind queue="test-queue-8" exchange="magento" topic="*.created.#"/> - <bind queue="test-queue-9" exchange="magento" topic="#"/> -</config> diff --git a/dev/tests/integration/testsuite/Magento/NewRelicReporting/Model/Module/CollectTest.php b/dev/tests/integration/testsuite/Magento/NewRelicReporting/Model/Module/CollectTest.php new file mode 100644 index 0000000000000..5e5051163cc1f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/NewRelicReporting/Model/Module/CollectTest.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\NewRelicReporting\Model\Module; + +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/*** + * Class CollectTest + */ +class CollectTest extends TestCase +{ + /** + * @var Collect + */ + private $collect; + + /** + * @inheritDoc + */ + protected function setUp() + { + $this->collect = Bootstrap::getObjectManager()->create(Collect::class); + } + + /** + * @return void + */ + public function testReport() + { + $this->collect->getModuleData(); + $moduleData = $this->collect->getModuleData(); + $this->assertEmpty($moduleData['changes']); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Controller/Transparent/ResponseTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Controller/Transparent/ResponseTest.php new file mode 100644 index 0000000000000..17464b6d65861 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/Controller/Transparent/ResponseTest.php @@ -0,0 +1,134 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Controller\Transparent; + +use Magento\Checkout\Model\Session; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Intl\DateTimeFactory; +use Magento\Framework\Session\Generic as GenericSession; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Api\Data\CartInterface; +use Magento\Quote\Api\PaymentMethodManagementInterface; + +/** + * Tests PayPal transparent response controller. + */ +class ResponseTest extends \Magento\TestFramework\TestCase\AbstractController +{ + /** + * Tests setting credit card expiration month and year to payment from PayPal response. + * + * @param string $currentDateTime + * @param string $paypalExpDate + * @param string $expectedCcMonth + * @param string $expectedCcYear + * @throws NoSuchEntityException + * + * @magentoConfigFixture current_store payment/payflowpro/active 1 + * @magentoDataFixture Magento/Sales/_files/quote.php + * @dataProvider paymentCcExpirationDateDataProvider + */ + public function testPaymentCcExpirationDate( + string $currentDateTime, + string $paypalExpDate, + string $expectedCcMonth, + string $expectedCcYear + ) { + $reservedOrderId = 'test01'; + $postData = [ + 'EXPDATE' => $paypalExpDate, + 'AMT' => '0.00', + 'RESPMSG' => 'Verified', + 'CVV2MATCH' => 'Y', + 'PNREF' => 'A10AAD866C87', + 'SECURETOKEN' => '3HYEHfG06skydAdBXbpIl8QJZ', + 'AVSDATA' => 'YNY', + 'RESULT' => '0', + 'IAVS' => 'N', + 'AVSADDR' => 'Y', + 'SECURETOKENID' => 'yqanLisRZbI0HAG8q3SbbKbhiwjNZAGf', + ]; + + $quote = $this->getQuote($reservedOrderId); + $this->getRequest()->setPostValue($postData); + + /** @var Session $checkoutSession */ + $checkoutSession = $this->_objectManager->get(GenericSession::class); + $checkoutSession->setQuoteId($quote->getId()); + $this->setCurrentDateTime($currentDateTime); + + $this->dispatch('paypal/transparent/response'); + + /** @var PaymentMethodManagementInterface $paymentManagment */ + $paymentManagment = $this->_objectManager->get(PaymentMethodManagementInterface::class); + $payment = $paymentManagment->get($quote->getId()); + + $this->assertEquals($expectedCcMonth, $payment->getCcExpMonth()); + $this->assertEquals($expectedCcYear, $payment->getCcExpYear()); + } + + /** + * @return array + */ + public function paymentCcExpirationDateDataProvider(): array + { + return [ + 'Expiration year in current century' => [ + 'currentDateTime' => '2019-07-05 00:00:00', + 'paypalExpDate' => '0321', + 'expectedCcMonth' => 3, + 'expectedCcYear' => 2021 + ], + 'Expiration year in next century' => [ + 'currentDateTime' => '2099-01-01 00:00:00', + 'paypalExpDate' => '1002', + 'expectedCcMonth' => 10, + 'expectedCcYear' => 2102 + ] + ]; + } + + /** + * Sets current date and time. + * + * @param string $date + */ + private function setCurrentDateTime(string $dateTime): void + { + $dateTime = new \DateTime($dateTime, new \DateTimeZone('UTC')); + $dateTimeFactory = $this->getMockBuilder(DateTimeFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $dateTimeFactory->method('create') + ->willReturn($dateTime); + + $this->_objectManager->addSharedInstance($dateTimeFactory, DateTimeFactory::class); + } + + /** + * Gets quote by reserved order ID. + * + * @param string $reservedOrderId + * @return CartInterface + */ + private function getQuote(string $reservedOrderId): CartInterface + { + $searchCriteria = $this->_objectManager->get(SearchCriteriaBuilder::class) + ->addFilter('reserved_order_id', $reservedOrderId) + ->create(); + + /** @var CartRepositoryInterface $quoteRepository */ + $quoteRepository = $this->_objectManager->get(CartRepositoryInterface::class); + $items = $quoteRepository->getList($searchCriteria) + ->getItems(); + + return array_pop($items); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/SaveTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/SaveTest.php index fa5da2e0e50d1..f589a0f5a1c74 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/SaveTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/SaveTest.php @@ -7,10 +7,13 @@ namespace Magento\Sales\Controller\Adminhtml\Order\Creditmemo; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Sales\Api\Data\OrderItemInterface; +use Magento\Sales\Model\Order; use PHPUnit\Framework\Constraint\StringContains; /** - * Class tests creditmemo creation in backend. + * Provide tests for CreditMemo save controller. * * @magentoDbIsolation enabled * @magentoAppArea adminhtml @@ -24,6 +27,8 @@ class SaveTest extends AbstractCreditmemoControllerTest protected $uri = 'backend/sales/order_creditmemo/save'; /** + * @magentoDbIsolation enabled + * @magentoDataFixture Magento/Sales/_files/invoice.php * @return void */ public function testSendEmailOnCreditmemoSave(): void @@ -54,6 +59,91 @@ public function testSendEmailOnCreditmemoSave(): void $this->assertThat($message->getRawMessage(), $messageConstraint); } + /** + * Test order will keep same(custom) status after partial refund, if state has not been changed. + * + * @magentoDataFixture Magento/Sales/_files/order_with_invoice_and_custom_status.php + */ + public function testOrderStatusPartialRefund() + { + /** @var Order $existingOrder */ + $existingOrder = $this->_objectManager->create(Order::class)->loadByIncrementId('100000001'); + $items = $this->getOrderItems($existingOrder, 1); + $requestParams = [ + 'creditmemo' => [ + 'items' => $items, + 'do_offline' => '1', + 'comment_text' => '', + 'shipping_amount' => '0', + 'adjustment_positive' => '0', + 'adjustment_negative' => '0', + ], + 'order_id' => $existingOrder->getId(), + ]; + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->getRequest()->setParams($requestParams); + $this->dispatch('backend/sales/order_creditmemo/save'); + + /** @var Order $updatedOrder */ + $updatedOrder = $this->_objectManager->create(Order::class) + ->loadByIncrementId($existingOrder->getIncrementId()); + + $this->assertSame('custom_processing', $updatedOrder->getStatus()); + $this->assertSame('processing', $updatedOrder->getState()); + } + + /** + * Test order will change custom status after total refund, when state has been changed. + * + * @magentoDataFixture Magento/Sales/_files/order_with_invoice_and_custom_status.php + */ + public function testOrderStatusTotalRefund() + { + /** @var Order $existingOrder */ + $existingOrder = $this->_objectManager->create(Order::class)->loadByIncrementId('100000001'); + $requestParams = [ + 'creditmemo' => [ + 'items' => $this->getOrderItems($existingOrder), + 'do_offline' => '1', + 'comment_text' => '', + 'shipping_amount' => '0', + 'adjustment_positive' => '0', + 'adjustment_negative' => '0', + ], + 'order_id' => $existingOrder->getId(), + ]; + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->getRequest()->setParams($requestParams); + $this->dispatch('backend/sales/order_creditmemo/save'); + + /** @var Order $updatedOrder */ + $updatedOrder = $this->_objectManager->create(Order::class) + ->loadByIncrementId($existingOrder->getIncrementId()); + + $this->assertSame('complete', $updatedOrder->getStatus()); + $this->assertSame('complete', $updatedOrder->getState()); + } + + /** + * Gets all items of given Order in proper format. + * + * @param Order $order + * @param int $subQty + * @return array + */ + private function getOrderItems(Order $order, int $subQty = 0) + { + $items = []; + /** @var OrderItemInterface $item */ + foreach ($order->getAllItems() as $item) { + $items[$item->getItemId()] = [ + 'qty' => $item->getQtyOrdered() - $subQty, + ]; + } + + return $items; + } + /** * @inheritdoc */ diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_list.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_list.php index 1f4253f18487c..99122d72df4b7 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/order_list.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_list.php @@ -9,6 +9,7 @@ use Magento\Sales\Model\Order\Address as OrderAddress; use Magento\Sales\Model\Order\Payment; +// phpcs:ignore Magento2.Security.IncludeFile require 'order.php'; /** @var Order $order */ /** @var Order\Payment $payment */ @@ -24,8 +25,7 @@ 'subtotal' => 120.00, 'base_grand_total' => 120.00, 'store_id' => 1, - 'website_id' => 1, - 'payment' => $payment + 'website_id' => 1 ], [ 'increment_id' => '100000003', @@ -35,8 +35,7 @@ 'base_grand_total' => 140.00, 'subtotal' => 140.00, 'store_id' => 0, - 'website_id' => 0, - 'payment' => $payment + 'website_id' => 0 ], [ 'increment_id' => '100000004', @@ -46,8 +45,7 @@ 'base_grand_total' => 140.00, 'subtotal' => 140.00, 'store_id' => 1, - 'website_id' => 1, - 'payment' => $payment + 'website_id' => 1 ], ]; diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_invoice_and_custom_status.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_invoice_and_custom_status.php new file mode 100644 index 0000000000000..46d3fb547cd09 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_invoice_and_custom_status.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Sales\Model\Order\Status; +use Magento\TestFramework\Helper\Bootstrap; + +// phpcs:ignore Magento2.Security.IncludeFile +require 'invoice.php'; + +$orderStatus = Bootstrap::getObjectManager()->create(Status::class); +$data = [ + 'status' => 'custom_processing', + 'label' => 'Custom Processing Status', +]; +$orderStatus->setData($data)->setStatus('custom_processing'); +$orderStatus->save(); +$orderStatus->assignState('processing'); + +$order->setStatus('custom_processing'); +$order->save(); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_invoice_and_custom_status_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_invoice_and_custom_status_rollback.php new file mode 100644 index 0000000000000..274cb3c74395d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_invoice_and_custom_status_rollback.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Sales\Model\Order\Status; +use Magento\TestFramework\Helper\Bootstrap; + +// phpcs:ignore Magento2.Security.IncludeFile +require 'default_rollback.php'; + +/** @var Status $orderStatus */ +$orderStatus = Bootstrap::getObjectManager()->create(Status::class); +$orderStatus->load('custom_processing', 'status'); +$orderStatus->delete(); diff --git a/dev/tests/integration/testsuite/Magento/Store/App/FrontController/Plugin/RequestPreprocessorTest.php b/dev/tests/integration/testsuite/Magento/Store/App/FrontController/Plugin/RequestPreprocessorTest.php index ebf302c16bd69..0e158821f1802 100644 --- a/dev/tests/integration/testsuite/Magento/Store/App/FrontController/Plugin/RequestPreprocessorTest.php +++ b/dev/tests/integration/testsuite/Magento/Store/App/FrontController/Plugin/RequestPreprocessorTest.php @@ -56,7 +56,7 @@ public function testHttpsPassSecureLoginPost() $this->prepareRequest(true); $this->dispatch('customer/account/loginPost/'); $redirectUrl = str_replace('http://', 'https://', $this->baseUrl) . - 'index.php/customer/account/'; + 'customer/account/'; $this->assertResponseRedirect($this->getResponse(), $redirectUrl); $this->assertTrue($this->_objectManager->get(Session::class)->isLoggedIn()); $this->setFrontendCompletelySecureRollback(); diff --git a/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php b/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php index bb6d1687052e3..00de5544d8fb7 100644 --- a/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php +++ b/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php @@ -8,7 +8,6 @@ use Magento\Catalog\Model\ProductRepository; use Magento\Framework\App\Bootstrap; -use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\UrlInterface; use Magento\Store\Api\StoreRepositoryInterface; @@ -16,6 +15,7 @@ /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * phpcs:disable Magento2.Security.Superglobal */ class StoreTest extends \PHPUnit\Framework\TestCase { @@ -201,7 +201,7 @@ public function testGetBaseUrlInPub() */ public function testGetBaseUrlForCustomEntryPoint($type, $useCustomEntryPoint, $useStoreCode, $expected) { - /* config operations require store to be loaded */ + /* config operations require store to be loaded */ $this->model->load('default'); \Magento\TestFramework\Helper\Bootstrap::getObjectManager() ->get(\Magento\Framework\App\Config\MutableScopeConfigInterface::class) @@ -213,6 +213,10 @@ public function testGetBaseUrlForCustomEntryPoint($type, $useCustomEntryPoint, $ // emulate custom entry point $_SERVER['SCRIPT_FILENAME'] = 'custom_entry.php'; + $request = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Framework\App\RequestInterface::class); + $request->setServer(new Parameters($_SERVER)); + if ($useCustomEntryPoint) { $property = new \ReflectionProperty($this->model, '_isCustomEntryPoint'); $property->setAccessible(true); @@ -298,11 +302,11 @@ public function testGetCurrentUrl() $url = $product->getUrlInStore(); $this->assertEquals( - $secondStore->getBaseUrl().'catalog/product/view/id/1/s/simple-product/', + $secondStore->getBaseUrl() . 'catalog/product/view/id/1/s/simple-product/', $url ); $this->assertEquals( - $secondStore->getBaseUrl().'?___from_store=default', + $secondStore->getBaseUrl() . '?___from_store=default', $secondStore->getCurrentUrl() ); $this->assertEquals( @@ -332,25 +336,25 @@ public function testGetCurrentUrlWithUseStoreInUrlFalse() $product->setStoreId($secondStore->getId()); $url = $product->getUrlInStore(); - /** @var \Magento\Catalog\Model\CategoryRepository $categoryRepository */ + /** @var \Magento\Catalog\Model\CategoryRepository $categoryRepository */ $categoryRepository = $objectManager->get(\Magento\Catalog\Model\CategoryRepository::class); $category = $categoryRepository->get(333, $secondStore->getStoreId()); $this->assertEquals( - $secondStore->getBaseUrl().'catalog/category/view/s/category-1/id/333/', + $secondStore->getBaseUrl() . 'catalog/category/view/s/category-1/id/333/', $category->getUrl() ); $this->assertEquals( - $secondStore->getBaseUrl(). + $secondStore->getBaseUrl() . 'catalog/product/view/id/333/s/simple-product-three/?___store=fixture_second_store', $url ); $this->assertEquals( - $secondStore->getBaseUrl().'?___store=fixture_second_store&___from_store=default', + $secondStore->getBaseUrl() . '?___store=fixture_second_store&___from_store=default', $secondStore->getCurrentUrl() ); $this->assertEquals( - $secondStore->getBaseUrl().'?___store=fixture_second_store', + $secondStore->getBaseUrl() . '?___store=fixture_second_store', $secondStore->getCurrentUrl(false) ); } @@ -405,7 +409,7 @@ public function testSaveValidation($badStoreData) /** * @return array */ - public static function saveValidationDataProvider() + public function saveValidationDataProvider() { return [ 'empty store name' => [['name' => '']], diff --git a/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/SetupUtil.php b/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/SetupUtil.php index bd505fd4db035..985019b687ce0 100644 --- a/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/SetupUtil.php +++ b/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/SetupUtil.php @@ -7,10 +7,19 @@ namespace Magento\Tax\Model\Sales\Total\Quote; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Quote\Model\Quote; use Magento\Tax\Model\Config; use Magento\Tax\Model\Calculation; +use Magento\Quote\Model\Quote\Item\Updater; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\Api\Filter; +use Magento\Framework\Api\Search\FilterGroup; +use Magento\Framework\Api\SearchCriteriaInterface; /** + * Setup utility for quote + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class SetupUtil @@ -594,7 +603,7 @@ protected function createCartRule($ruleDataOverride) * * @param array $quoteData * @param \Magento\Customer\Api\Data\CustomerInterface $customer - * @return \Magento\Quote\Model\Quote + * @return Quote */ protected function createQuote($quoteData, $customer) { @@ -619,8 +628,8 @@ protected function createQuote($quoteData, $customer) $quoteBillingAddress = $this->objectManager->create(\Magento\Quote\Model\Quote\Address::class); $quoteBillingAddress->importCustomerAddressData($addressService->getById($billingAddress->getId())); - /** @var \Magento\Quote\Model\Quote $quote */ - $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class); + /** @var Quote $quote */ + $quote = $this->objectManager->create(Quote::class); $quote->setStoreId(1) ->setIsActive(true) ->setIsMultiShipping(false) @@ -634,7 +643,7 @@ protected function createQuote($quoteData, $customer) /** * Add products to quote * - * @param \Magento\Quote\Model\Quote $quote + * @param Quote $quote * @param array $itemsData * @return $this */ @@ -657,7 +666,8 @@ protected function addProductToQuote($quote, $itemsData) * Create a quote based on given data * * @param array $quoteData - * @return \Magento\Quote\Model\Quote + * + * @return Quote */ public function setupQuote($quoteData) { @@ -666,7 +676,9 @@ public function setupQuote($quoteData) $quote = $this->createQuote($quoteData, $customer); $this->addProductToQuote($quote, $quoteData['items']); - + if (isset($quoteData['update_items'])) { + $this->updateItems($quote, $quoteData['update_items']); + } //Set shipping amount if (isset($quoteData['shipping_method'])) { $quote->getShippingAddress()->setShippingMethod($quoteData['shipping_method']); @@ -683,4 +695,33 @@ public function setupQuote($quoteData) return $quote; } + + /** + * Update quote items + * + * @param Quote $quote + * @param array $items + * + * @return void + */ + private function updateItems(Quote $quote, array $items): void + { + $updater = $this->objectManager->get(Updater::class); + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $filter = $this->objectManager->create(Filter::class); + $filter->setField('sku')->setValue(array_keys($items)); + $filterGroup = $this->objectManager->create(FilterGroup::class); + $filterGroup->setFilters([$filter]); + $searchCriteria = $this->objectManager->create(SearchCriteriaInterface::class); + $searchCriteria->setFilterGroups([$filterGroup]); + $products = $productRepository->getList($searchCriteria)->getItems(); + /** @var ProductInterface $product */ + foreach ($products as $product) { + $quoteItem = $quote->getItemByProduct($product); + $updater->update( + $quoteItem, + $items[$product->getSku()] + ); + } + } } diff --git a/dev/tests/integration/testsuite/Magento/Tax/_files/scenarios/including_tax_with_custom_price.php b/dev/tests/integration/testsuite/Magento/Tax/_files/scenarios/including_tax_with_custom_price.php new file mode 100644 index 0000000000000..290c133f455f6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Tax/_files/scenarios/including_tax_with_custom_price.php @@ -0,0 +1,93 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Tax\Model\Config; +use Magento\Tax\Model\Sales\Total\Quote\SetupUtil; + +$taxCalculationData['including_tax_with_custom_price'] = [ + 'config_data' => [ + SetupUtil::CONFIG_OVERRIDES => [ + Config::CONFIG_XML_PATH_PRICE_INCLUDES_TAX => 1, + Config::CONFIG_XML_PATH_APPLY_ON => 0, + ], + SetupUtil::TAX_RATE_OVERRIDES => [ + SetupUtil::TAX_RATE_TX => 8.25, + SetupUtil::TAX_STORE_RATE => 8.25, + ], + SetupUtil::TAX_RULE_OVERRIDES => [ + ], + ], + 'quote_data' => [ + 'billing_address' => [ + 'region_id' => SetupUtil::REGION_TX, + ], + 'shipping_address' => [ + 'region_id' => SetupUtil::REGION_TX, + ], + 'items' => [ + [ + 'sku' => 'simple1', + 'price' => 16.24, + 'qty' => 1, + ], + ], + 'update_items' => [ + 'simple1' => [ + 'custom_price' => 14, + 'qty' => 1, + ], + ], + ], + 'expected_results' => [ + 'address_data' => [ + 'subtotal' => 12.93, + 'base_subtotal' => 12.93, + 'subtotal_incl_tax' => 14, + 'base_subtotal_incl_tax' => 14, + 'tax_amount' => 1.07, + 'base_tax_amount' => 1.07, + 'shipping_amount' => 0, + 'base_shipping_amount' => 0, + 'shipping_incl_tax' => 0, + 'base_shipping_incl_tax' => 0, + 'shipping_taxable' => 0, + 'base_shipping_taxable' => 0, + 'shipping_tax_amount' => 0, + 'base_shipping_tax_amount' => 0, + 'discount_amount' => 0, + 'base_discount_amount' => 0, + 'discount_tax_compensation_amount' => 0, + 'base_discount_tax_compensation_amount' => 0, + 'shipping_discount_tax_compensation_amount' => 0, + 'base_shipping_discount_tax_compensation_amount' => 0, + 'grand_total' => 14, + 'base_grand_total' => 14, + ], + 'items_data' => [ + 'simple1' => [ + 'row_total' => 12.93, + 'base_row_total' => 12.93, + 'tax_percent' => 8.25, + 'price' => 12.93, + 'custom_price' => 12.93, + 'original_custom_price' => 14, + 'base_price' => 12.93, + 'price_incl_tax' => 14, + 'base_price_incl_tax' => 14, + 'row_total_incl_tax' => 14, + 'base_row_total_incl_tax' => 14, + 'tax_amount' => 1.07, + 'base_tax_amount' => 1.07, + 'discount_amount' => 0, + 'base_discount_amount' => 0, + 'discount_percent' => 0, + 'discount_tax_compensation_amount' => 0, + 'base_discount_tax_compensation_amount' => 0, + ], + ], + ], +]; diff --git a/dev/tests/integration/testsuite/Magento/Tax/_files/tax_calculation_data_aggregated.php b/dev/tests/integration/testsuite/Magento/Tax/_files/tax_calculation_data_aggregated.php index f22b48a259685..3c56b1bf815a6 100644 --- a/dev/tests/integration/testsuite/Magento/Tax/_files/tax_calculation_data_aggregated.php +++ b/dev/tests/integration/testsuite/Magento/Tax/_files/tax_calculation_data_aggregated.php @@ -3,14 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); /** - * Global array that holds test scenarios data + * Global array that holds test scenarios data. * * @var array */ $taxCalculationData = []; - +//phpcs:disable Magento2.Security.IncludeFile require_once __DIR__ . '/scenarios/excluding_tax_apply_tax_after_discount.php'; require_once __DIR__ . '/scenarios/excluding_tax_apply_tax_after_discount_discount_tax.php'; require_once __DIR__ . '/scenarios/excluding_tax_apply_tax_before_discount.php'; @@ -31,3 +32,4 @@ require_once __DIR__ . '/scenarios/multi_tax_rule_two_row_calculate_subtotal_yes_row.php'; require_once __DIR__ . '/scenarios/multi_tax_rule_two_row_calculate_subtotal_yes_total.php'; require_once __DIR__ . '/scenarios/including_tax_apply_tax_after_discount.php'; +require_once __DIR__ . '/scenarios/including_tax_with_custom_price.php'; diff --git a/dev/tests/integration/testsuite/Magento/Test/Integrity/Modular/LayoutFilesTest.php b/dev/tests/integration/testsuite/Magento/Test/Integrity/Modular/LayoutFilesTest.php index a11cd4b73b5c7..32a58e285d762 100644 --- a/dev/tests/integration/testsuite/Magento/Test/Integrity/Modular/LayoutFilesTest.php +++ b/dev/tests/integration/testsuite/Magento/Test/Integrity/Modular/LayoutFilesTest.php @@ -77,9 +77,8 @@ public function layoutArgumentsDataProvider() */ protected function isSkippedArgument(array $argumentData) { - // Do not take into account argument name and parameters - unset($argumentData['name']); - unset($argumentData['param']); + // Do not take into account argument name, shared and parameters + unset($argumentData['name'], $argumentData['param'], $argumentData['shared']); $isUpdater = isset($argumentData['updater']); unset($argumentData['updater']); diff --git a/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php b/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php index fe4067cdc49f5..042bd03b1cd4c 100644 --- a/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php +++ b/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php @@ -3,11 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Ups\Model; use Magento\TestFramework\Helper\Bootstrap; use Magento\Quote\Model\Quote\Address\RateRequestFactory; +/** + * Integration tests for Carrier model class + */ class CarrierTest extends \PHPUnit\Framework\TestCase { /** @@ -64,12 +69,12 @@ public function testGetShipConfirmUrlLive() /** * @magentoConfigFixture current_store carriers/ups/active 1 + * @magentoConfigFixture current_store carriers/ups/type UPS * @magentoConfigFixture current_store carriers/ups/allowed_methods 1DA,GND * @magentoConfigFixture current_store carriers/ups/free_method GND */ public function testCollectFreeRates() { - $this->markTestSkipped('Test is blocked by MAGETWO-97467.'); $rateRequest = Bootstrap::getObjectManager()->get(RateRequestFactory::class)->create(); $rateRequest->setDestCountryId('US'); $rateRequest->setDestRegionId('CA'); diff --git a/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/User/InvalidateTokenTest.php b/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/User/InvalidateTokenTest.php index 672cbd7a586ec..937a26fdf0a89 100644 --- a/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/User/InvalidateTokenTest.php +++ b/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/User/InvalidateTokenTest.php @@ -89,10 +89,6 @@ public function testInvalidateTokenNoTokens() // invalidate token $this->getRequest()->setParam('user_id', $adminUserId); $this->dispatch('backend/admin/user/invalidateToken'); - $this->assertSessionMessages( - $this->equalTo(['This user has no tokens.']), - MessageInterface::TYPE_ERROR - ); } public function testInvalidateTokenNoUser() @@ -110,9 +106,5 @@ public function testInvalidateTokenInvalidUser() // invalidate token $this->getRequest()->setParam('user_id', $adminUserId); $this->dispatch('backend/admin/user/invalidateToken'); - $this->assertSessionMessages( - $this->equalTo(['This user has no tokens.']), - MessageInterface::TYPE_ERROR - ); } } diff --git a/dev/tests/integration/testsuite/Magento/Webapi/Controller/PathProcessorTest.php b/dev/tests/integration/testsuite/Magento/Webapi/Controller/PathProcessorTest.php index 932ad03d691e4..14fbc2ffc67d5 100644 --- a/dev/tests/integration/testsuite/Magento/Webapi/Controller/PathProcessorTest.php +++ b/dev/tests/integration/testsuite/Magento/Webapi/Controller/PathProcessorTest.php @@ -8,6 +8,9 @@ use Magento\Store\Model\Store; +/** + * Test for Magento\Webapi\Controller\PathProcessor class. + */ class PathProcessorTest extends \PHPUnit\Framework\TestCase { /** @@ -15,6 +18,11 @@ class PathProcessorTest extends \PHPUnit\Framework\TestCase */ protected $storeManager; + /** + * @var \Magento\Framework\Locale\ResolverInterface::class + */ + private $localeResolver; + /** * @var \Magento\Webapi\Controller\PathProcessor */ @@ -25,6 +33,7 @@ protected function setUp() $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); $this->storeManager = $objectManager->get(\Magento\Store\Model\StoreManagerInterface::class); $this->storeManager->reinitStores(); + $this->localeResolver = $objectManager->get(\Magento\Framework\Locale\ResolverInterface::class); $this->pathProcessor = $objectManager->get(\Magento\Webapi\Controller\PathProcessor::class); } @@ -59,4 +68,20 @@ public function testProcessWithoutStoreCode() $this->assertEquals($path, $result); $this->assertEquals('default', $this->storeManager->getStore()->getCode()); } + + /** + * @magentoDataFixture Magento/Store/_files/core_fixturestore.php + * @magentoConfigFixture default_store general/locale/code en_US + * @magentoConfigFixture fixturestore_store general/locale/code de_DE + */ + public function testProcessWithValidStoreCodeApplyLocale() + { + $locale = 'de_DE'; + $storeCode = 'fixturestore'; + $basePath = "rest/{$storeCode}"; + $path = $basePath . '/V1/customerAccounts/createCustomer'; + $this->pathProcessor->process($path); + $this->assertEquals($locale, $this->localeResolver->getLocale()); + $this->assertNotEquals('en_US', $this->localeResolver->getLocale()); + } } diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js index 8fdef2cbaadbb..429342b43bcb2 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js @@ -15,6 +15,18 @@ define([ describe('Magento_Braintree/js/view/payment/method-renderer/cc-form', function () { var injector = new Squire(), mocks = { + 'Magento_Checkout/js/model/checkout-data-resolver': { + + /** Stub */ + applyBillingAddress: function () { + return true; + }, + + /** Stub */ + resolveBillingAddress: function () { + return true; + } + }, 'Magento_Checkout/js/model/quote': { billingAddress: ko.observable(), shippingAddress: ko.observable(), diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js index 4fc73caf7e14b..6ba0ed0b58f03 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js @@ -14,6 +14,18 @@ define([ var injector = new Squire(), mocks = { + 'Magento_Checkout/js/model/checkout-data-resolver': { + + /** Stub */ + applyBillingAddress: function () { + return true; + }, + + /** Stub */ + resolveBillingAddress: function () { + return true; + } + }, 'Magento_Checkout/js/model/quote': { billingAddress: ko.observable(), shippingAddress: ko.observable({ diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js index 29a2e8db914a7..7bc9a2a0113aa 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js @@ -24,6 +24,18 @@ define([ return true; }).and.callThrough(), mocks = { + 'Magento_Checkout/js/model/checkout-data-resolver': { + + /** Stub */ + applyBillingAddress: function () { + return true; + }, + + /** Stub */ + resolveBillingAddress: function () { + return true; + } + }, 'Magento_Checkout/js/model/quote': { billingAddress: ko.observable(), shippingAddress: ko.observable(), diff --git a/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/CookieAndSessionMisuse.php b/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/CookieAndSessionMisuse.php index ee56158a54509..707c3442d4056 100644 --- a/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/CookieAndSessionMisuse.php +++ b/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/CookieAndSessionMisuse.php @@ -54,6 +54,19 @@ private function isUiDataProvider(\ReflectionClass $class): bool ); } + /** + * Is given class a Layout Processor? + * + * @param \ReflectionClass $class + * @return bool + */ + private function isLayoutProcessor(\ReflectionClass $class): bool + { + return $class->isSubclassOf( + \Magento\Checkout\Block\Checkout\LayoutProcessorInterface::class + ); + } + /** * Is given class an HTML UI Document? * @@ -159,6 +172,7 @@ private function doesUseRestrictedClasses(\ReflectionClass $class): bool * @inheritdoc * * @param ClassNode|ASTClass $node + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function apply(AbstractNode $node) { @@ -176,6 +190,7 @@ public function apply(AbstractNode $node) && !$this->isUiDocument($class) && !$this->isControllerPlugin($class) && !$this->isBlockPlugin($class) + && !$this->isLayoutProcessor($class) ) { $this->addViolation($node, [$node->getFullQualifiedName()]); } diff --git a/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/SerializationAware.php b/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/SerializationAware.php new file mode 100644 index 0000000000000..e38fba8558bad --- /dev/null +++ b/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/SerializationAware.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\CodeMessDetector\Rule\Design; + +use PHPMD\AbstractNode; +use PHPMD\AbstractRule; +use PHPMD\Node\ClassNode; +use PHPMD\Node\MethodNode; +use PDepend\Source\AST\ASTMethod; +use PHPMD\Rule\MethodAware; + +/** + * Detect PHP serialization aware methods. + */ +class SerializationAware extends AbstractRule implements MethodAware +{ + /** + * @inheritDoc + * + * @param ASTMethod|MethodNode $method + */ + public function apply(AbstractNode $method) + { + if ($method->getName() === '__wakeup' || $method->getName() === '__sleep') { + $this->addViolation($method, [$method->getName(), $method->getParent()->getFullQualifiedName()]); + } + } +} diff --git a/dev/tests/static/framework/Magento/CodeMessDetector/resources/rulesets/design.xml b/dev/tests/static/framework/Magento/CodeMessDetector/resources/rulesets/design.xml index 53f2fe4a0084e..5f2461812bab7 100644 --- a/dev/tests/static/framework/Magento/CodeMessDetector/resources/rulesets/design.xml +++ b/dev/tests/static/framework/Magento/CodeMessDetector/resources/rulesets/design.xml @@ -60,6 +60,31 @@ class OrderProcessor $currentOrder = $this->session->get('current_order'); ... } +} + ]]> + </example> + </rule> + <rule name="SerializationAware" + class="Magento\CodeMessDetector\Rule\Design\SerializationAware" + message="{1} has {0} method and is PHP serialization aware - PHP serialization must be avoided."> + <description> + <![CDATA[ +Using PHP serialization must be avoided in Magento for security reasons and for prevention of unexpected behaviour. + ]]> + </description> + <priority>2</priority> + <properties /> + <example> + <![CDATA[ +class MyModel extends AbstractModel +{ + + ....... + + public function __sleep() + { + ..... + } } ]]> </example> diff --git a/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php b/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php index 3f477f7ce5033..33df6e8ae54f1 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php +++ b/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php @@ -4,6 +4,7 @@ * See COPYING.txt for license details. */ declare(strict_types=1); + namespace Magento\Sniffs\Annotation; use PHP_CodeSniffer\Files\File; @@ -249,6 +250,60 @@ public function validateTagGroupingFormat(File $phpcsFile, int $commentStartPtr) } } + /** + * Validates tag aligning format + * + * @param File $phpcsFile + * @param int $commentStartPtr + */ + public function validateTagAligningFormat(File $phpcsFile, int $commentStartPtr) : void + { + $tokens = $phpcsFile->getTokens(); + $noAlignmentPositions = []; + $actualPositions = []; + $stackPtr = null; + foreach ($tokens[$commentStartPtr]['comment_tags'] as $tag) { + $content = $tokens[$tag]['content']; + if (preg_match('/^@/', $content) && ($tokens[$tag]['line'] === $tokens[$tag + 2]['line'])) { + $noAlignmentPositions[] = $tokens[$tag + 1]['column'] + 1; + $actualPositions[] = $tokens[$tag + 2]['column']; + $stackPtr = $stackPtr ?? $tag; + } + } + + if (!$this->allTagsAligned($actualPositions) + && !$this->noneTagsAligned($actualPositions, $noAlignmentPositions)) { + $phpcsFile->addFixableError( + 'Tags visual alignment must be consistent', + $stackPtr, + 'MethodArguments' + ); + } + } + + /** + * Check whether all docblock params are aligned. + * + * @param array $actualPositions + * @return bool + */ + private function allTagsAligned(array $actualPositions) + { + return count(array_unique($actualPositions)) === 1; + } + + /** + * Check whether all docblock params are not aligned. + * + * @param array $actualPositions + * @param array $noAlignmentPositions + * @return bool + */ + private function noneTagsAligned(array $actualPositions, array $noAlignmentPositions) + { + return $actualPositions === $noAlignmentPositions; + } + /** * Validates extra newline before short description * diff --git a/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodAnnotationStructureSniff.php b/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodAnnotationStructureSniff.php index 445671d245e03..f05ae64a170d7 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodAnnotationStructureSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodAnnotationStructureSniff.php @@ -75,6 +75,7 @@ public function process(File $phpcsFile, $stackPtr) $emptyTypeTokens ); $this->annotationFormatValidator->validateTagGroupingFormat($phpcsFile, $commentStartPtr); + $this->annotationFormatValidator->validateTagAligningFormat($phpcsFile, $commentStartPtr); } } } diff --git a/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodArgumentsSniff.php b/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodArgumentsSniff.php index 879334d8c553b..50efca9b1ed23 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodArgumentsSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodArgumentsSniff.php @@ -8,8 +8,8 @@ namespace Magento\Sniffs\Annotation; -use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Sniffs\Sniff; /** * Sniff to validate method arguments annotations @@ -42,7 +42,7 @@ class MethodArgumentsSniff implements Sniff /** * @inheritdoc */ - public function register() : array + public function register(): array { return [ T_FUNCTION @@ -55,7 +55,7 @@ public function register() : array * @param string $type * @return bool */ - private function isTokenBeforeClosingCommentTagValid(string $type) : bool + private function isTokenBeforeClosingCommentTagValid(string $type): bool { return in_array($type, $this->validTokensBeforeClosingCommentTag); } @@ -68,7 +68,7 @@ private function isTokenBeforeClosingCommentTagValid(string $type) : bool * @param int $stackPtr * @return bool */ - private function validateCommentBlockExists(File $phpcsFile, int $previousCommentClosePtr, int $stackPtr) : bool + private function validateCommentBlockExists(File $phpcsFile, int $previousCommentClosePtr, int $stackPtr): bool { $tokens = $phpcsFile->getTokens(); for ($tempPtr = $previousCommentClosePtr + 1; $tempPtr < $stackPtr; $tempPtr++) { @@ -85,7 +85,7 @@ private function validateCommentBlockExists(File $phpcsFile, int $previousCommen * @param string $type * @return bool */ - private function isInvalidType(string $type) : bool + private function isInvalidType(string $type): bool { return in_array(strtolower($type), $this->invalidTypes); } @@ -98,7 +98,7 @@ private function isInvalidType(string $type) : bool * @param int $closedParenthesisPtr * @return array */ - private function getMethodArguments(File $phpcsFile, int $openParenthesisPtr, int $closedParenthesisPtr) : array + private function getMethodArguments(File $phpcsFile, int $openParenthesisPtr, int $closedParenthesisPtr): array { $tokens = $phpcsFile->getTokens(); $methodArguments = []; @@ -121,10 +121,11 @@ private function getMethodArguments(File $phpcsFile, int $openParenthesisPtr, in * @param array $paramDefinitions * @return array */ - private function getMethodParameters(array $paramDefinitions) : array + private function getMethodParameters(array $paramDefinitions): array { $paramName = []; - for ($i = 0; $i < count($paramDefinitions); $i++) { + $paramCount = count($paramDefinitions); + for ($i = 0; $i < $paramCount; $i++) { if (isset($paramDefinitions[$i]['paramName'])) { $paramName[] = $paramDefinitions[$i]['paramName']; } @@ -143,7 +144,7 @@ private function validateInheritdocAnnotationWithoutBracesExists( File $phpcsFile, int $previousCommentOpenPtr, int $previousCommentClosePtr - ) : bool { + ): bool { return $this->validateInheritdocAnnotationExists( $phpcsFile, $previousCommentOpenPtr, @@ -163,7 +164,7 @@ private function validateInheritdocAnnotationWithBracesExists( File $phpcsFile, int $previousCommentOpenPtr, int $previousCommentClosePtr - ) : bool { + ): bool { return $this->validateInheritdocAnnotationExists( $phpcsFile, $previousCommentOpenPtr, @@ -186,7 +187,7 @@ private function validateInheritdocAnnotationExists( int $previousCommentOpenPtr, int $previousCommentClosePtr, string $inheritdocAnnotation - ) : bool { + ): bool { $tokens = $phpcsFile->getTokens(); for ($ptr = $previousCommentOpenPtr; $ptr < $previousCommentClosePtr; $ptr++) { if (strtolower($tokens[$ptr]['content']) === $inheritdocAnnotation) { @@ -213,7 +214,7 @@ private function validateParameterAnnotationForArgumentExists( int $previousCommentOpenPtr, int $previousCommentClosePtr, int $stackPtr - ) : void { + ): void { if ($argumentsCount > 0 && $parametersCount === 0) { $inheritdocAnnotationWithoutBracesExists = $this->validateInheritdocAnnotationWithoutBracesExists( $phpcsFile, @@ -256,7 +257,7 @@ private function validateCommentBlockDoesnotHaveExtraParameterAnnotation( int $argumentsCount, int $parametersCount, int $stackPtr - ) : void { + ): void { if ($argumentsCount < $parametersCount && $argumentsCount > 0) { $phpcsFile->addFixableError( 'Extra @param found in method annotation', @@ -287,10 +288,10 @@ private function validateArgumentNameInParameterAnnotationExists( File $phpcsFile, array $methodArguments, array $paramDefinitions - ) : void { + ): void { $parameterNames = $this->getMethodParameters($paramDefinitions); if (!in_array($methodArguments[$ptr], $parameterNames)) { - $error = $methodArguments[$ptr]. ' parameter is missing in method annotation'; + $error = $methodArguments[$ptr] . ' parameter is missing in method annotation'; $phpcsFile->addFixableError($error, $stackPtr, 'MethodArguments'); } } @@ -310,7 +311,7 @@ private function validateParameterPresentInMethodSignature( array $methodArguments, File $phpcsFile, array $paramPointers - ) : void { + ): void { if (!in_array($paramDefinitionsArguments, $methodArguments)) { $phpcsFile->addFixableError( $paramDefinitionsArguments . ' parameter is missing in method arguments signature', @@ -333,7 +334,7 @@ private function validateParameterOrderIsCorrect( array $methodArguments, File $phpcsFile, array $paramPointers - ) : void { + ): void { $parameterNames = $this->getMethodParameters($paramDefinitions); $paramDefinitionsCount = count($paramDefinitions); for ($ptr = 0; $ptr < $paramDefinitionsCount; $ptr++) { @@ -342,7 +343,7 @@ private function validateParameterOrderIsCorrect( ) { if ($methodArguments[$ptr] != $parameterNames[$ptr]) { $phpcsFile->addFixableError( - $methodArguments[$ptr].' parameter is not in order', + $methodArguments[$ptr] . ' parameter is not in order', $paramPointers[$ptr], 'MethodArguments' ); @@ -366,15 +367,16 @@ private function validateDuplicateAnnotationDoesnotExists( array $paramPointers, File $phpcsFile, array $methodArguments - ) : void { + ): void { $argumentsCount = count($methodArguments); $parametersCount = count($paramPointers); if ($argumentsCount <= $parametersCount && $argumentsCount > 0) { $duplicateParameters = []; - for ($i = 0; $i < sizeof($paramDefinitions); $i++) { + $paramCount = count($paramDefinitions); + for ($i = 0; $i < $paramCount; $i++) { if (isset($paramDefinitions[$i]['paramName'])) { $parameterContent = $paramDefinitions[$i]['paramName']; - for ($j = $i + 1; $j < count($paramDefinitions); $j++) { + for ($j = $i + 1; $j < $paramCount; $j++) { if (isset($paramDefinitions[$j]['paramName']) && $parameterContent === $paramDefinitions[$j]['paramName'] ) { @@ -408,7 +410,7 @@ private function validateParameterAnnotationFormatIsCorrect( array $methodArguments, array $paramDefinitions, array $paramPointers - ) : void { + ): void { switch (count($paramDefinitions)) { case 0: $phpcsFile->addFixableError( @@ -429,7 +431,7 @@ private function validateParameterAnnotationFormatIsCorrect( case 2: if ($this->isInvalidType($paramDefinitions[0])) { $phpcsFile->addFixableError( - $paramDefinitions[0].' is not a valid PHP type', + $paramDefinitions[0] . ' is not a valid PHP type', $paramPointers[$ptr], 'MethodArguments' ); @@ -451,7 +453,7 @@ private function validateParameterAnnotationFormatIsCorrect( ); if ($this->isInvalidType($paramDefinitions[0])) { $phpcsFile->addFixableError( - $paramDefinitions[0].' is not a valid PHP type', + $paramDefinitions[0] . ' is not a valid PHP type', $paramPointers[$ptr], 'MethodArguments' ); @@ -480,7 +482,7 @@ private function validateMethodParameterAnnotations( array $methodArguments, int $previousCommentOpenPtr, int $previousCommentClosePtr - ) : void { + ): void { $argumentCount = count($methodArguments); $paramCount = count($paramPointers); $this->validateParameterAnnotationForArgumentExists( @@ -510,8 +512,14 @@ private function validateMethodParameterAnnotations( $phpcsFile, $paramPointers ); - for ($ptr = 0; $ptr < count($methodArguments); $ptr++) { - $tokens = $phpcsFile->getTokens(); + $this->validateFormattingConsistency( + $paramDefinitions, + $methodArguments, + $phpcsFile, + $paramPointers + ); + $tokens = $phpcsFile->getTokens(); + for ($ptr = 0; $ptr < $argumentCount; $ptr++) { if (isset($paramPointers[$ptr])) { $this->validateArgumentNameInParameterAnnotationExists( $stackPtr, @@ -520,7 +528,7 @@ private function validateMethodParameterAnnotations( $methodArguments, $paramDefinitions ); - $paramContent = $tokens[$paramPointers[$ptr]+2]['content']; + $paramContent = $tokens[$paramPointers[$ptr] + 2]['content']; $paramContentExplode = explode(' ', $paramContent); $this->validateParameterAnnotationFormatIsCorrect( $ptr, @@ -540,36 +548,40 @@ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); $numTokens = count($tokens); - $previousCommentOpenPtr = $phpcsFile->findPrevious(T_DOC_COMMENT_OPEN_TAG, $stackPtr-1, 0); - $previousCommentClosePtr = $phpcsFile->findPrevious(T_DOC_COMMENT_CLOSE_TAG, $stackPtr-1, 0); + $previousCommentOpenPtr = $phpcsFile->findPrevious(T_DOC_COMMENT_OPEN_TAG, $stackPtr - 1, 0); + $previousCommentClosePtr = $phpcsFile->findPrevious(T_DOC_COMMENT_CLOSE_TAG, $stackPtr - 1, 0); if (!$this->validateCommentBlockExists($phpcsFile, $previousCommentClosePtr, $stackPtr)) { $phpcsFile->addError('Comment block is missing', $stackPtr, 'MethodArguments'); return; } - $openParenthesisPtr = $phpcsFile->findNext(T_OPEN_PARENTHESIS, $stackPtr+1, $numTokens); - $closedParenthesisPtr = $phpcsFile->findNext(T_CLOSE_PARENTHESIS, $stackPtr+1, $numTokens); + $openParenthesisPtr = $phpcsFile->findNext(T_OPEN_PARENTHESIS, $stackPtr + 1, $numTokens); + $closedParenthesisPtr = $phpcsFile->findNext(T_CLOSE_PARENTHESIS, $stackPtr + 1, $numTokens); $methodArguments = $this->getMethodArguments($phpcsFile, $openParenthesisPtr, $closedParenthesisPtr); $paramPointers = $paramDefinitions = []; for ($tempPtr = $previousCommentOpenPtr; $tempPtr < $previousCommentClosePtr; $tempPtr++) { if (strtolower($tokens[$tempPtr]['content']) === '@param') { $paramPointers[] = $tempPtr; - $paramAnnotationParts = explode(' ', $tokens[$tempPtr+2]['content']); + $content = preg_replace('/\s+/', ' ', $tokens[$tempPtr + 2]['content'], 2); + $paramAnnotationParts = explode(' ', $content, 3); if (count($paramAnnotationParts) === 1) { if ((preg_match('/^\$.*/', $paramAnnotationParts[0]))) { $paramDefinitions[] = [ 'type' => null, - 'paramName' => rtrim(ltrim($tokens[$tempPtr+2]['content'], '&'), ',') + 'paramName' => rtrim(ltrim($tokens[$tempPtr + 2]['content'], '&'), ','), + 'comment' => null ]; } else { $paramDefinitions[] = [ - 'type' => $tokens[$tempPtr+2]['content'], - 'paramName' => null + 'type' => $tokens[$tempPtr + 2]['content'], + 'paramName' => null, + 'comment' => null ]; } } else { $paramDefinitions[] = [ 'type' => $paramAnnotationParts[0], - 'paramName' => rtrim(ltrim($paramAnnotationParts[1], '&'), ',') + 'paramName' => rtrim(ltrim($paramAnnotationParts[1], '&'), ','), + 'comment' => $paramAnnotationParts[2] ?? null, ]; } } @@ -584,4 +596,81 @@ public function process(File $phpcsFile, $stackPtr) $previousCommentClosePtr ); } + + /** + * Validates function params format consistency. + * + * @param array $paramDefinitions + * @param array $methodArguments + * @param File $phpcsFile + * @param array $paramPointers + * + * @see https://devdocs.magento.com/guides/v2.3/coding-standards/docblock-standard-general.html#format-consistency + */ + private function validateFormattingConsistency( + array $paramDefinitions, + array $methodArguments, + File $phpcsFile, + array $paramPointers + ): void { + $argumentPositions = []; + $commentPositions = []; + $tokens = $phpcsFile->getTokens(); + $argumentCount = count($methodArguments); + for ($ptr = 0; $ptr < $argumentCount; $ptr++) { + if (isset($paramPointers[$ptr])) { + $paramContent = $tokens[$paramPointers[$ptr] + 2]['content']; + $paramDefinition = $paramDefinitions[$ptr]; + $argumentPositions[] = strpos($paramContent, $paramDefinition['paramName']); + $commentPositions[] = $paramDefinition['comment'] + ? strpos($paramContent, $paramDefinition['comment']) : null; + } + } + if (!$this->allParamsAligned($argumentPositions, $commentPositions) + && !$this->noneParamsAligned($argumentPositions, $commentPositions, $paramDefinitions)) { + $phpcsFile->addFixableError( + 'Visual alignment must be consistent', + $paramPointers[0], + 'MethodArguments' + ); + } + } + + /** + * Check all params are aligned. + * + * @param array $argumentPositions + * @param array $commentPositions + * @return bool + */ + private function allParamsAligned(array $argumentPositions, array $commentPositions): bool + { + return count(array_unique($argumentPositions)) === 1 + && count(array_unique(array_filter($commentPositions))) <= 1; + } + + /** + * Check none of params are aligned. + * + * @param array $argumentPositions + * @param array $commentPositions + * @param array $paramDefinitions + * @return bool + */ + private function noneParamsAligned(array $argumentPositions, array $commentPositions, array $paramDefinitions): bool + { + $flag = true; + foreach ($argumentPositions as $index => $argumentPosition) { + $commentPosition = $commentPositions[$index]; + $type = $paramDefinitions[$index]['type']; + $paramName = $paramDefinitions[$index]['paramName']; + if (($argumentPosition !== strlen($type) + 1) || + (isset($commentPosition) && ($commentPosition !== $argumentPosition + strlen($paramName) + 1))) { + $flag = false; + break; + } + } + + return $flag; + } } diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/DeclarativeDependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/DeclarativeDependencyTest.php index 87cc5afd5ecb3..e090338f90c65 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/DeclarativeDependencyTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/DeclarativeDependencyTest.php @@ -22,11 +22,6 @@ class DeclarativeDependencyTest extends \PHPUnit\Framework\TestCase */ private $dependencyProvider; - /** - * @var array - */ - private $blacklistedDependencies = []; - /** * Sets up data * @@ -50,14 +45,6 @@ protected function setUp() */ public function testUndeclaredDependencies() { - /** TODO: Remove this temporary solution after MC-15534 is closed */ - $filePattern = __DIR__ . '/_files/dependency_test/blacklisted_dependencies_*.php'; - $blacklistedDependencies = []; - foreach (glob($filePattern) as $fileName) { - $blacklistedDependencies = array_merge($blacklistedDependencies, require $fileName); - } - $this->blacklistedDependencies = $blacklistedDependencies; - $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this); $invoker( /** @@ -84,9 +71,7 @@ function ($file) { $result = []; foreach ($undeclaredDependency as $name => $modules) { $modules = array_unique($modules); - if ($this->filterBlacklistedDependencies($foundModuleName, $modules)) { - $result[] = $this->getErrorMessage($name) . "\n" . implode("\t\n", $modules) . "\n"; - } + $result[] = $this->getErrorMessage($name) . "\n" . implode("\t\n", $modules) . "\n"; } if (!empty($result)) { $this->fail( @@ -98,24 +83,6 @@ function ($file) { ); } - /** - * Filter blacklisted dependencies. - * - * @todo Remove this temporary solution after MC-15534 is closed - * - * @param string $moduleName - * @param array $dependencies - * @return array - */ - private function filterBlacklistedDependencies(string $moduleName, array $dependencies): array - { - if (!empty($this->blacklistedDependencies[$moduleName])) { - $dependencies = array_diff($dependencies, $this->blacklistedDependencies[$moduleName]); - } - - return $dependencies; - } - /** * Convert file list to data provider structure. * diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/blacklisted_dependencies_ce.php b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/blacklisted_dependencies_ce.php deleted file mode 100644 index 270cb99c29caa..0000000000000 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/blacklisted_dependencies_ce.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); -return [ - "Magento\InventorySales" => ["Magento\Inventory"], -]; diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt index 35ba5803b09cc..055a3faf70be7 100644 --- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt @@ -214,3 +214,4 @@ Magento/Elasticsearch/Model/Layer/Search Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver Magento/Elasticsearch6/Model/Client Magento/Config/App/Config/Type +Magento/InventoryReservationCli/Test/Integration diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml index 0e3b5fa3d341c..e65a9a089da9e 100644 --- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml +++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml @@ -45,5 +45,6 @@ <!-- Magento Specific Rules --> <rule ref="Magento/CodeMessDetector/resources/rulesets/design.xml/AllPurposeAction" /> <rule ref="Magento/CodeMessDetector/resources/rulesets/design.xml/CookieAndSessionMisuse" /> + <rule ref="Magento/CodeMessDetector/resources/rulesets/design.xml/SerializationAware" /> </ruleset> diff --git a/dev/tools/grunt/configs/watch.js b/dev/tools/grunt/configs/watch.js index 356d0b4b7b2d3..c85ecb00f7809 100644 --- a/dev/tools/grunt/configs/watch.js +++ b/dev/tools/grunt/configs/watch.js @@ -11,11 +11,8 @@ var combo = require('./combo'), var themeOptions = {}; -_.each(themes, function(theme, name) { +_.each(themes, function (theme, name) { themeOptions[name] = { - 'options': { - livereload: true - }, 'files': [ '<%= combo.autopath(\''+name+'\', path.pub) %>/**/*.less' ], diff --git a/lib/internal/Magento/Framework/App/Action/HttpHeadActionInterface.php b/lib/internal/Magento/Framework/App/Action/HttpHeadActionInterface.php index d2f9b70913c1f..389bd8089967b 100644 --- a/lib/internal/Magento/Framework/App/Action/HttpHeadActionInterface.php +++ b/lib/internal/Magento/Framework/App/Action/HttpHeadActionInterface.php @@ -12,6 +12,8 @@ /** * Marker for actions processing HEAD requests. + * + * @deprecated Both GET and HEAD requests map to HttpGetActionInterface */ interface HttpHeadActionInterface extends ActionInterface { diff --git a/lib/internal/Magento/Framework/App/AreaList/Proxy.php b/lib/internal/Magento/Framework/App/AreaList/Proxy.php index d3b26ee9a4190..09115add57190 100644 --- a/lib/internal/Magento/Framework/App/AreaList/Proxy.php +++ b/lib/internal/Magento/Framework/App/AreaList/Proxy.php @@ -1,12 +1,14 @@ <?php /** - * Application area list - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Framework\App\AreaList; +/** + * Proxy for area list. + */ class Proxy extends \Magento\Framework\App\AreaList implements \Magento\Framework\ObjectManager\NoninterceptableInterface { @@ -56,10 +58,17 @@ public function __construct( } /** + * Remove links to other objects. + * * @return array + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + return ['_subject', '_isShared']; } @@ -67,9 +76,14 @@ public function __sleep() * Retrieve ObjectManager from global scope * * @return void + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $this->_objectManager = \Magento\Framework\App\ObjectManager::getInstance(); } diff --git a/lib/internal/Magento/Framework/App/Config/ScopeCodeResolver.php b/lib/internal/Magento/Framework/App/Config/ScopeCodeResolver.php index 321997afdba95..681af35944695 100644 --- a/lib/internal/Magento/Framework/App/Config/ScopeCodeResolver.php +++ b/lib/internal/Magento/Framework/App/Config/ScopeCodeResolver.php @@ -5,6 +5,7 @@ */ namespace Magento\Framework\App\Config; +use Magento\Framework\App\ScopeInterface; use Magento\Framework\App\ScopeResolverPool; /** @@ -34,7 +35,7 @@ public function __construct(ScopeResolverPool $scopeResolverPool) * Resolve scope code * * @param string $scopeType - * @param string $scopeCode + * @param string|null $scopeCode * @return string */ public function resolve($scopeType, $scopeCode) @@ -42,20 +43,24 @@ public function resolve($scopeType, $scopeCode) if (isset($this->resolvedScopeCodes[$scopeType][$scopeCode])) { return $this->resolvedScopeCodes[$scopeType][$scopeCode]; } - if (($scopeCode === null || is_numeric($scopeCode)) - && $scopeType !== ScopeConfigInterface::SCOPE_TYPE_DEFAULT - ) { + + if ($scopeType !== ScopeConfigInterface::SCOPE_TYPE_DEFAULT) { $scopeResolver = $this->scopeResolverPool->get($scopeType); $resolverScopeCode = $scopeResolver->getScope($scopeCode); } else { $resolverScopeCode = $scopeCode; } - if ($resolverScopeCode instanceof \Magento\Framework\App\ScopeInterface) { + if ($resolverScopeCode instanceof ScopeInterface) { $resolverScopeCode = $resolverScopeCode->getCode(); } + if ($scopeCode === null) { + $scopeCode = $resolverScopeCode; + } + $this->resolvedScopeCodes[$scopeType][$scopeCode] = $resolverScopeCode; + return $resolverScopeCode; } diff --git a/lib/internal/Magento/Framework/App/DocRootLocator.php b/lib/internal/Magento/Framework/App/DocRootLocator.php index 6fb35c42f1330..d73baf8e4e742 100644 --- a/lib/internal/Magento/Framework/App/DocRootLocator.php +++ b/lib/internal/Magento/Framework/App/DocRootLocator.php @@ -3,10 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Framework\App; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\ReadFactory; /** @@ -20,18 +22,26 @@ class DocRootLocator private $request; /** + * @deprecated * @var ReadFactory */ private $readFactory; + /** + * @var Filesystem + */ + private $filesystem; + /** * @param RequestInterface $request * @param ReadFactory $readFactory + * @param Filesystem|null $filesystem */ - public function __construct(RequestInterface $request, ReadFactory $readFactory) + public function __construct(RequestInterface $request, ReadFactory $readFactory, Filesystem $filesystem = null) { $this->request = $request; $this->readFactory = $readFactory; + $this->filesystem = $filesystem ?: ObjectManager::getInstance()->get(Filesystem::class); } /** @@ -42,7 +52,8 @@ public function __construct(RequestInterface $request, ReadFactory $readFactory) public function isPub() { $rootBasePath = $this->request->getServer('DOCUMENT_ROOT'); - $readDirectory = $this->readFactory->create(DirectoryList::ROOT); - return (substr($rootBasePath, -strlen('/pub')) === '/pub') && !$readDirectory->isExist($rootBasePath . 'setup'); + $readDirectory = $this->filesystem->getDirectoryRead(DirectoryList::ROOT); + + return (substr($rootBasePath, -\strlen('/pub')) === '/pub') && ! $readDirectory->isExist('setup'); } } diff --git a/lib/internal/Magento/Framework/App/Http.php b/lib/internal/Magento/Framework/App/Http.php index 23024a44c2def..ca3976da1df52 100644 --- a/lib/internal/Magento/Framework/App/Http.php +++ b/lib/internal/Magento/Framework/App/Http.php @@ -3,17 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Framework\App; use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\Framework\Debug; -use Magento\Framework\ObjectManager\ConfigLoaderInterface; use Magento\Framework\App\Request\Http as RequestHttp; use Magento\Framework\App\Response\Http as ResponseHttp; use Magento\Framework\App\Response\HttpInterface; use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\Debug; use Magento\Framework\Event; use Magento\Framework\Filesystem; +use Magento\Framework\ObjectManager\ConfigLoaderInterface; /** * HTTP web application. Called from webroot index.php to serve web requests. @@ -143,12 +144,31 @@ public function launch() } else { throw new \InvalidArgumentException('Invalid return type'); } + if ($this->_request->isHead() && $this->_response->getHttpResponseCode() == 200) { + $this->handleHeadRequest(); + } // This event gives possibility to launch something before sending output (allow cookie setting) $eventParams = ['request' => $this->_request, 'response' => $this->_response]; $this->_eventManager->dispatch('controller_front_send_response_before', $eventParams); return $this->_response; } + /** + * Handle HEAD requests by adding the Content-Length header and removing the body from the response. + * + * @return void + */ + private function handleHeadRequest() + { + // It is possible that some PHP installations have overloaded strlen to use mb_strlen instead. + // This means strlen might return the actual number of characters in a non-ascii string instead + // of the number of bytes. Use mb_strlen explicitly with a single byte character encoding to ensure + // that the content length is calculated in bytes. + $contentLength = mb_strlen($this->_response->getContent(), '8bit'); + $this->_response->clearBody(); + $this->_response->setHeader('Content-Length', $contentLength); + } + /** * @inheritdoc */ @@ -248,7 +268,7 @@ private function redirectToSetup(Bootstrap $bootstrap, \Exception $exception) . "because the Magento setup directory cannot be accessed. \n" . 'You can install Magento using either the command line or you must restore access ' . 'to the following directory: ' . $setupInfo->getDir($projectRoot) . "\n"; - + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception($newMessage, 0, $exception); } } @@ -257,13 +277,14 @@ private function redirectToSetup(Bootstrap $bootstrap, \Exception $exception) * Handler for bootstrap errors * * @param Bootstrap $bootstrap - * @param \Exception &$exception + * @param \Exception $exception * @return bool */ private function handleBootstrapErrors(Bootstrap $bootstrap, \Exception &$exception) { $bootstrapCode = $bootstrap->getErrorCode(); if (Bootstrap::ERR_MAINTENANCE == $bootstrapCode) { + // phpcs:ignore Magento2.Security.IncludeFile require $this->_filesystem->getDirectoryRead(DirectoryList::PUB)->getAbsolutePath('errors/503.php'); return true; } @@ -304,6 +325,7 @@ private function handleInitException(\Exception $exception) { if ($exception instanceof \Magento\Framework\Exception\State\InitException) { $this->getLogger()->critical($exception); + // phpcs:ignore Magento2.Security.IncludeFile require $this->_filesystem->getDirectoryRead(DirectoryList::PUB)->getAbsolutePath('errors/404.php'); return true; } @@ -335,6 +357,7 @@ private function handleGenericReport(Bootstrap $bootstrap, \Exception $exception if (isset($params['SCRIPT_NAME'])) { $reportData['script_name'] = $params['SCRIPT_NAME']; } + // phpcs:ignore Magento2.Security.IncludeFile require $this->_filesystem->getDirectoryRead(DirectoryList::PUB)->getAbsolutePath('errors/report.php'); return true; } diff --git a/lib/internal/Magento/Framework/App/PageCache/Kernel.php b/lib/internal/Magento/Framework/App/PageCache/Kernel.php index 13e18ed28fd67..b507e2e5ca43a 100644 --- a/lib/internal/Magento/Framework/App/PageCache/Kernel.php +++ b/lib/internal/Magento/Framework/App/PageCache/Kernel.php @@ -5,6 +5,9 @@ */ namespace Magento\Framework\App\PageCache; +use Magento\Framework\App\State as AppState; +use Magento\Framework\App\ObjectManager; + /** * Builtin cache processor */ @@ -52,6 +55,11 @@ class Kernel */ private $httpFactory; + /** + * @var AppState + */ + private $state; + /** * @param Cache $cache * @param Identifier $identifier @@ -60,6 +68,8 @@ class Kernel * @param \Magento\Framework\App\Http\ContextFactory|null $contextFactory * @param \Magento\Framework\App\Response\HttpFactory|null $httpFactory * @param \Magento\Framework\Serialize\SerializerInterface|null $serializer + * @param AppState|null $state + * @param \Magento\PageCache\Model\Cache\Type|null $fullPageCache */ public function __construct( \Magento\Framework\App\PageCache\Cache $cache, @@ -68,40 +78,27 @@ public function __construct( \Magento\Framework\App\Http\Context $context = null, \Magento\Framework\App\Http\ContextFactory $contextFactory = null, \Magento\Framework\App\Response\HttpFactory $httpFactory = null, - \Magento\Framework\Serialize\SerializerInterface $serializer = null + \Magento\Framework\Serialize\SerializerInterface $serializer = null, + AppState $state = null, + \Magento\PageCache\Model\Cache\Type $fullPageCache = null ) { $this->cache = $cache; $this->identifier = $identifier; $this->request = $request; - - if ($context) { - $this->context = $context; - } else { - $this->context = \Magento\Framework\App\ObjectManager::getInstance()->get( - \Magento\Framework\App\Http\Context::class - ); - } - if ($contextFactory) { - $this->contextFactory = $contextFactory; - } else { - $this->contextFactory = \Magento\Framework\App\ObjectManager::getInstance()->get( - \Magento\Framework\App\Http\ContextFactory::class - ); - } - if ($httpFactory) { - $this->httpFactory = $httpFactory; - } else { - $this->httpFactory = \Magento\Framework\App\ObjectManager::getInstance()->get( - \Magento\Framework\App\Response\HttpFactory::class - ); - } - if ($serializer) { - $this->serializer = $serializer; - } else { - $this->serializer = \Magento\Framework\App\ObjectManager::getInstance()->get( - \Magento\Framework\Serialize\SerializerInterface::class - ); - } + $this->context = $context ?? ObjectManager::getInstance()->get(\Magento\Framework\App\Http\Context::class); + $this->contextFactory = $contextFactory ?? ObjectManager::getInstance()->get( + \Magento\Framework\App\Http\ContextFactory::class + ); + $this->httpFactory = $httpFactory ?? ObjectManager::getInstance()->get( + \Magento\Framework\App\Response\HttpFactory::class + ); + $this->serializer = $serializer ?? ObjectManager::getInstance()->get( + \Magento\Framework\Serialize\SerializerInterface::class + ); + $this->state = $state ?? ObjectManager::getInstance()->get(AppState::class); + $this->fullPageCache = $fullPageCache ?? ObjectManager::getInstance()->get( + \Magento\PageCache\Model\Cache\Type::class + ); } /** @@ -112,7 +109,7 @@ public function __construct( public function load() { if ($this->request->isGet() || $this->request->isHead()) { - $responseData = $this->getCache()->load($this->identifier->getValue()); + $responseData = $this->fullPageCache->load($this->identifier->getValue()); if (!$responseData) { return false; } @@ -144,12 +141,14 @@ public function process(\Magento\Framework\App\Response\Http $response) $tags = $tagsHeader ? explode(',', $tagsHeader->getFieldValue()) : []; $response->clearHeader('Set-Cookie'); - $response->clearHeader('X-Magento-Tags'); + if ($this->state->getMode() != AppState::MODE_DEVELOPER) { + $response->clearHeader('X-Magento-Tags'); + } if (!headers_sent()) { header_remove('Set-Cookie'); } - $this->getCache()->save( + $this->fullPageCache->save( $this->serializer->serialize($this->getPreparedData($response)), $this->identifier->getValue(), $tags, @@ -203,19 +202,4 @@ private function buildResponse($responseData) return $response; } - - /** - * TODO: Workaround to support backwards compatibility, will rework to use Dependency Injection in MAGETWO-49547 - * - * @return \Magento\PageCache\Model\Cache\Type - */ - private function getCache() - { - if (!$this->fullPageCache) { - $this->fullPageCache = \Magento\Framework\App\ObjectManager::getInstance()->get( - \Magento\PageCache\Model\Cache\Type::class - ); - } - return $this->fullPageCache; - } } diff --git a/lib/internal/Magento/Framework/App/Response/Http.php b/lib/internal/Magento/Framework/App/Response/Http.php index 62ff94e7043f5..a80d9cbdd6689 100644 --- a/lib/internal/Magento/Framework/App/Response/Http.php +++ b/lib/internal/Magento/Framework/App/Response/Http.php @@ -1,10 +1,9 @@ <?php /** - * HTTP response - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Framework\App\Response; use Magento\Framework\App\Http\Context; @@ -16,6 +15,11 @@ use Magento\Framework\App\Request\Http as HttpRequest; use Magento\Framework\Session\Config\ConfigInterface; +/** + * HTTP Response. + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ class Http extends \Magento\Framework\HTTP\PhpEnvironment\Response { /** Cookie to store page vary string */ @@ -113,8 +117,9 @@ public function sendVary() } /** - * Set headers for public cache - * Accepts the time-to-live (max-age) parameter + * Set headers for public cache. + * + * Also accepts the time-to-live (max-age) parameter. * * @param int $ttl * @return void @@ -174,11 +179,18 @@ public function representJson($content) } /** + * Remove links to other objects. + * * @return string[] * @codeCoverageIgnore + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + return ['content', 'isRedirect', 'statusCode', 'context', 'headers']; } @@ -187,9 +199,14 @@ public function __sleep() * * @return void * @codeCoverageIgnore + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $objectManager = ObjectManager::getInstance(); $this->cookieManager = $objectManager->create(\Magento\Framework\Stdlib\CookieManagerInterface::class); $this->cookieMetadataFactory = $objectManager->get( diff --git a/lib/internal/Magento/Framework/App/Route/ConfigInterface/Proxy.php b/lib/internal/Magento/Framework/App/Route/ConfigInterface/Proxy.php index fd37590bb7782..5e79315238f7d 100644 --- a/lib/internal/Magento/Framework/App/Route/ConfigInterface/Proxy.php +++ b/lib/internal/Magento/Framework/App/Route/ConfigInterface/Proxy.php @@ -60,10 +60,17 @@ public function __construct( } /** + * Remove links to other objects. + * * @return array + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + return ['_subject', '_isShared']; } @@ -71,9 +78,14 @@ public function __sleep() * Retrieve ObjectManager from global scope * * @return void + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $this->_objectManager = \Magento\Framework\App\ObjectManager::getInstance(); } diff --git a/lib/internal/Magento/Framework/App/Test/Unit/DocRootLocatorTest.php b/lib/internal/Magento/Framework/App/Test/Unit/DocRootLocatorTest.php index 23afbbc73d2b9..ef4152ba2e49e 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/DocRootLocatorTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/DocRootLocatorTest.php @@ -8,6 +8,9 @@ use Magento\Framework\App\DocRootLocator; +/** + * Test for Magento\Framework\App\DocRootLocator class. + */ class DocRootLocatorTest extends \PHPUnit\Framework\TestCase { /** @@ -21,11 +24,15 @@ public function testIsPub($path, $isExist, $result) { $request = $this->createMock(\Magento\Framework\App\Request\Http::class); $request->expects($this->once())->method('getServer')->willReturn($path); + + $readFactory = $this->createMock(\Magento\Framework\Filesystem\Directory\ReadFactory::class); + $reader = $this->createMock(\Magento\Framework\Filesystem\Directory\Read::class); + $filesystem = $this->createMock(\Magento\Framework\Filesystem::class); + $filesystem->expects($this->once())->method('getDirectoryRead')->willReturn($reader); $reader->expects($this->any())->method('isExist')->willReturn($isExist); - $readFactory = $this->createMock(\Magento\Framework\Filesystem\Directory\ReadFactory::class); - $readFactory->expects($this->once())->method('create')->willReturn($reader); - $model = new DocRootLocator($request, $readFactory); + + $model = new DocRootLocator($request, $readFactory, $filesystem); $this->assertSame($result, $model->isPub()); } diff --git a/lib/internal/Magento/Framework/App/Test/Unit/HttpTest.php b/lib/internal/Magento/Framework/App/Test/Unit/HttpTest.php index a299e04e152cc..dbb315e88a526 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/HttpTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/HttpTest.php @@ -92,7 +92,7 @@ protected function setUp() 'pathInfoProcessor' => $pathInfoProcessorMock, 'objectManager' => $objectManagerMock ]) - ->setMethods(['getFrontName']) + ->setMethods(['getFrontName', 'isHead']) ->getMock(); $this->areaListMock = $this->getMockBuilder(\Magento\Framework\App\AreaList::class) ->disableOriginalConstructor() @@ -135,12 +135,17 @@ private function setUpLaunch() { $frontName = 'frontName'; $areaCode = 'areaCode'; - $this->requestMock->expects($this->once())->method('getFrontName')->will($this->returnValue($frontName)); + $this->requestMock->expects($this->once()) + ->method('getFrontName') + ->willReturn($frontName); $this->areaListMock->expects($this->once()) ->method('getCodeByFrontName') - ->with($frontName)->will($this->returnValue($areaCode)); + ->with($frontName) + ->willReturn($areaCode); $this->configLoaderMock->expects($this->once()) - ->method('load')->with($areaCode)->will($this->returnValue([])); + ->method('load') + ->with($areaCode) + ->willReturn([]); $this->objectManagerMock->expects($this->once())->method('configure')->with([]); $this->objectManagerMock->expects($this->once()) ->method('get') @@ -149,12 +154,15 @@ private function setUpLaunch() $this->frontControllerMock->expects($this->once()) ->method('dispatch') ->with($this->requestMock) - ->will($this->returnValue($this->responseMock)); + ->willReturn($this->responseMock); } public function testLaunchSuccess() { $this->setUpLaunch(); + $this->requestMock->expects($this->once()) + ->method('isHead') + ->willReturn(false); $this->eventManagerMock->expects($this->once()) ->method('dispatch') ->with( @@ -171,33 +179,101 @@ public function testLaunchSuccess() public function testLaunchException() { $this->setUpLaunch(); - $this->frontControllerMock->expects($this->once())->method('dispatch')->with($this->requestMock)->will( - $this->returnCallback( - function () { - throw new \Exception('Message'); - } - ) - ); + $this->frontControllerMock->expects($this->once()) + ->method('dispatch') + ->with($this->requestMock) + ->willThrowException( + new \Exception('Message') + ); $this->http->launch(); } + /** + * Test that HEAD requests lead to an empty body and a Content-Length header matching the original body size. + * @dataProvider dataProviderForTestLaunchHeadRequest + * @param string $body + * @param int $expectedLength + */ + public function testLaunchHeadRequest($body, $expectedLength) + { + $this->setUpLaunch(); + $this->requestMock->expects($this->once()) + ->method('isHead') + ->willReturn(true); + $this->responseMock->expects($this->once()) + ->method('getHttpResponseCode') + ->willReturn(200); + $this->responseMock->expects($this->once()) + ->method('getContent') + ->willReturn($body); + $this->responseMock->expects($this->once()) + ->method('clearBody') + ->willReturn($this->responseMock); + $this->responseMock->expects($this->once()) + ->method('setHeader') + ->with('Content-Length', $expectedLength) + ->willReturn($this->responseMock); + $this->eventManagerMock->expects($this->once()) + ->method('dispatch') + ->with( + 'controller_front_send_response_before', + ['request' => $this->requestMock, 'response' => $this->responseMock] + ); + $this->assertSame($this->responseMock, $this->http->launch()); + } + + /** + * Different test content for responseMock with their expected lengths in bytes. + * @return array + */ + public function dataProviderForTestLaunchHeadRequest(): array + { + return [ + [ + "<html><head></head><body>Test</body></html>", // Ascii text + 43 // Expected Content-Length + ], + [ + "<html><head></head><body>部落格</body></html>", // Multi-byte characters + 48 // Expected Content-Length + ], + [ + "<html><head></head><body>\0</body></html>", // Null byte + 40 // Expected Content-Length + ], + [ + "<html><head></head>خرید<body></body></html>", // LTR text + 47 // Expected Content-Length + ] + ]; + } + public function testHandleDeveloperModeNotInstalled() { $dir = $this->getMockForAbstractClass(\Magento\Framework\Filesystem\Directory\ReadInterface::class); - $dir->expects($this->once())->method('getAbsolutePath')->willReturn(__DIR__); + $dir->expects($this->once()) + ->method('getAbsolutePath') + ->willReturn(__DIR__); $this->filesystemMock->expects($this->once()) ->method('getDirectoryRead') ->with(DirectoryList::ROOT) ->willReturn($dir); - $this->responseMock->expects($this->once())->method('setRedirect')->with('/_files/'); - $this->responseMock->expects($this->once())->method('sendHeaders'); + $this->responseMock->expects($this->once()) + ->method('setRedirect') + ->with('/_files/'); + $this->responseMock->expects($this->once()) + ->method('sendHeaders'); $bootstrap = $this->getBootstrapNotInstalled(); - $bootstrap->expects($this->once())->method('getParams')->willReturn([ - 'SCRIPT_NAME' => '/index.php', - 'DOCUMENT_ROOT' => __DIR__, - 'SCRIPT_FILENAME' => __DIR__ . '/index.php', - SetupInfo::PARAM_NOT_INSTALLED_URL_PATH => '_files', - ]); + $bootstrap->expects($this->once()) + ->method('getParams') + ->willReturn( + [ + 'SCRIPT_NAME' => '/index.php', + 'DOCUMENT_ROOT' => __DIR__, + 'SCRIPT_FILENAME' => __DIR__ . '/index.php', + SetupInfo::PARAM_NOT_INSTALLED_URL_PATH => '_files', + ] + ); $this->assertTrue($this->http->catchException($bootstrap, new \Exception('Test Message'))); } @@ -206,24 +282,37 @@ public function testHandleDeveloperMode() $this->filesystemMock->expects($this->once()) ->method('getDirectoryRead') ->will($this->throwException(new \Exception('strange error'))); - $this->responseMock->expects($this->once())->method('setHttpResponseCode')->with(500); - $this->responseMock->expects($this->once())->method('setHeader')->with('Content-Type', 'text/plain'); + $this->responseMock->expects($this->once()) + ->method('setHttpResponseCode') + ->with(500); + $this->responseMock->expects($this->once()) + ->method('setHeader') + ->with('Content-Type', 'text/plain'); $constraint = new \PHPUnit\Framework\Constraint\StringStartsWith('1 exception(s):'); - $this->responseMock->expects($this->once())->method('setBody')->with($constraint); - $this->responseMock->expects($this->once())->method('sendResponse'); + $this->responseMock->expects($this->once()) + ->method('setBody') + ->with($constraint); + $this->responseMock->expects($this->once()) + ->method('sendResponse'); $bootstrap = $this->getBootstrapNotInstalled(); - $bootstrap->expects($this->once())->method('getParams')->willReturn( - ['DOCUMENT_ROOT' => 'something', 'SCRIPT_FILENAME' => 'something/else'] - ); + $bootstrap->expects($this->once()) + ->method('getParams') + ->willReturn( + ['DOCUMENT_ROOT' => 'something', 'SCRIPT_FILENAME' => 'something/else'] + ); $this->assertTrue($this->http->catchException($bootstrap, new \Exception('Test'))); } public function testCatchExceptionSessionException() { - $this->responseMock->expects($this->once())->method('setRedirect'); - $this->responseMock->expects($this->once())->method('sendHeaders'); + $this->responseMock->expects($this->once()) + ->method('setRedirect'); + $this->responseMock->expects($this->once()) + ->method('sendHeaders'); $bootstrap = $this->createMock(\Magento\Framework\App\Bootstrap::class); - $bootstrap->expects($this->once())->method('isDeveloperMode')->willReturn(false); + $bootstrap->expects($this->once()) + ->method('isDeveloperMode') + ->willReturn(false); $this->assertTrue($this->http->catchException( $bootstrap, new \Magento\Framework\Exception\SessionException(new \Magento\Framework\Phrase('Test')) @@ -238,8 +327,12 @@ public function testCatchExceptionSessionException() private function getBootstrapNotInstalled() { $bootstrap = $this->createMock(\Magento\Framework\App\Bootstrap::class); - $bootstrap->expects($this->once())->method('isDeveloperMode')->willReturn(true); - $bootstrap->expects($this->once())->method('getErrorCode')->willReturn(Bootstrap::ERR_IS_INSTALLED); + $bootstrap->expects($this->once()) + ->method('isDeveloperMode') + ->willReturn(true); + $bootstrap->expects($this->once()) + ->method('getErrorCode') + ->willReturn(Bootstrap::ERR_IS_INSTALLED); return $bootstrap; } } diff --git a/lib/internal/Magento/Framework/App/Test/Unit/Response/HttpTest.php b/lib/internal/Magento/Framework/App/Test/Unit/Response/HttpTest.php index efb35b7321c3b..9be68b379900a 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/Response/HttpTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/Response/HttpTest.php @@ -290,45 +290,6 @@ public function testRepresentJson() $this->assertEquals('json_string', $this->model->getBody('default')); } - /** - * - * @expectedException \RuntimeException - * @expectedExceptionMessage ObjectManager isn't initialized - */ - public function testWakeUpWithException() - { - /* ensure that the test preconditions are met */ - $objectManagerClass = new \ReflectionClass(\Magento\Framework\App\ObjectManager::class); - $instanceProperty = $objectManagerClass->getProperty('_instance'); - $instanceProperty->setAccessible(true); - $instanceProperty->setValue(null); - - $this->model->__wakeup(); - $this->assertNull($this->cookieMetadataFactoryMock); - $this->assertNull($this->cookieManagerMock); - } - - /** - * Test for the magic method __wakeup - * - * @covers \Magento\Framework\App\Response\Http::__wakeup - */ - public function testWakeUpWith() - { - $objectManagerMock = $this->createMock(\Magento\Framework\App\ObjectManager::class); - $objectManagerMock->expects($this->once()) - ->method('create') - ->with(\Magento\Framework\Stdlib\CookieManagerInterface::class) - ->will($this->returnValue($this->cookieManagerMock)); - $objectManagerMock->expects($this->at(1)) - ->method('get') - ->with(\Magento\Framework\Stdlib\Cookie\CookieMetadataFactory::class) - ->will($this->returnValue($this->cookieMetadataFactoryMock)); - - \Magento\Framework\App\ObjectManager::setInstance($objectManagerMock); - $this->model->__wakeup(); - } - public function testSetXFrameOptions() { $value = 'DENY'; diff --git a/lib/internal/Magento/Framework/Code/Generator.php b/lib/internal/Magento/Framework/Code/Generator.php index 4dec7d1a28146..b46c8c681bb52 100644 --- a/lib/internal/Magento/Framework/Code/Generator.php +++ b/lib/internal/Magento/Framework/Code/Generator.php @@ -8,11 +8,15 @@ use Magento\Framework\Code\Generator\DefinedClasses; use Magento\Framework\Code\Generator\EntityAbstract; use Magento\Framework\Code\Generator\Io; +use Magento\Framework\ObjectManager\ConfigInterface; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Phrase; use Magento\Framework\Filesystem\Driver\File; use Psr\Log\LoggerInterface; +/** + * Class code generator. + */ class Generator { const GENERATION_SUCCESS = 'success'; @@ -232,7 +236,21 @@ protected function shouldSkipGeneration($resultEntityType, $sourceClassName, $re { if (!$resultEntityType || !$sourceClassName) { return self::GENERATION_ERROR; - } elseif ($this->definedClasses->isClassLoadableFromDisk($resultClass)) { + } + + /** @var ConfigInterface $omConfig */ + $omConfig = $this->objectManager->get(ConfigInterface::class); + $virtualTypes = $omConfig->getVirtualTypes(); + + /** + * Do not try to autogenerate virtual types + * For example virtual types with names overlapping autogenerated suffixes + */ + if (isset($virtualTypes[$resultClass])) { + return self::GENERATION_SKIP; + } + + if ($this->definedClasses->isClassLoadableFromDisk($resultClass)) { $generatedFileName = $this->_ioObject->generateResultFileName($resultClass); /** * Must handle two edge cases: a competing process has generated the class and written it to disc already, @@ -244,9 +262,12 @@ protected function shouldSkipGeneration($resultEntityType, $sourceClassName, $re $this->_ioObject->includeFile($generatedFileName); } return self::GENERATION_SKIP; - } elseif (!isset($this->_generatedEntities[$resultEntityType])) { + } + + if (!isset($this->_generatedEntities[$resultEntityType])) { throw new \InvalidArgumentException('Unknown generation entity.'); } + return false; } } diff --git a/lib/internal/Magento/Framework/Code/Test/Unit/GeneratorTest.php b/lib/internal/Magento/Framework/Code/Test/Unit/GeneratorTest.php index 9cc93f7620b1f..2753561683385 100644 --- a/lib/internal/Magento/Framework/Code/Test/Unit/GeneratorTest.php +++ b/lib/internal/Magento/Framework/Code/Test/Unit/GeneratorTest.php @@ -3,11 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\Code\Test\Unit; use Magento\Framework\Code\Generator; use Magento\Framework\Code\Generator\DefinedClasses; use Magento\Framework\Code\Generator\Io; +use Magento\Framework\ObjectManager\ConfigInterface; +use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Magento\Framework\ObjectManager\Code\Generator\Factory; use Magento\Framework\ObjectManager\Code\Generator\Proxy; @@ -17,13 +21,19 @@ use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Code\Generator\EntityAbstract; use Magento\GeneratedClass\Factory as GeneratedClassFactory; +use RuntimeException; +/** + * Tests for code generator. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class GeneratorTest extends TestCase { /** * Class name parameter value */ - const SOURCE_CLASS = 'testClassName'; + private const SOURCE_CLASS = 'testClassName'; /** * Expected generated entities @@ -58,6 +68,19 @@ class GeneratorTest extends TestCase */ private $loggerMock; + /** + * @var ObjectManagerInterface|MockObject + */ + private $objectManagerMock; + + /** + * @var ConfigInterface|MockObject + */ + private $objectManagerConfigMock; + + /** + * @inheritDoc + */ protected function setUp() { $this->definedClassesMock = $this->createMock(DefinedClasses::class); @@ -65,6 +88,12 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); $this->loggerMock = $this->getMockForAbstractClass(LoggerInterface::class); + $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->objectManagerConfigMock = $this->getMockBuilder(ConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); $this->model = new Generator( $this->ioObjectMock, @@ -78,7 +107,7 @@ protected function setUp() ); } - public function testGetGeneratedEntities() + public function testGetGeneratedEntities(): void { $this->model = new Generator( $this->ioObjectMock, @@ -91,22 +120,58 @@ public function testGetGeneratedEntities() /** * @param string $className * @param string $entityType - * @expectedException \RuntimeException + * @expectedException RuntimeException * @dataProvider generateValidClassDataProvider */ - public function testGenerateClass($className, $entityType) + public function testGenerateClass($className, $entityType): void { - $objectManagerMock = $this->createMock(ObjectManagerInterface::class); $fullClassName = $className . $entityType; + $entityGeneratorMock = $this->getMockBuilder(EntityAbstract::class) ->disableOriginalConstructor() ->getMock(); - $objectManagerMock->expects($this->once())->method('create')->willReturn($entityGeneratorMock); - $this->model->setObjectManager($objectManagerMock); - $this->model->generateClass($fullClassName); + $this->objectManagerMock + ->expects($this->once()) + ->method('create') + ->willReturn($entityGeneratorMock); + + $this->objectManagerConfigMock + ->expects($this->once()) + ->method('getVirtualTypes') + ->willReturn([]); + $this->objectManagerMock + ->expects($this->once()) + ->method('get') + ->with(ConfigInterface::class) + ->willReturn($this->objectManagerConfigMock); + $this->model->setObjectManager($this->objectManagerMock); + + $this->assertSame( + Generator::GENERATION_SUCCESS, + $this->model->generateClass($fullClassName) + ); } - public function testGenerateClassWithWrongName() + public function testShouldNotGenerateVirtualType(): void + { + $this->objectManagerConfigMock + ->expects($this->once()) + ->method('getVirtualTypes') + ->willReturn([GeneratedClassFactory::class => GeneratedClassFactory::class]); + $this->objectManagerMock + ->expects($this->once()) + ->method('get') + ->with(ConfigInterface::class) + ->willReturn($this->objectManagerConfigMock); + $this->model->setObjectManager($this->objectManagerMock); + + $this->assertSame( + Generator::GENERATION_SKIP, + $this->model->generateClass(GeneratedClassFactory::class) + ); + } + + public function testGenerateClassWithWrongName(): void { $this->assertEquals( Generator::GENERATION_ERROR, @@ -115,25 +180,42 @@ public function testGenerateClassWithWrongName() } /** - * @expectedException \RuntimeException + * @expectedException RuntimeException */ - public function testGenerateClassWhenClassIsNotGenerationSuccess() + public function testGenerateClassWhenClassIsNotGenerationSuccess(): void { $expectedEntities = array_values($this->expectedEntities); $resultClassName = self::SOURCE_CLASS . ucfirst(array_shift($expectedEntities)); - $objectManagerMock = $this->createMock(ObjectManagerInterface::class); + $entityGeneratorMock = $this->getMockBuilder(EntityAbstract::class) ->disableOriginalConstructor() ->getMock(); - $objectManagerMock->expects($this->once())->method('create')->willReturn($entityGeneratorMock); - $this->model->setObjectManager($objectManagerMock); - $this->model->generateClass($resultClassName); + $this->objectManagerMock + ->expects($this->once()) + ->method('create') + ->willReturn($entityGeneratorMock); + + $this->objectManagerConfigMock + ->expects($this->once()) + ->method('getVirtualTypes') + ->willReturn([]); + $this->objectManagerMock + ->expects($this->once()) + ->method('get') + ->with(ConfigInterface::class) + ->willReturn($this->objectManagerConfigMock); + $this->model->setObjectManager($this->objectManagerMock); + + $this->assertSame( + Generator::GENERATION_SUCCESS, + $this->model->generateClass($resultClassName) + ); } /** * @inheritdoc */ - public function testGenerateClassWithErrors() + public function testGenerateClassWithErrors(): void { $expectedEntities = array_values($this->expectedEntities); $resultClassName = self::SOURCE_CLASS . ucfirst(array_shift($expectedEntities)); @@ -148,17 +230,15 @@ public function testGenerateClassWithErrors() . 'directory permission is set to write --- the requested class did not generate properly, then ' . 'you must add the generated class object to the signature of the related construct method, only.'; $FinalErrorMessage = implode(PHP_EOL, $errorMessages) . "\n" . $mainErrorMessage; - $this->expectException(\RuntimeException::class); + $this->expectException(RuntimeException::class); $this->expectExceptionMessage($FinalErrorMessage); - /** @var ObjectManagerInterface|Mock $objectManagerMock */ - $objectManagerMock = $this->createMock(ObjectManagerInterface::class); /** @var EntityAbstract|Mock $entityGeneratorMock */ $entityGeneratorMock = $this->getMockBuilder(EntityAbstract::class) ->disableOriginalConstructor() ->getMock(); - $objectManagerMock->expects($this->once()) + $this->objectManagerMock->expects($this->once()) ->method('create') ->willReturn($entityGeneratorMock); $entityGeneratorMock->expects($this->once()) @@ -177,26 +257,62 @@ public function testGenerateClassWithErrors() $this->loggerMock->expects($this->once()) ->method('critical') ->with($FinalErrorMessage); - $this->model->setObjectManager($objectManagerMock); - $this->model->generateClass($resultClassName); + + $this->objectManagerConfigMock + ->expects($this->once()) + ->method('getVirtualTypes') + ->willReturn([]); + $this->objectManagerMock + ->expects($this->once()) + ->method('get') + ->with(ConfigInterface::class) + ->willReturn($this->objectManagerConfigMock); + $this->model->setObjectManager($this->objectManagerMock); + + $this->assertSame( + Generator::GENERATION_SUCCESS, + $this->model->generateClass($resultClassName) + ); } /** * @dataProvider trueFalseDataProvider + * @param $fileExists */ - public function testGenerateClassWithExistName($fileExists) + public function testGenerateClassWithExistName($fileExists): void { $this->definedClassesMock->expects($this->any()) ->method('isClassLoadableFromDisk') ->willReturn(true); $resultClassFileName = '/Magento/Path/To/Class.php'; - $this->ioObjectMock->expects($this->once())->method('generateResultFileName')->willReturn($resultClassFileName); - $this->ioObjectMock->expects($this->once())->method('fileExists')->willReturn($fileExists); + + $this->objectManagerConfigMock + ->expects($this->once()) + ->method('getVirtualTypes') + ->willReturn([]); + $this->objectManagerMock + ->expects($this->once()) + ->method('get') + ->with(ConfigInterface::class) + ->willReturn($this->objectManagerConfigMock); + $this->model->setObjectManager($this->objectManagerMock); + + $this->ioObjectMock + ->expects($this->once()) + ->method('generateResultFileName') + ->willReturn($resultClassFileName); + $this->ioObjectMock + ->expects($this->once()) + ->method('fileExists') + ->willReturn($fileExists); + $includeFileInvokeCount = $fileExists ? 1 : 0; - $this->ioObjectMock->expects($this->exactly($includeFileInvokeCount))->method('includeFile'); + $this->ioObjectMock + ->expects($this->exactly($includeFileInvokeCount)) + ->method('includeFile'); - $this->assertEquals( + $this->assertSame( Generator::GENERATION_SKIP, $this->model->generateClass(GeneratedClassFactory::class) ); @@ -205,7 +321,7 @@ public function testGenerateClassWithExistName($fileExists) /** * @return array */ - public function trueFalseDataProvider() + public function trueFalseDataProvider(): array { return [[true], [false]]; } @@ -215,7 +331,7 @@ public function trueFalseDataProvider() * * @return array */ - public function generateValidClassDataProvider() + public function generateValidClassDataProvider(): array { $data = []; foreach ($this->expectedEntities as $generatedEntity) { diff --git a/lib/internal/Magento/Framework/Config/Dom.php b/lib/internal/Magento/Framework/Config/Dom.php index f4721660d8da6..5c97c996634dd 100644 --- a/lib/internal/Magento/Framework/Config/Dom.php +++ b/lib/internal/Magento/Framework/Config/Dom.php @@ -190,9 +190,20 @@ protected function _mergeNode(\DOMElement $node, $parentPath) /* override node value */ if ($this->_isTextNode($node)) { /* skip the case when the matched node has children, otherwise they get overridden */ - if (!$matchedNode->hasChildNodes() || $this->_isTextNode($matchedNode)) { + if (!$matchedNode->hasChildNodes() + || $this->_isTextNode($matchedNode) + || $this->isCdataNode($matchedNode) + ) { $matchedNode->nodeValue = $node->childNodes->item(0)->nodeValue; } + } elseif ($this->isCdataNode($node) && $this->_isTextNode($matchedNode)) { + /* Replace text node with CDATA section */ + if ($this->findCdataSection($node)) { + $matchedNode->nodeValue = $this->findCdataSection($node)->nodeValue; + } + } elseif ($this->isCdataNode($node) && $this->isCdataNode($matchedNode)) { + /* Replace CDATA with new one */ + $this->replaceCdataNode($matchedNode, $node); } else { /* recursive merge for all child nodes */ foreach ($node->childNodes as $childNode) { @@ -220,6 +231,56 @@ protected function _isTextNode($node) return $node->childNodes->length == 1 && $node->childNodes->item(0) instanceof \DOMText; } + /** + * Check if the node content is CDATA (probably surrounded with text nodes) or just text node + * + * @param \DOMNode $node + * @return bool + */ + private function isCdataNode($node) + { + // If every child node of current is NOT \DOMElement + // It is arbitrary combination of text nodes and CDATA sections. + foreach ($node->childNodes as $childNode) { + if ($childNode instanceof \DOMElement) { + return false; + } + } + + return true; + } + + /** + * Finds CDATA section from given node children + * + * @param \DOMNode $node + * @return \DOMCdataSection|null + */ + private function findCdataSection($node) + { + foreach ($node->childNodes as $childNode) { + if ($childNode instanceof \DOMCdataSection) { + return $childNode; + } + } + } + + /** + * Replaces CDATA section in $oldNode with $newNode's + * + * @param \DOMNode $oldNode + * @param \DOMNode $newNode + */ + private function replaceCdataNode($oldNode, $newNode) + { + $oldCdata = $this->findCdataSection($oldNode); + $newCdata = $this->findCdataSection($newNode); + + if ($oldCdata && $newCdata) { + $oldCdata->nodeValue = $newCdata->nodeValue; + } + } + /** * Merges attributes of the merge node to the base node * diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/DomTest.php b/lib/internal/Magento/Framework/Config/Test/Unit/DomTest.php index 0508b5e4fb359..73968ac1ed897 100644 --- a/lib/internal/Magento/Framework/Config/Test/Unit/DomTest.php +++ b/lib/internal/Magento/Framework/Config/Test/Unit/DomTest.php @@ -5,6 +5,9 @@ */ namespace Magento\Framework\Config\Test\Unit; +/** + * Test for \Magento\Framework\Config\Dom class. + */ class DomTest extends \PHPUnit\Framework\TestCase { /** @@ -62,6 +65,37 @@ public function mergeDataProvider() ['override_node.xml', 'override_node_new.xml', [], null, 'override_node_merged.xml'], ['override_node_new.xml', 'override_node.xml', [], null, 'override_node_merged.xml'], ['text_node.xml', 'text_node_new.xml', [], null, 'text_node_merged.xml'], + 'text node replaced with cdata' => [ + 'text_node_cdata.xml', + 'text_node_cdata_new.xml', + [], + null, + 'text_node_cdata_merged.xml' + ], + 'cdata' => ['cdata.xml', 'cdata_new.xml', [], null, 'cdata_merged.xml'], + 'cdata with html' => ['cdata_html.xml', 'cdata_html_new.xml', [], null, 'cdata_html_merged.xml'], + 'cdata replaced with text node' => [ + 'cdata_text.xml', + 'cdata_text_new.xml', + [], + null, + 'cdata_text_merged.xml' + ], + 'big cdata' => ['big_cdata.xml', 'big_cdata_new.xml', [], null, 'big_cdata_merged.xml'], + 'big cdata with attribute' => [ + 'big_cdata_attribute.xml', + 'big_cdata_attribute_new.xml', + [], + null, + 'big_cdata_attribute_merged.xml' + ], + 'big cdata replaced with text' => [ + 'big_cdata_text.xml', + 'big_cdata_text_new.xml', + [], + null, + 'big_cdata_text_merged.xml' + ], [ 'recursive.xml', 'recursive_new.xml', diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata.xml new file mode 100644 index 0000000000000..69eb0035958e6 --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode> + <![CDATA[Some Phrase]]> + </subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_attribute.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_attribute.xml new file mode 100644 index 0000000000000..12a9389e3d238 --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_attribute.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode attr="text"> + <![CDATA[Some Phrase]]> + </subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_attribute_merged.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_attribute_merged.xml new file mode 100644 index 0000000000000..6e95d843e34ba --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_attribute_merged.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode attr="text"> + <![CDATA[Some Other Phrase]]> + </subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_attribute_new.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_attribute_new.xml new file mode 100644 index 0000000000000..b905781a9fe50 --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_attribute_new.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode> + <![CDATA[Some Other Phrase]]> + </subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_merged.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_merged.xml new file mode 100644 index 0000000000000..b905781a9fe50 --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_merged.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode> + <![CDATA[Some Other Phrase]]> + </subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_new.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_new.xml new file mode 100644 index 0000000000000..b905781a9fe50 --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_new.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode> + <![CDATA[Some Other Phrase]]> + </subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_text.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_text.xml new file mode 100644 index 0000000000000..69eb0035958e6 --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_text.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode> + <![CDATA[Some Phrase]]> + </subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_text_merged.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_text_merged.xml new file mode 100644 index 0000000000000..3e37e67ffcf35 --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_text_merged.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode>Some Other Phrase</subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_text_new.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_text_new.xml new file mode 100644 index 0000000000000..3e37e67ffcf35 --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/big_cdata_text_new.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode>Some Other Phrase</subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata.xml new file mode 100644 index 0000000000000..f65a21e122394 --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode><![CDATA[Some Phrase]]></subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_html.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_html.xml new file mode 100644 index 0000000000000..15294f46445ec --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_html.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode><![CDATA[Some <br /> Phrase]]></subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_html_merged.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_html_merged.xml new file mode 100644 index 0000000000000..709d921f737e4 --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_html_merged.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode><![CDATA[Some <br /> Other <br /> Phrase]]></subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_html_new.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_html_new.xml new file mode 100644 index 0000000000000..709d921f737e4 --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_html_new.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode><![CDATA[Some <br /> Other <br /> Phrase]]></subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_merged.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_merged.xml new file mode 100644 index 0000000000000..e6d2d809d7f7f --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_merged.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode><![CDATA[Some Other Phrase]]></subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_new.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_new.xml new file mode 100644 index 0000000000000..e6d2d809d7f7f --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_new.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode><![CDATA[Some Other Phrase]]></subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_text.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_text.xml new file mode 100644 index 0000000000000..f65a21e122394 --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_text.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode><![CDATA[Some Phrase]]></subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_text_merged.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_text_merged.xml new file mode 100644 index 0000000000000..3e37e67ffcf35 --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_text_merged.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode>Some Other Phrase</subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_text_new.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_text_new.xml new file mode 100644 index 0000000000000..3e37e67ffcf35 --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/cdata_text_new.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode>Some Other Phrase</subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/text_node_cdata.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/text_node_cdata.xml new file mode 100644 index 0000000000000..6807872aa3d3a --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/text_node_cdata.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode>Some Phrase</subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/text_node_cdata_merged.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/text_node_cdata_merged.xml new file mode 100644 index 0000000000000..b905781a9fe50 --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/text_node_cdata_merged.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode> + <![CDATA[Some Other Phrase]]> + </subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/text_node_cdata_new.xml b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/text_node_cdata_new.xml new file mode 100644 index 0000000000000..b905781a9fe50 --- /dev/null +++ b/lib/internal/Magento/Framework/Config/Test/Unit/_files/dom/text_node_cdata_new.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root> + <node> + <subnode> + <![CDATA[Some Other Phrase]]> + </subnode> + </node> +</root> diff --git a/lib/internal/Magento/Framework/DB/Select.php b/lib/internal/Magento/Framework/DB/Select.php index 4d178b81af6df..f33aaea7d0e68 100644 --- a/lib/internal/Magento/Framework/DB/Select.php +++ b/lib/internal/Magento/Framework/DB/Select.php @@ -400,7 +400,7 @@ public function useStraightJoin($flag = true) /** * Render STRAIGHT_JOIN clause * - * @param string $sql SQL query + * @param string $sql SQL query * @return string */ protected function _renderStraightjoin($sql) @@ -452,7 +452,7 @@ public function orderRand($field = null) /** * Render FOR UPDATE clause * - * @param string $sql SQL query + * @param string $sql SQL query * @return string */ protected function _renderForupdate($sql) @@ -467,9 +467,9 @@ protected function _renderForupdate($sql) /** * Add EXISTS clause * - * @param Select $select - * @param string $joinCondition - * @param bool $isExists + * @param Select $select + * @param string $joinCondition + * @param bool $isExists * @return $this */ public function exists($select, $joinCondition, $isExists = true) @@ -509,11 +509,18 @@ public function assemble() } /** + * Remove links to other objects. + * * @return string[] * @since 100.0.11 + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $properties = array_keys(get_object_vars($this)); $properties = array_diff( $properties, @@ -530,9 +537,14 @@ public function __sleep() * * @return void * @since 100.0.11 + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); $this->_adapter = $objectManager->get(ResourceConnection::class)->getConnection(); $this->selectRenderer = $objectManager->get(\Magento\Framework\DB\Select\SelectRenderer::class); diff --git a/lib/internal/Magento/Framework/DB/Select/RendererProxy.php b/lib/internal/Magento/Framework/DB/Select/RendererProxy.php index 3626f6a07fa18..dc69b96b79050 100644 --- a/lib/internal/Magento/Framework/DB/Select/RendererProxy.php +++ b/lib/internal/Magento/Framework/DB/Select/RendererProxy.php @@ -56,10 +56,17 @@ public function __construct( } /** + * Remove links to other objects. + * * @return array + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + return ['_subject', '_isShared']; } @@ -67,9 +74,14 @@ public function __sleep() * Retrieve ObjectManager from global scope * * @return void + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $this->_objectManager = \Magento\Framework\App\ObjectManager::getInstance(); } @@ -99,7 +111,7 @@ protected function _getSubject() } /** - * {@inheritdoc} + * @inheritdoc */ public function render(\Magento\Framework\DB\Select $select, $sql = '') { diff --git a/lib/internal/Magento/Framework/Data/Collection.php b/lib/internal/Magento/Framework/Data/Collection.php index 9c789e81913c4..2f3aaad98dfe5 100644 --- a/lib/internal/Magento/Framework/Data/Collection.php +++ b/lib/internal/Magento/Framework/Data/Collection.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Framework\Data; use Magento\Framework\Data\Collection\EntityFactoryInterface; @@ -391,7 +392,7 @@ public function getItemByColumnValue($column, $value) /** * Adding item to item array * - * @param \Magento\Framework\DataObject $item + * @param \Magento\Framework\DataObject $item * @return $this * @throws \Exception */ @@ -401,6 +402,7 @@ public function addItem(\Magento\Framework\DataObject $item) if ($itemId !== null) { if (isset($this->_items[$itemId])) { + //phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception( 'Item (' . get_class($item) . ') with the same ID "' . $item->getId() . '" already exists.' ); @@ -452,7 +454,7 @@ public function getAllIds() /** * Remove item from collection by item key * - * @param mixed $key + * @param mixed $key * @return $this */ public function removeItemByKey($key) @@ -483,6 +485,7 @@ public function clear() { $this->_setIsLoaded(false); $this->_items = []; + $this->_totalRecords = null; return $this; } @@ -539,8 +542,8 @@ public function each($objMethod, $args = []) /** * Setting data for all collection items * - * @param mixed $key - * @param mixed $value + * @param mixed $key + * @param mixed $value * @return $this */ public function setDataToAll($key, $value = null) @@ -560,7 +563,7 @@ public function setDataToAll($key, $value = null) /** * Set current page * - * @param int $page + * @param int $page * @return $this */ public function setCurPage($page) @@ -572,7 +575,7 @@ public function setCurPage($page) /** * Set collection page size * - * @param int $size + * @param int $size * @return $this */ public function setPageSize($size) @@ -584,8 +587,8 @@ public function setPageSize($size) /** * Set select order * - * @param string $field - * @param string $direction + * @param string $field + * @param string $direction * @return $this */ public function setOrder($field, $direction = self::SORT_ORDER_DESC) @@ -597,7 +600,7 @@ public function setOrder($field, $direction = self::SORT_ORDER_DESC) /** * Set collection item class name * - * @param string $className + * @param string $className * @return $this * @throws \InvalidArgumentException */ @@ -886,9 +889,14 @@ public function hasFlag($flag) * * @return string[] * @since 100.0.11 + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $properties = array_keys(get_object_vars($this)); $properties = array_diff( $properties, @@ -904,9 +912,14 @@ public function __sleep() * * @return void * @since 100.0.11 + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); $this->_entityFactory = $objectManager->get(EntityFactoryInterface::class); } diff --git a/lib/internal/Magento/Framework/Data/Collection/AbstractDb.php b/lib/internal/Magento/Framework/Data/Collection/AbstractDb.php index 308f2a12f506e..1b28e367dcc3a 100644 --- a/lib/internal/Magento/Framework/Data/Collection/AbstractDb.php +++ b/lib/internal/Magento/Framework/Data/Collection/AbstractDb.php @@ -890,9 +890,14 @@ private function getMainTableAlias() /** * @inheritdoc * @since 100.0.11 + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + return array_diff( parent::__sleep(), ['_fetchStrategy', '_logger', '_conn', 'extensionAttributesJoinProcessor'] @@ -902,9 +907,14 @@ public function __sleep() /** * @inheritdoc * @since 100.0.11 + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + parent::__wakeup(); $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); $this->_logger = $objectManager->get(Logger::class); diff --git a/lib/internal/Magento/Framework/Data/Form/Element/Date.php b/lib/internal/Magento/Framework/Data/Form/Element/Date.php index 897617e560be5..6e4e97dbac79d 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/Date.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/Date.php @@ -53,6 +53,19 @@ public function __construct( } } + /** + * Check if a string is a date value + * + * @param string $value + * @return bool + */ + private function isDate(string $value): bool + { + $date = date_parse($value); + + return !empty($date['year']) && !empty($date['month']) && !empty($date['day']); + } + /** * If script executes on x64 system, converts large numeric values to timestamp limit * @@ -85,13 +98,13 @@ public function setValue($value) $this->_value = $value; return $this; } - try { if (preg_match('/^[0-9]+$/', $value)) { $this->_value = (new \DateTime())->setTimestamp($this->_toTimestamp($value)); + } elseif (is_string($value) && $this->isDate($value)) { + $this->_value = new \DateTime($value, new \DateTimeZone($this->localeDate->getConfigTimezone())); } else { - $this->_value = new \DateTime($value); - $this->_value->setTimezone(new \DateTimeZone($this->localeDate->getConfigTimezone())); + $this->_value = ''; } } catch (\Exception $e) { $this->_value = ''; diff --git a/lib/internal/Magento/Framework/Data/Form/Element/Textarea.php b/lib/internal/Magento/Framework/Data/Form/Element/Textarea.php index 7cd3fb1f7fb99..1970ebeb9544e 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/Textarea.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/Textarea.php @@ -4,15 +4,15 @@ * See COPYING.txt for license details. */ -/** - * Form textarea element - * - * @author Magento Core Team <core@magentocommerce.com> - */ namespace Magento\Framework\Data\Form\Element; use Magento\Framework\Escaper; +/** + * Form textarea element. + * + * @author Magento Core Team <core@magentocommerce.com> + */ class Textarea extends AbstractElement { /** @@ -64,6 +64,7 @@ public function getHtmlAttributes() 'rows', 'cols', 'readonly', + 'maxlength', 'disabled', 'onkeyup', 'tabindex', diff --git a/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/TextareaTest.php b/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/TextareaTest.php index e99df6c4c6e6f..eec85ca35775d 100644 --- a/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/TextareaTest.php +++ b/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/TextareaTest.php @@ -4,11 +4,11 @@ * See COPYING.txt for license details. */ -/** - * Tests for \Magento\Framework\Data\Form\Element\Textarea - */ namespace Magento\Framework\Data\Test\Unit\Form\Element; +/** + * Tests for \Magento\Framework\Data\Form\Element\Textarea class. + */ class TextareaTest extends \PHPUnit\Framework\TestCase { /** @@ -76,6 +76,7 @@ public function testGetHtmlAttributes() 'rows', 'cols', 'readonly', + 'maxlength', 'disabled', 'onkeyup', 'tabindex', diff --git a/lib/internal/Magento/Framework/DataObject/Copy/Config/Data/Proxy.php b/lib/internal/Magento/Framework/DataObject/Copy/Config/Data/Proxy.php index 880da5db771eb..b0f5742afef10 100644 --- a/lib/internal/Magento/Framework/DataObject/Copy/Config/Data/Proxy.php +++ b/lib/internal/Magento/Framework/DataObject/Copy/Config/Data/Proxy.php @@ -57,10 +57,17 @@ public function __construct( } /** + * Remove links to other objects. + * * @return array + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + return ['_subject', '_isShared']; } @@ -68,9 +75,14 @@ public function __sleep() * Retrieve ObjectManager from global scope * * @return void + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $this->_objectManager = \Magento\Framework\App\ObjectManager::getInstance(); } @@ -100,7 +112,7 @@ protected function _getSubject() } /** - * {@inheritdoc} + * @inheritdoc */ public function merge(array $config) { @@ -108,7 +120,7 @@ public function merge(array $config) } /** - * {@inheritdoc} + * @inheritdoc */ public function get($path = null, $default = null) { @@ -116,7 +128,7 @@ public function get($path = null, $default = null) } /** - * {@inheritdoc} + * @inheritdoc */ public function reset() { diff --git a/lib/internal/Magento/Framework/File/Test/Unit/Transfer/Adapter/HttpTest.php b/lib/internal/Magento/Framework/File/Test/Unit/Transfer/Adapter/HttpTest.php index d945791282a2d..023c4cc4ddba6 100644 --- a/lib/internal/Magento/Framework/File/Test/Unit/Transfer/Adapter/HttpTest.php +++ b/lib/internal/Magento/Framework/File/Test/Unit/Transfer/Adapter/HttpTest.php @@ -3,24 +3,37 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Framework\File\Test\Unit\Transfer\Adapter; -use \Magento\Framework\File\Transfer\Adapter\Http; +use Magento\Framework\File\Transfer\Adapter\Http; +use Magento\Framework\File\Mime; +use Magento\Framework\HTTP\PhpEnvironment\Response; +use Magento\Framework\App\Request\Http as RequestHttp; +use PHPUnit\Framework\MockObject\MockObject; +/** + * Tests http transfer adapter. + */ class HttpTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Framework\HTTP\PhpEnvironment\Response|\PHPUnit_Framework_MockObject_MockObject + * @var RequestHttp|MockObject + */ + private $request; + + /** + * @var Response|MockObject */ private $response; /** - * @var Http|\PHPUnit_Framework_MockObject_MockObject + * @var Http|MockObject */ private $object; /** - * @var \Magento\Framework\File\Mime|\PHPUnit_Framework_MockObject_MockObject + * @var Mime|MockObject */ private $mime; @@ -30,11 +43,15 @@ class HttpTest extends \PHPUnit\Framework\TestCase protected function setUp() { $this->response = $this->createPartialMock( - \Magento\Framework\HTTP\PhpEnvironment\Response::class, + Response::class, ['setHeader', 'sendHeaders', 'setHeaders'] ); - $this->mime = $this->createMock(\Magento\Framework\File\Mime::class); - $this->object = new Http($this->response, $this->mime); + $this->mime = $this->createMock(Mime::class); + $this->request = $this->createPartialMock( + RequestHttp::class, + ['isHead'] + ); + $this->object = new Http($this->response, $this->mime, $this->request); } /** @@ -56,7 +73,10 @@ public function testSend(): void $this->mime->expects($this->once()) ->method('getMimeType') ->with($file) - ->will($this->returnValue($contentType)); + ->willReturn($contentType); + $this->request->expects($this->once()) + ->method('isHead') + ->willReturn(false); $this->expectOutputString(file_get_contents($file)); $this->object->send($file); @@ -82,7 +102,10 @@ public function testSendWithOptions(): void $this->mime->expects($this->once()) ->method('getMimeType') ->with($file) - ->will($this->returnValue($contentType)); + ->willReturn($contentType); + $this->request->expects($this->once()) + ->method('isHead') + ->willReturn(false); $this->expectOutputString(file_get_contents($file)); $this->object->send(['filepath' => $file, 'headers' => $headers]); @@ -106,4 +129,32 @@ public function testSendNoFileExistException(): void { $this->object->send('nonexistent.file'); } + + /** + * @return void + */ + public function testSendHeadRequest(): void + { + $file = __DIR__ . '/../../_files/javascript.js'; + $contentType = 'content/type'; + + $this->response->expects($this->at(0)) + ->method('setHeader') + ->with('Content-length', filesize($file)); + $this->response->expects($this->at(1)) + ->method('setHeader') + ->with('Content-Type', $contentType); + $this->response->expects($this->once()) + ->method('sendHeaders'); + $this->mime->expects($this->once()) + ->method('getMimeType') + ->with($file) + ->willReturn($contentType); + $this->request->expects($this->once()) + ->method('isHead') + ->willReturn(true); + + $this->object->send($file); + $this->assertEquals(false, $this->hasOutput()); + } } diff --git a/lib/internal/Magento/Framework/File/Transfer/Adapter/Http.php b/lib/internal/Magento/Framework/File/Transfer/Adapter/Http.php index aa527866eff55..cd42c8d04b477 100644 --- a/lib/internal/Magento/Framework/File/Transfer/Adapter/Http.php +++ b/lib/internal/Magento/Framework/File/Transfer/Adapter/Http.php @@ -6,28 +6,46 @@ namespace Magento\Framework\File\Transfer\Adapter; +use Magento\Framework\HTTP\PhpEnvironment\Response; +use Magento\Framework\File\Mime; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\App\ObjectManager; +use Zend\Http\Headers; + +/** + * File adapter to send the file to the client. + */ class Http { /** - * @var \Magento\Framework\HTTP\PhpEnvironment\Response + * @var Response */ private $response; /** - * @var \Magento\Framework\File\Mime + * @var Mime */ private $mime; /** - * @param \Magento\Framework\App\Response\Http $response - * @param \Magento\Framework\File\Mime $mime + * @var HttpRequest + */ + private $request; + + /** + * @param Response $response + * @param Mime $mime + * @param HttpRequest|null $request */ public function __construct( - \Magento\Framework\HTTP\PhpEnvironment\Response $response, - \Magento\Framework\File\Mime $mime + Response $response, + Mime $mime, + HttpRequest $request = null ) { $this->response = $response; $this->mime = $mime; + $objectManager = ObjectManager::getInstance(); + $this->request = $request ?: $objectManager->get(HttpRequest::class); } /** @@ -46,18 +64,17 @@ public function send($options = null) throw new \InvalidArgumentException("File '{$filepath}' does not exists."); } - $mimeType = $this->mime->getMimeType($filepath); - if (is_array($options) && isset($options['headers']) && $options['headers'] instanceof \Zend\Http\Headers) { - $this->response->setHeaders($options['headers']); - } - $this->response->setHeader('Content-length', filesize($filepath)); - $this->response->setHeader('Content-Type', $mimeType); + $this->prepareResponse($options, $filepath); - $this->response->sendHeaders(); + if ($this->request->isHead()) { + // Do not send the body on HEAD requests. + return; + } $handle = fopen($filepath, 'r'); if ($handle) { while (($buffer = fgets($handle, 4096)) !== false) { + // phpcs:ignore Magento2.Security.LanguageConstruct.DirectOutput echo $buffer; } if (!feof($handle)) { @@ -88,4 +105,22 @@ private function getFilePath($options): string return $filePath; } + + /** + * Set and send all necessary headers. + * + * @param array $options + * @param string $filepath + */ + private function prepareResponse($options, string $filepath): void + { + $mimeType = $this->mime->getMimeType($filepath); + if (is_array($options) && isset($options['headers']) && $options['headers'] instanceof Headers) { + $this->response->setHeaders($options['headers']); + } + $this->response->setHeader('Content-length', filesize($filepath)); + $this->response->setHeader('Content-Type', $mimeType); + + $this->response->sendHeaders(); + } } diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/Field.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/Field.php index 76cfa06f9c11f..0fc51e4ecd069 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/Field.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/Field.php @@ -48,6 +48,11 @@ class Field implements OutputFieldInterface */ private $description; + /** + * @var array + */ + private $cache; + /** * @param string $name * @param string $type @@ -57,6 +62,7 @@ class Field implements OutputFieldInterface * @param string $resolver * @param string $description * @param array $arguments + * @param array $cache */ public function __construct( string $name, @@ -66,7 +72,8 @@ public function __construct( string $itemType = '', string $resolver = '', string $description = '', - array $arguments = [] + array $arguments = [], + array $cache = [] ) { $this->name = $name; $this->type = $isList ? $itemType : $type; @@ -75,6 +82,7 @@ public function __construct( $this->resolver = $resolver; $this->description = $description; $this->arguments = $arguments; + $this->cache = $cache; } /** @@ -146,4 +154,14 @@ public function getDescription() : string { return $this->description; } + + /** + * Return the cache tag for the field. + * + * @return array|null + */ + public function getCache() : array + { + return $this->cache; + } } diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php index b9ec1dd87d122..60191b69be47f 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php @@ -62,7 +62,8 @@ public function createFromConfigData( 'itemType' => isset($fieldData['itemType']) ? $fieldData['itemType'] : '', 'resolver' => isset($fieldData['resolver']) ? $fieldData['resolver'] : '', 'description' => isset($fieldData['description']) ? $fieldData['description'] : '', - 'arguments' => $arguments + 'cache' => isset($fieldData['cache']) ? $fieldData['cache'] : [], + 'arguments' => $arguments, ] ); } diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/IdentityInterface.php b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/IdentityInterface.php new file mode 100644 index 0000000000000..d65e86a37550d --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/IdentityInterface.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Query\Resolver; + +interface IdentityInterface +{ + + /** + * Get identities from resolved data + * + * @param array $resolvedData + * @return string[] + */ + public function getIdentities(array $resolvedData) : array; +} diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/CacheTagReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/CacheTagReader.php new file mode 100644 index 0000000000000..2613b2829e79a --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/CacheTagReader.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader; + +/** + * Reads documentation from the annotation @cache of an AST node + */ +class CacheTagReader +{ + /** + * Read documentation annotation for a specific node if exists + * + * @param \GraphQL\Language\AST\NodeList $directives + * @return array + */ + public function read(\GraphQL\Language\AST\NodeList $directives) : array + { + $argMap = []; + foreach ($directives as $directive) { + if ($directive->name->value == 'cache') { + foreach ($directive->arguments as $directiveArgument) { + if ($directiveArgument->name->value == 'cacheTag') { + $argMap = array_merge( + $argMap, + ["cacheTag" => $directiveArgument->value->value] + ); + } + if ($directiveArgument->name->value == 'cacheable') { + $argMap = array_merge( + $argMap, + ["cacheable" => $directiveArgument->value->value] + ); + } + if ($directiveArgument->name->value == 'cacheIdentity') { + $argMap = array_merge( + $argMap, + ["cacheIdentity" => $directiveArgument->value->value] + ); + } + } + } + } + return $argMap; + } +} diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php index 736a944711004..554d2636cf8c3 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php @@ -7,8 +7,6 @@ namespace Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader; -use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\TypeMetaWrapperReader; - /** * Reads fields and possible arguments from a meta field */ @@ -24,14 +22,25 @@ class FieldMetaReader */ private $docReader; + /** + * @var CacheTagReader + */ + private $cacheTagReader; + /** * @param TypeMetaWrapperReader $typeMetaReader * @param DocReader $docReader + * @param CacheTagReader|null $cacheTagReader */ - public function __construct(TypeMetaWrapperReader $typeMetaReader, DocReader $docReader) - { + public function __construct( + TypeMetaWrapperReader $typeMetaReader, + DocReader $docReader, + CacheTagReader $cacheTagReader = null + ) { $this->typeMetaReader = $typeMetaReader; $this->docReader = $docReader; + $this->cacheTagReader = $cacheTagReader ?? \Magento\Framework\App\ObjectManager::getInstance() + ->get(CacheTagReader::class); } /** @@ -63,6 +72,10 @@ public function read(\GraphQL\Type\Definition\FieldDefinition $fieldMeta) : arra $result['description'] = $this->docReader->read($fieldMeta->astNode->directives); } + if ($this->docReader->read($fieldMeta->astNode->directives)) { + $result['cache'] = $this->cacheTagReader->read($fieldMeta->astNode->directives); + } + $arguments = $fieldMeta->args; foreach ($arguments as $argumentMeta) { $argumentName = $argumentMeta->name; diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InputObjectType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InputObjectType.php index 3aea555e67f98..2eda79ce68b04 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InputObjectType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InputObjectType.php @@ -10,6 +10,7 @@ use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\TypeMetaReaderInterface; use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\TypeMetaWrapperReader; use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\DocReader; +use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\CacheTagReader; /** * Composite configuration reader to handle the input object type meta @@ -26,18 +27,29 @@ class InputObjectType implements TypeMetaReaderInterface */ private $docReader; + /** + * @var CacheTagReader + */ + private $cacheTagReader; + /** * @param TypeMetaWrapperReader $typeMetaReader * @param DocReader $docReader + * @param CacheTagReader|null $cacheTagReader */ - public function __construct(TypeMetaWrapperReader $typeMetaReader, DocReader $docReader) - { + public function __construct( + TypeMetaWrapperReader $typeMetaReader, + DocReader $docReader, + CacheTagReader $cacheTagReader = null + ) { $this->typeMetaReader = $typeMetaReader; $this->docReader = $docReader; + $this->cacheTagReader = $cacheTagReader ?? \Magento\Framework\App\ObjectManager::getInstance() + ->get(CacheTagReader::class); } /** - * {@inheritdoc} + * @inheritdoc */ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array { @@ -56,6 +68,10 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array if ($this->docReader->read($typeMeta->astNode->directives)) { $result['description'] = $this->docReader->read($typeMeta->astNode->directives); } + + if ($this->docReader->read($typeMeta->astNode->directives)) { + $result['cache'] = $this->cacheTagReader->read($typeMeta->astNode->directives); + } return $result; } else { return []; @@ -63,6 +79,8 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array } /** + * Read the input's meta data + * * @param \GraphQL\Type\Definition\InputObjectField $fieldMeta * @return array */ diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InterfaceType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InterfaceType.php index dd934ffebc2c1..7c040cd2e104c 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InterfaceType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InterfaceType.php @@ -10,6 +10,7 @@ use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\TypeMetaReaderInterface; use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\FieldMetaReader; use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\DocReader; +use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\CacheTagReader; /** * Composite configuration reader to handle the interface object type meta @@ -26,18 +27,29 @@ class InterfaceType implements TypeMetaReaderInterface */ private $docReader; + /** + * @var CacheTagReader + */ + private $cacheTagReader; + /** * @param FieldMetaReader $fieldMetaReader * @param DocReader $docReader + * @param CacheTagReader|null $cacheTagReader */ - public function __construct(FieldMetaReader $fieldMetaReader, DocReader $docReader) - { + public function __construct( + FieldMetaReader $fieldMetaReader, + DocReader $docReader, + CacheTagReader $cacheTagReader = null + ) { $this->fieldMetaReader = $fieldMetaReader; $this->docReader = $docReader; + $this->cacheTagReader = $cacheTagReader ?? \Magento\Framework\App\ObjectManager::getInstance() + ->get(CacheTagReader::class); } /** - * {@inheritdoc} + * @inheritdoc */ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array { @@ -63,6 +75,10 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array $result['description'] = $this->docReader->read($typeMeta->astNode->directives); } + if ($this->docReader->read($typeMeta->astNode->directives)) { + $result['cache'] = $this->cacheTagReader->read($typeMeta->astNode->directives); + } + return $result; } else { return []; diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php index fb015922087b6..77a44460f00ae 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php @@ -11,6 +11,7 @@ use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\FieldMetaReader; use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\DocReader; use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\ImplementsReader; +use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\CacheTagReader; /** * Composite configuration reader to handle the object type meta @@ -33,22 +34,32 @@ class ObjectType implements TypeMetaReaderInterface private $implementsAnnotation; /** + * @var CacheTagReader + */ + private $cacheTagReader; + + /** + * ObjectType constructor. * @param FieldMetaReader $fieldMetaReader * @param DocReader $docReader * @param ImplementsReader $implementsAnnotation + * @param CacheTagReader|null $cacheTagReader */ public function __construct( FieldMetaReader $fieldMetaReader, DocReader $docReader, - ImplementsReader $implementsAnnotation + ImplementsReader $implementsAnnotation, + CacheTagReader $cacheTagReader = null ) { $this->fieldMetaReader = $fieldMetaReader; $this->docReader = $docReader; $this->implementsAnnotation = $implementsAnnotation; + $this->cacheTagReader = $cacheTagReader ?? \Magento\Framework\App\ObjectManager::getInstance() + ->get(CacheTagReader::class); } /** - * {@inheritdoc} + * @inheritdoc */ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array { @@ -77,6 +88,10 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array $result['description'] = $this->docReader->read($typeMeta->astNode->directives); } + if ($this->docReader->read($typeMeta->astNode->directives)) { + $result['cache'] = $this->cacheTagReader->read($typeMeta->astNode->directives); + } + return $result; } else { return []; diff --git a/lib/internal/Magento/Framework/HTTP/Adapter/Curl.php b/lib/internal/Magento/Framework/HTTP/Adapter/Curl.php index bc833bf3bb2d4..f2a703a193e28 100644 --- a/lib/internal/Magento/Framework/HTTP/Adapter/Curl.php +++ b/lib/internal/Magento/Framework/HTTP/Adapter/Curl.php @@ -4,11 +4,6 @@ * See COPYING.txt for license details. */ -/** - * HTTP CURL Adapter - * - * @author Magento Core Team <core@magentocommerce.com> - */ namespace Magento\Framework\HTTP\Adapter; /** @@ -115,8 +110,8 @@ public function setOptions(array $options = []) /** * Add additional option to cURL * - * @param int $option the CURLOPT_* constants - * @param mixed $value + * @param int $option the CURLOPT_* constants + * @param mixed $value * @return $this */ public function addOption($option, $value) @@ -183,6 +178,12 @@ public function write($method, $url, $http_ver = '1.1', $headers = [], $body = ' curl_setopt($this->_getResource(), CURLOPT_CUSTOMREQUEST, 'GET'); } + if ($http_ver === \Zend_Http_Client::HTTP_1) { + curl_setopt($this->_getResource(), CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + } elseif ($http_ver === \Zend_Http_Client::HTTP_0) { + curl_setopt($this->_getResource(), CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + } + if (is_array($headers)) { curl_setopt($this->_getResource(), CURLOPT_HTTPHEADER, $headers); } diff --git a/lib/internal/Magento/Framework/Interception/Interceptor.php b/lib/internal/Magento/Framework/Interception/Interceptor.php index 07600c5168181..df1b680234220 100644 --- a/lib/internal/Magento/Framework/Interception/Interceptor.php +++ b/lib/internal/Magento/Framework/Interception/Interceptor.php @@ -62,9 +62,14 @@ public function ___callParent($method, array $arguments) * Calls parent class sleep if defined, otherwise provides own implementation * * @return array + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + if (method_exists(get_parent_class($this), '__sleep')) { $properties = parent::__sleep(); } else { @@ -78,9 +83,14 @@ public function __sleep() * Causes Interceptor to be initialized * * @return void + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + if (method_exists(get_parent_class($this), '__wakeup')) { parent::__wakeup(); } diff --git a/lib/internal/Magento/Framework/MessageQueue/CallbackInvoker.php b/lib/internal/Magento/Framework/MessageQueue/CallbackInvoker.php index 48c33c48f12e6..fe0a84af3ca93 100644 --- a/lib/internal/Magento/Framework/MessageQueue/CallbackInvoker.php +++ b/lib/internal/Magento/Framework/MessageQueue/CallbackInvoker.php @@ -6,11 +6,41 @@ namespace Magento\Framework\MessageQueue; +use Magento\Framework\MessageQueue\PoisonPill\PoisonPillCompareInterface; +use Magento\Framework\MessageQueue\PoisonPill\PoisonPillReadInterface; + /** * Class CallbackInvoker to invoke callbacks for consumer classes */ class CallbackInvoker implements CallbackInvokerInterface { + /** + * @var PoisonPillReadInterface $poisonPillRead + */ + private $poisonPillRead; + + /** + * @var int $poisonPillVersion + */ + private $poisonPillVersion; + + /** + * @var PoisonPillCompareInterface + */ + private $poisonPillCompare; + + /** + * @param PoisonPillReadInterface $poisonPillRead + * @param PoisonPillCompareInterface $poisonPillCompare + */ + public function __construct( + PoisonPillReadInterface $poisonPillRead, + PoisonPillCompareInterface $poisonPillCompare + ) { + $this->poisonPillRead = $poisonPillRead; + $this->poisonPillCompare = $poisonPillCompare; + } + /** * Run short running process * @@ -21,10 +51,17 @@ class CallbackInvoker implements CallbackInvokerInterface */ public function invoke(QueueInterface $queue, $maxNumberOfMessages, $callback) { + $this->poisonPillVersion = $this->poisonPillRead->getLatestVersion(); for ($i = $maxNumberOfMessages; $i > 0; $i--) { do { $message = $queue->dequeue(); + // phpcs:ignore Magento2.Functions.DiscouragedFunction } while ($message === null && (sleep(1) === 0)); + if (false === $this->poisonPillCompare->isLatestVersion($this->poisonPillVersion)) { + $queue->reject($message); + // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage + exit(0); + } $callback($message); } } diff --git a/lib/internal/Magento/Framework/MessageQueue/PoisonPill/PoisonPillCompare.php b/lib/internal/Magento/Framework/MessageQueue/PoisonPill/PoisonPillCompare.php new file mode 100644 index 0000000000000..0b85fbb61472a --- /dev/null +++ b/lib/internal/Magento/Framework/MessageQueue/PoisonPill/PoisonPillCompare.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\MessageQueue\PoisonPill; + +/** + * Describes how to compare given version of poison pill with latest in DB. + */ +class PoisonPillCompare implements PoisonPillCompareInterface +{ + /** + * Stub implementation + * + * @todo Will use cache storage after @MC-15997 + * + * @param string $poisonPillVersion + * @return bool + */ + public function isLatestVersion(string $poisonPillVersion): bool + { + return true; + } +} diff --git a/app/code/Magento/MessageQueue/Api/PoisonPillCompareInterface.php b/lib/internal/Magento/Framework/MessageQueue/PoisonPill/PoisonPillCompareInterface.php similarity index 57% rename from app/code/Magento/MessageQueue/Api/PoisonPillCompareInterface.php rename to lib/internal/Magento/Framework/MessageQueue/PoisonPill/PoisonPillCompareInterface.php index 3d5b895575597..acf78f5014ef6 100644 --- a/app/code/Magento/MessageQueue/Api/PoisonPillCompareInterface.php +++ b/lib/internal/Magento/Framework/MessageQueue/PoisonPill/PoisonPillCompareInterface.php @@ -5,20 +5,18 @@ */ declare(strict_types=1); -namespace Magento\MessageQueue\Api; +namespace Magento\Framework\MessageQueue\PoisonPill; /** - * Interface describes how to describes how to compare poison pill with latest in DB. - * - * @api + * Interface describes how to compare given version of poison pill with latest in DB. */ interface PoisonPillCompareInterface { /** * Check if version of current poison pill is latest. * - * @param int $poisonPillVersion + * @param string $poisonPillVersion * @return bool */ - public function isLatestVersion(int $poisonPillVersion): bool; + public function isLatestVersion(string $poisonPillVersion): bool; } diff --git a/lib/internal/Magento/Framework/MessageQueue/PoisonPill/PoisonPillPut.php b/lib/internal/Magento/Framework/MessageQueue/PoisonPill/PoisonPillPut.php new file mode 100644 index 0000000000000..7e6828633aea9 --- /dev/null +++ b/lib/internal/Magento/Framework/MessageQueue/PoisonPill/PoisonPillPut.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\MessageQueue\PoisonPill; + +/** + * Command interface describes how to create new version on poison pill. + */ +class PoisonPillPut implements PoisonPillPutInterface +{ + /** + * First version of poison pill. + * + * @var string + */ + private $firstVersion = ''; + + /** + * Stub implementation. + * + * @todo Will use cache storage after @MC-15997 + * + * @return string + */ + public function put(): string + { + return $this->firstVersion; + } +} diff --git a/app/code/Magento/MessageQueue/Api/PoisonPillPutInterface.php b/lib/internal/Magento/Framework/MessageQueue/PoisonPill/PoisonPillPutInterface.php similarity index 75% rename from app/code/Magento/MessageQueue/Api/PoisonPillPutInterface.php rename to lib/internal/Magento/Framework/MessageQueue/PoisonPill/PoisonPillPutInterface.php index 02293c99bb3f4..dbf300101c453 100644 --- a/app/code/Magento/MessageQueue/Api/PoisonPillPutInterface.php +++ b/lib/internal/Magento/Framework/MessageQueue/PoisonPill/PoisonPillPutInterface.php @@ -5,20 +5,18 @@ */ declare(strict_types=1); -namespace Magento\MessageQueue\Api; +namespace Magento\Framework\MessageQueue\PoisonPill; /** * Command interface describes how to create new version on poison pill. - * - * @api */ interface PoisonPillPutInterface { /** * Put new version of poison pill inside DB. * - * @return int + * @return string * @throws \Exception */ - public function put(): int; + public function put(): string; } diff --git a/lib/internal/Magento/Framework/MessageQueue/PoisonPill/PoisonPillRead.php b/lib/internal/Magento/Framework/MessageQueue/PoisonPill/PoisonPillRead.php new file mode 100644 index 0000000000000..0f0ed5a807473 --- /dev/null +++ b/lib/internal/Magento/Framework/MessageQueue/PoisonPill/PoisonPillRead.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\MessageQueue\PoisonPill; + +/** + * Describes how to get latest version of poison pill. + */ +class PoisonPillRead implements PoisonPillReadInterface +{ + /** + * Stub implementation. + * + * @todo Will use cache storage after @MC-15997 + * + * @return string + */ + public function getLatestVersion(): string + { + return ''; + } +} diff --git a/app/code/Magento/MessageQueue/Api/PoisonPillReadInterface.php b/lib/internal/Magento/Framework/MessageQueue/PoisonPill/PoisonPillReadInterface.php similarity index 71% rename from app/code/Magento/MessageQueue/Api/PoisonPillReadInterface.php rename to lib/internal/Magento/Framework/MessageQueue/PoisonPill/PoisonPillReadInterface.php index db97990ebbad5..6748e3208611f 100644 --- a/app/code/Magento/MessageQueue/Api/PoisonPillReadInterface.php +++ b/lib/internal/Magento/Framework/MessageQueue/PoisonPill/PoisonPillReadInterface.php @@ -5,19 +5,17 @@ */ declare(strict_types=1); -namespace Magento\MessageQueue\Api; +namespace Magento\Framework\MessageQueue\PoisonPill; /** * Describes how to get latest version of poison pill. - * - * @api */ interface PoisonPillReadInterface { /** * Returns get latest version of poison pill. * - * @return int + * @return string */ - public function getLatestVersion(): int; + public function getLatestVersion(): string; } diff --git a/lib/internal/Magento/Framework/MessageQueue/Test/Unit/ConsumerTest.php b/lib/internal/Magento/Framework/MessageQueue/Test/Unit/ConsumerTest.php index 5eb7cd587aaa2..7a3eb3b16baca 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Test/Unit/ConsumerTest.php +++ b/lib/internal/Magento/Framework/MessageQueue/Test/Unit/ConsumerTest.php @@ -6,6 +6,8 @@ namespace Magento\Framework\MessageQueue\Test\Unit; +use Magento\Framework\MessageQueue\PoisonPill\PoisonPillCompareInterface; +use Magento\Framework\MessageQueue\PoisonPill\PoisonPillReadInterface; use Magento\Framework\Phrase; /** @@ -65,6 +67,16 @@ class ConsumerTest extends \PHPUnit\Framework\TestCase */ private $consumer; + /** + * @var PoisonPillReadInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $poisonPillRead; + + /** + * @var PoisonPillCompareInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $poisonPillCompare; + /** * Set up. * @@ -85,8 +97,15 @@ protected function setUp() ->disableOriginalConstructor()->getMock(); $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->poisonPillCompare = $this->getMockBuilder(PoisonPillCompareInterface::class) + ->disableOriginalConstructor()->getMock(); + $this->poisonPillRead = $this->getMockBuilder(PoisonPillReadInterface::class) + ->disableOriginalConstructor()->getMock(); //Hard dependency used because CallbackInvoker invokes closure logic defined inside of Customer class. - $this->callbackInvoker = new \Magento\Framework\MessageQueue\CallbackInvoker(); + $this->callbackInvoker = new \Magento\Framework\MessageQueue\CallbackInvoker( + $this->poisonPillRead, + $this->poisonPillCompare + ); $this->consumer = $objectManager->getObject( \Magento\Framework\MessageQueue\Consumer::class, [ @@ -134,7 +153,8 @@ public function testProcessWithNotFoundException() $numberOfMessages = 1; $consumerName = 'consumer.name'; $exceptionPhrase = new Phrase('Exception successfully thrown'); - + $this->poisonPillRead->expects($this->atLeastOnce())->method('getLatestVersion')->willReturn('version-1'); + $this->poisonPillCompare->expects($this->atLeastOnce())->method('isLatestVersion')->willReturn(true); $queue = $this->getMockBuilder(\Magento\Framework\MessageQueue\QueueInterface::class) ->disableOriginalConstructor()->getMock(); $this->configuration->expects($this->once())->method('getQueue')->willReturn($queue); diff --git a/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php b/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php index 1cffba2543b0b..e1f6c792c9c3e 100644 --- a/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php +++ b/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php @@ -158,7 +158,7 @@ public function getCustomAttribute($attributeCode) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomAttributes(array $attributes) { @@ -166,7 +166,7 @@ public function setCustomAttributes(array $attributes) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomAttribute($attributeCode, $attributeValue) { @@ -182,7 +182,7 @@ public function setCustomAttribute($attributeCode, $attributeValue) } /** - * {@inheritdoc} + * @inheritdoc * * Added custom attributes support. */ @@ -200,7 +200,7 @@ public function setData($key, $value = null) } /** - * {@inheritdoc} + * @inheritdoc * * Unset customAttributesChanged flag */ @@ -359,17 +359,27 @@ private function populateExtensionAttributes(array $extensionAttributesData = [] /** * @inheritdoc + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + return array_diff(parent::__sleep(), ['extensionAttributesFactory', 'customAttributeFactory']); } /** * @inheritdoc + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + parent::__wakeup(); $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); $this->extensionAttributesFactory = $objectManager->get(ExtensionAttributesFactory::class); diff --git a/lib/internal/Magento/Framework/Model/AbstractModel.php b/lib/internal/Magento/Framework/Model/AbstractModel.php index 567d174938b11..f5095dbb6e872 100644 --- a/lib/internal/Magento/Framework/Model/AbstractModel.php +++ b/lib/internal/Magento/Framework/Model/AbstractModel.php @@ -219,9 +219,14 @@ protected function _init($resourceModel) * Remove unneeded properties from serialization * * @return string[] + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $properties = array_keys(get_object_vars($this)); $properties = array_diff( $properties, @@ -243,9 +248,14 @@ public function __sleep() * Init not serializable fields * * @return void + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); $this->_registry = $objectManager->get(\Magento\Framework\Registry::class); diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php index 1eaed75bcbfdd..0cadb10aaafe2 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php @@ -156,9 +156,14 @@ public function __construct( * Provide variables to serialize * * @return array + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $properties = array_keys(get_object_vars($this)); $properties = array_diff($properties, ['_resources', '_connections']); return $properties; @@ -168,9 +173,14 @@ public function __sleep() * Restore global dependencies * * @return void + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $this->_resources = \Magento\Framework\App\ObjectManager::getInstance() ->get(\Magento\Framework\App\ResourceConnection::class); } @@ -219,8 +229,10 @@ protected function _setResource($connections, $tables = null) } /** - * Set main entity table name and primary key field name - * If field name is omitted {table_name}_id will be used + * Main table setter. + * + * Set main entity table name and primary key field name. + * If field name is omitted {table_name}_id will be used. * * @param string $mainTable * @param string|null $idFieldName @@ -253,8 +265,10 @@ public function getIdFieldName() } /** + * Main table getter. + * * Returns main table name - extracted from "module/table" style and - * validated by db adapter + * validated by db adapter. * * @throws LocalizedException * @return string @@ -542,8 +556,7 @@ protected function _prepareDataForSave(\Magento\Framework\Model\AbstractModel $o } /** - * Check that model data fields that can be saved - * has really changed comparing with origData + * Check that model data fields that can be saved has really changed comparing with origData. * * @param \Magento\Framework\Model\AbstractModel $object * @return bool diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php index 8ec47ed97e11c..bc2187f474919 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php @@ -606,9 +606,14 @@ public function save() /** * @inheritdoc * @since 100.0.11 + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + return array_diff( parent::__sleep(), ['_resource', '_eventManager'] @@ -618,9 +623,14 @@ public function __sleep() /** * @inheritdoc * @since 100.0.11 + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + parent::__wakeup(); $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); $this->_eventManager = $objectManager->get(\Magento\Framework\Event\ManagerInterface::class); diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/ResourceModelPool.php b/lib/internal/Magento/Framework/Model/ResourceModel/ResourceModelPool.php deleted file mode 100644 index f62619f16e1d1..0000000000000 --- a/lib/internal/Magento/Framework/Model/ResourceModel/ResourceModelPool.php +++ /dev/null @@ -1,36 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Framework\Model\ResourceModel; - -use Magento\Framework\ObjectManagerInterface; - -/** - * Pool of resource model instances per entity - */ -class ResourceModelPool implements ResourceModelPoolInterface -{ - /** - * @var ObjectManagerInterface - */ - private $objectManager; - - /** - * @param ObjectManagerInterface $objectManager - */ - public function __construct(ObjectManagerInterface $objectManager) - { - $this->objectManager = $objectManager; - } - - /** - * @inheritdoc - */ - public function get(string $resourceClassName): AbstractResource - { - return $this->objectManager->get($resourceClassName); - } -} diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/ResourceModelPoolInterface.php b/lib/internal/Magento/Framework/Model/ResourceModel/ResourceModelPoolInterface.php deleted file mode 100644 index 0274bb6504a0c..0000000000000 --- a/lib/internal/Magento/Framework/Model/ResourceModel/ResourceModelPoolInterface.php +++ /dev/null @@ -1,23 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Framework\Model\ResourceModel; - -/** - * Pool of resource model instances per entity - * - * @api - */ -interface ResourceModelPoolInterface -{ - /** - * Return instance for given class name from pool. - * - * @param string $resourceClassName - * @return AbstractResource - */ - public function get(string $resourceClassName): AbstractResource; -} diff --git a/lib/internal/Magento/Framework/Mview/Config/Data/Proxy.php b/lib/internal/Magento/Framework/Mview/Config/Data/Proxy.php index a83e9507bda04..d67c380207554 100644 --- a/lib/internal/Magento/Framework/Mview/Config/Data/Proxy.php +++ b/lib/internal/Magento/Framework/Mview/Config/Data/Proxy.php @@ -55,10 +55,17 @@ public function __construct( } /** + * Remove links to objects. + * * @return array + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + return ['subject', 'isShared']; } @@ -66,9 +73,14 @@ public function __sleep() * Retrieve ObjectManager from global scope * * @return void + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $this->objectManager = \Magento\Framework\App\ObjectManager::getInstance(); } @@ -100,7 +112,7 @@ protected function _getSubject() } /** - * {@inheritdoc} + * @inheritdoc */ public function merge(array $config) { @@ -108,7 +120,7 @@ public function merge(array $config) } /** - * {@inheritdoc} + * @inheritdoc */ public function get($path = null, $default = null) { diff --git a/lib/internal/Magento/Framework/Setup/Patch/DependentPatchInterface.php b/lib/internal/Magento/Framework/Setup/Patch/DependentPatchInterface.php index abda94a0e6f8e..7573441fa9466 100644 --- a/lib/internal/Magento/Framework/Setup/Patch/DependentPatchInterface.php +++ b/lib/internal/Magento/Framework/Setup/Patch/DependentPatchInterface.php @@ -6,7 +6,7 @@ namespace Magento\Framework\Setup\Patch; /** - * Each patch can have dependecies, that should be applied before such patch + * Each patch can have dependencies, that should be applied before such patch * * / Patch2 --- Patch3 * / @@ -20,7 +20,7 @@ interface DependentPatchInterface /** * Get array of patches that have to be executed prior to this. * - * example of implementation: + * Example of implementation: * * [ * \Vendor_Name\Module_Name\Setup\Patch\Patch1::class, diff --git a/lib/internal/Magento/Framework/Setup/Test/Unit/Patch/PatchApplierTest.php b/lib/internal/Magento/Framework/Setup/Test/Unit/Patch/PatchApplierTest.php index f89bdc9e137dd..cb40845bcc488 100644 --- a/lib/internal/Magento/Framework/Setup/Test/Unit/Patch/PatchApplierTest.php +++ b/lib/internal/Magento/Framework/Setup/Test/Unit/Patch/PatchApplierTest.php @@ -91,7 +91,7 @@ class PatchApplierTest extends \PHPUnit\Framework\TestCase /** * @var PatchBackwardCompatability |\PHPUnit_Framework_MockObject_MockObject */ - private $patchBacwardCompatability; + private $patchBackwardCompatability; protected function setUp() { @@ -109,7 +109,7 @@ protected function setUp() $this->moduleDataSetupMock->expects($this->any())->method('getConnection')->willReturn($this->connectionMock); $objectManager = new ObjectManager($this); - $this->patchBacwardCompatability = $objectManager->getObject( + $this->patchBackwardCompatability = $objectManager->getObject( PatchBackwardCompatability::class, [ 'moduleResource' => $this->moduleResourceMock @@ -128,7 +128,7 @@ protected function setUp() 'objectManager' => $this->objectManagerMock, 'schemaSetup' => $this->schemaSetupMock, 'moduleDataSetup' => $this->moduleDataSetupMock, - 'patchBackwardCompatability' => $this->patchBacwardCompatability + 'patchBackwardCompatability' => $this->patchBackwardCompatability ] ); require_once __DIR__ . '/../_files/data_patch_classes.php'; diff --git a/lib/internal/Magento/Framework/Test/Unit/Data/CollectionTest.php b/lib/internal/Magento/Framework/Test/Unit/Data/CollectionTest.php index 75f8b1cd0633f..913d5a577e62a 100644 --- a/lib/internal/Magento/Framework/Test/Unit/Data/CollectionTest.php +++ b/lib/internal/Magento/Framework/Test/Unit/Data/CollectionTest.php @@ -57,6 +57,27 @@ public function testWalk() ); } + /** + * Ensure that getSize works correctly with clear + * + */ + public function testClearTotalRecords() + { + $objOne = new \Magento\Framework\DataObject(['id' => 1, 'name' => 'one']); + $objTwo = new \Magento\Framework\DataObject(['id' => 2, 'name' => 'two']); + $objThree = new \Magento\Framework\DataObject(['id' => 3, 'name' => 'three']); + + /** @noinspection PhpUnhandledExceptionInspection */ + $this->collection->addItem($objOne); + /** @noinspection PhpUnhandledExceptionInspection */ + $this->collection->addItem($objTwo); + /** @noinspection PhpUnhandledExceptionInspection */ + $this->collection->addItem($objThree); + $this->assertEquals(3, $this->collection->getSize()); + $this->collection->clear(); + $this->assertEquals(0, $this->collection->getSize()); + } + /** * Callback function. * diff --git a/lib/internal/Magento/Framework/Translate/Inline/Proxy.php b/lib/internal/Magento/Framework/Translate/Inline/Proxy.php index 370a88d6d9a43..e6d6cc57c2b09 100644 --- a/lib/internal/Magento/Framework/Translate/Inline/Proxy.php +++ b/lib/internal/Magento/Framework/Translate/Inline/Proxy.php @@ -55,10 +55,17 @@ public function __construct( } /** + * Remove links to other objects. + * * @return array + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + return ['subject', 'isShared']; } @@ -66,9 +73,14 @@ public function __sleep() * Retrieve ObjectManager from global scope * * @return void + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $this->objectManager = \Magento\Framework\App\ObjectManager::getInstance(); } diff --git a/lib/internal/Magento/Framework/Validator/Factory.php b/lib/internal/Magento/Framework/Validator/Factory.php index f2089c662e955..2a296f7cdcb24 100644 --- a/lib/internal/Magento/Framework/Validator/Factory.php +++ b/lib/internal/Magento/Framework/Validator/Factory.php @@ -6,22 +6,33 @@ namespace Magento\Framework\Validator; +use Magento\Framework\Module\Dir\Reader; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Phrase; +use Magento\Framework\Validator; use Magento\Framework\Cache\FrontendInterface; +/** + * Factory for \Magento\Framework\Validator and \Magento\Framework\Validator\Builder. + */ class Factory { - /** cache key */ + /** + * cache key + * + * @deprecated + */ const CACHE_KEY = __CLASS__; /** - * @var \Magento\Framework\ObjectManagerInterface + * @var ObjectManagerInterface */ protected $_objectManager; /** * Validator config files * - * @var array|null + * @var iterable|null */ protected $_configFiles = null; @@ -31,40 +42,25 @@ class Factory private $isDefaultTranslatorInitialized = false; /** - * @var \Magento\Framework\Module\Dir\Reader + * @var Reader */ private $moduleReader; - /** - * @var FrontendInterface - */ - private $cache; - - /** - * @var \Magento\Framework\Serialize\SerializerInterface - */ - private $serializer; - - /** - * @var \Magento\Framework\Config\FileIteratorFactory - */ - private $fileIteratorFactory; - /** * Initialize dependencies * - * @param \Magento\Framework\ObjectManagerInterface $objectManager - * @param \Magento\Framework\Module\Dir\Reader $moduleReader - * @param FrontendInterface $cache + * @param ObjectManagerInterface $objectManager + * @param Reader $moduleReader + * @param FrontendInterface $cache @deprecated + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( - \Magento\Framework\ObjectManagerInterface $objectManager, - \Magento\Framework\Module\Dir\Reader $moduleReader, + ObjectManagerInterface $objectManager, + Reader $moduleReader, FrontendInterface $cache ) { $this->_objectManager = $objectManager; $this->moduleReader = $moduleReader; - $this->cache = $cache; } /** @@ -75,17 +71,7 @@ public function __construct( protected function _initializeConfigList() { if (!$this->_configFiles) { - $this->_configFiles = $this->cache->load(self::CACHE_KEY); - if (!$this->_configFiles) { - $this->_configFiles = $this->moduleReader->getConfigurationFiles('validation.xml'); - $this->cache->save( - $this->getSerializer()->serialize($this->_configFiles->toArray()), - self::CACHE_KEY - ); - } else { - $filesArray = $this->getSerializer()->unserialize($this->_configFiles); - $this->_configFiles = $this->getFileIteratorFactory()->create(array_keys($filesArray)); - } + $this->_configFiles = $this->moduleReader->getConfigurationFiles('validation.xml'); } } @@ -93,6 +79,7 @@ protected function _initializeConfigList() * Create and set default translator to \Magento\Framework\Validator\AbstractValidator. * * @return void + * @throws \Zend_Translate_Exception */ protected function _initializeDefaultTranslator() { @@ -100,7 +87,7 @@ protected function _initializeDefaultTranslator() // Pass translations to \Magento\Framework\TranslateInterface from validators $translatorCallback = function () { $argc = func_get_args(); - return (string)new \Magento\Framework\Phrase(array_shift($argc), $argc); + return (string)new Phrase(array_shift($argc), $argc); }; /** @var \Magento\Framework\Translate\Adapter $translator */ $translator = $this->_objectManager->create(\Magento\Framework\Translate\Adapter::class); @@ -115,14 +102,15 @@ protected function _initializeDefaultTranslator() * * Will instantiate \Magento\Framework\Validator\Config * - * @return \Magento\Framework\Validator\Config + * @return Config + * @throws \Zend_Translate_Exception */ public function getValidatorConfig() { $this->_initializeConfigList(); $this->_initializeDefaultTranslator(); return $this->_objectManager->create( - \Magento\Framework\Validator\Config::class, + Config::class, ['configFiles' => $this->_configFiles] ); } @@ -133,7 +121,8 @@ public function getValidatorConfig() * @param string $entityName * @param string $groupName * @param array|null $builderConfig - * @return \Magento\Framework\Validator\Builder + * @return Builder + * @throws \Zend_Translate_Exception */ public function createValidatorBuilder($entityName, $groupName, array $builderConfig = null) { @@ -147,43 +136,12 @@ public function createValidatorBuilder($entityName, $groupName, array $builderCo * @param string $entityName * @param string $groupName * @param array|null $builderConfig - * @return \Magento\Framework\Validator + * @return Validator + * @throws \Zend_Translate_Exception */ public function createValidator($entityName, $groupName, array $builderConfig = null) { $this->_initializeDefaultTranslator(); return $this->getValidatorConfig()->createValidator($entityName, $groupName, $builderConfig); } - - /** - * Get serializer - * - * @return \Magento\Framework\Serialize\SerializerInterface - * @deprecated 100.2.0 - */ - private function getSerializer() - { - if ($this->serializer === null) { - $this->serializer = $this->_objectManager->get( - \Magento\Framework\Serialize\SerializerInterface::class - ); - } - return $this->serializer; - } - - /** - * Get file iterator factory - * - * @return \Magento\Framework\Config\FileIteratorFactory - * @deprecated 100.2.0 - */ - private function getFileIteratorFactory() - { - if ($this->fileIteratorFactory === null) { - $this->fileIteratorFactory = $this->_objectManager->get( - \Magento\Framework\Config\FileIteratorFactory::class - ); - } - return $this->fileIteratorFactory; - } } diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/FactoryTest.php b/lib/internal/Magento/Framework/Validator/Test/Unit/FactoryTest.php index 5511627c6dcc3..73a8c95c9a2ff 100644 --- a/lib/internal/Magento/Framework/Validator/Test/Unit/FactoryTest.php +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/FactoryTest.php @@ -25,21 +25,6 @@ class FactoryTest extends \PHPUnit\Framework\TestCase */ private $validatorConfigMock; - /** - * @var \Magento\Framework\Cache\FrontendInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $cacheMock; - - /** - * @var \Magento\Framework\Serialize\SerializerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $serializerMock; - - /** - * @var \Magento\Framework\Config\FileIteratorFactory|\PHPUnit_Framework_MockObject_MockObject - */ - private $fileIteratorFactoryMock; - /** * @var \Magento\Framework\Config\FileIterator|\PHPUnit_Framework_MockObject_MockObject */ @@ -55,11 +40,6 @@ class FactoryTest extends \PHPUnit\Framework\TestCase */ private $factory; - /** - * @var string - */ - private $jsonString = '["\/tmp\/moduleOne\/etc\/validation.xml"]'; - /** * @var array */ @@ -99,23 +79,9 @@ protected function setUp() \Magento\Framework\Validator\Factory::class, [ 'objectManager' => $this->objectManagerMock, - 'moduleReader' => $this->readerMock, - 'cache' => $this->cacheMock + 'moduleReader' => $this->readerMock ] ); - - $this->serializerMock = $this->createMock(\Magento\Framework\Serialize\SerializerInterface::class); - $this->fileIteratorFactoryMock = $this->createMock(\Magento\Framework\Config\FileIteratorFactory::class); - $objectManager->setBackwardCompatibleProperty( - $this->factory, - 'serializer', - $this->serializerMock - ); - $objectManager->setBackwardCompatibleProperty( - $this->factory, - 'fileIteratorFactory', - $this->fileIteratorFactoryMock - ); } /** @@ -147,46 +113,6 @@ public function testGetValidatorConfig() ); } - public function testGetValidatorConfigCacheNotExist() - { - $this->cacheMock->expects($this->once()) - ->method('load') - ->willReturn(false); - $this->readerMock->expects($this->once()) - ->method('getConfigurationFiles') - ->willReturn($this->fileIteratorMock); - $this->fileIteratorMock->method('toArray') - ->willReturn($this->data); - $this->cacheMock->expects($this->once()) - ->method('save') - ->with($this->jsonString); - $this->serializerMock->expects($this->once()) - ->method('serialize') - ->with($this->data) - ->willReturn($this->jsonString); - $this->factory->getValidatorConfig(); - $this->factory->getValidatorConfig(); - } - - public function testGetValidatorConfigCacheExist() - { - $this->cacheMock->expects($this->once()) - ->method('load') - ->willReturn($this->jsonString); - $this->readerMock->expects($this->never()) - ->method('getConfigurationFiles'); - $this->cacheMock->expects($this->never()) - ->method('save'); - $this->serializerMock->expects($this->once()) - ->method('unserialize') - ->with($this->jsonString) - ->willReturn($this->data); - $this->fileIteratorFactoryMock->method('create') - ->willReturn($this->fileIteratorMock); - $this->factory->getValidatorConfig(); - $this->factory->getValidatorConfig(); - } - public function testCreateValidatorBuilder() { $this->readerMock->method('getConfigurationFiles') diff --git a/lib/internal/Magento/Framework/View/Asset/MergeStrategy/Direct.php b/lib/internal/Magento/Framework/View/Asset/MergeStrategy/Direct.php index 1e8eb74bf5414..315c3ee204864 100644 --- a/lib/internal/Magento/Framework/View/Asset/MergeStrategy/Direct.php +++ b/lib/internal/Magento/Framework/View/Asset/MergeStrategy/Direct.php @@ -6,6 +6,8 @@ namespace Magento\Framework\View\Asset\MergeStrategy; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Math\Random; use Magento\Framework\View\Asset; /** @@ -30,38 +32,46 @@ class Direct implements \Magento\Framework\View\Asset\MergeStrategyInterface */ private $cssUrlResolver; + /** + * @var Random + */ + private $mathRandom; + /** * @param \Magento\Framework\Filesystem $filesystem * @param \Magento\Framework\View\Url\CssResolver $cssUrlResolver + * @param Random|null $mathRandom */ public function __construct( \Magento\Framework\Filesystem $filesystem, - \Magento\Framework\View\Url\CssResolver $cssUrlResolver + \Magento\Framework\View\Url\CssResolver $cssUrlResolver, + Random $mathRandom = null ) { $this->filesystem = $filesystem; $this->cssUrlResolver = $cssUrlResolver; + $this->mathRandom = $mathRandom ?: ObjectManager::getInstance()->get(Random::class); } /** - * {@inheritdoc} + * @inheritdoc */ public function merge(array $assetsToMerge, Asset\LocalInterface $resultAsset) { $mergedContent = $this->composeMergedContent($assetsToMerge, $resultAsset); $filePath = $resultAsset->getPath(); + $tmpFilePath = $filePath . $this->mathRandom->getUniqueHash('_'); $staticDir = $this->filesystem->getDirectoryWrite(DirectoryList::STATIC_VIEW); $tmpDir = $this->filesystem->getDirectoryWrite(DirectoryList::TMP); - $tmpDir->writeFile($filePath, $mergedContent); - $tmpDir->renameFile($filePath, $filePath, $staticDir); + $tmpDir->writeFile($tmpFilePath, $mergedContent); + $tmpDir->renameFile($tmpFilePath, $filePath, $staticDir); } /** * Merge files together and modify content if needed * - * @param \Magento\Framework\View\Asset\MergeableInterface[] $assetsToMerge - * @param \Magento\Framework\View\Asset\LocalInterface $resultAsset - * @return string - * @throws \Magento\Framework\Exception\LocalizedException + * @param array $assetsToMerge + * @param Asset\LocalInterface $resultAsset + * @return array|string */ private function composeMergedContent(array $assetsToMerge, Asset\LocalInterface $resultAsset) { diff --git a/lib/internal/Magento/Framework/View/Layout/Argument/Interpreter/DataObject.php b/lib/internal/Magento/Framework/View/Layout/Argument/Interpreter/DataObject.php index ea9fef369e312..bf607672cae03 100644 --- a/lib/internal/Magento/Framework/View/Layout/Argument/Interpreter/DataObject.php +++ b/lib/internal/Magento/Framework/View/Layout/Argument/Interpreter/DataObject.php @@ -3,10 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\View\Layout\Argument\Interpreter; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Data\Argument\InterpreterInterface; use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Stdlib\BooleanUtils; /** * Interpreter that instantiates object by a class name @@ -14,43 +18,54 @@ class DataObject implements InterpreterInterface { /** - * @var ObjectManagerInterface + * @var \Magento\Framework\ObjectManagerInterface */ private $objectManager; + /** + * @var \Magento\Framework\Stdlib\BooleanUtils + */ + private $booleanUtils; + /** * @var string|null */ private $expectedClass; /** - * @param ObjectManagerInterface $objectManager + * @param \Magento\Framework\ObjectManagerInterface $objectManager * @param string|null $expectedClass + * @param \Magento\Framework\Stdlib\BooleanUtils|null $booleanUtils */ - public function __construct(ObjectManagerInterface $objectManager, $expectedClass = null) - { + public function __construct( + ObjectManagerInterface $objectManager, + ?string $expectedClass = null, + ?BooleanUtils $booleanUtils = null + ) { $this->objectManager = $objectManager; $this->expectedClass = $expectedClass; + $this->booleanUtils = $booleanUtils ?? ObjectManager::getInstance()->get(BooleanUtils::class); } /** - * {@inheritdoc} - * @return object - * @throws \InvalidArgumentException - * @throws \UnexpectedValueException + * @inheritdoc */ public function evaluate(array $data) { if (!isset($data['value'])) { throw new \InvalidArgumentException('Object class name is missing.'); } + + $shared = isset($data['shared']) ? $this->booleanUtils->toBoolean($data['shared']) : true; $className = $data['value']; - $result = $this->objectManager->create($className); + $result = $shared ? $this->objectManager->get($className) : $this->objectManager->create($className); + if ($this->expectedClass && !$result instanceof $this->expectedClass) { throw new \UnexpectedValueException( - sprintf("Instance of %s is expected, got %s instead.", $this->expectedClass, get_class($result)) + \sprintf('Instance of %s is expected, got %s instead.', $this->expectedClass, \get_class($result)) ); } + return $result; } } diff --git a/lib/internal/Magento/Framework/View/Layout/GeneratorPool.php b/lib/internal/Magento/Framework/View/Layout/GeneratorPool.php index 266a1f873f4b7..a585eda37df68 100644 --- a/lib/internal/Magento/Framework/View/Layout/GeneratorPool.php +++ b/lib/internal/Magento/Framework/View/Layout/GeneratorPool.php @@ -37,7 +37,7 @@ class GeneratorPool * @param ScheduledStructure\Helper $helper * @param ConditionFactory $conditionFactory * @param \Psr\Log\LoggerInterface $logger - * @param array $generators + * @param array|null $generators */ public function __construct( ScheduledStructure\Helper $helper, @@ -67,7 +67,7 @@ public function getGenerator($type) } /** - * Traverse through all generators and generate all scheduled elements + * Traverse through all generators and generate all scheduled elements. * * @param Reader\Context $readerContext * @param Generator\Context $generatorContext @@ -86,11 +86,17 @@ public function process(Reader\Context $readerContext, Generator\Context $genera * Add generators to pool * * @param GeneratorInterface[] $generators + * * @return void */ protected function addGenerators(array $generators) { foreach ($generators as $generator) { + if (!$generator instanceof GeneratorInterface) { + throw new \InvalidArgumentException( + sprintf('Generator class must be an instance of %s', GeneratorInterface::class) + ); + } $this->generators[$generator->getType()] = $generator; } } @@ -131,9 +137,9 @@ protected function buildStructure(ScheduledStructure $scheduledStructure, Data\S } /** - * Reorder a child of a specified element + * Reorder a child of a specified element. * - * @param ScheduledStructure $scheduledStructure, + * @param ScheduledStructure $scheduledStructure * @param Data\Structure $structure * @param string $elementName * @return void @@ -227,6 +233,8 @@ protected function moveElementInStructure( } /** + * Check visibility conditions exists in data. + * * @param array $data * * @return bool diff --git a/lib/internal/Magento/Framework/View/Layout/Proxy.php b/lib/internal/Magento/Framework/View/Layout/Proxy.php index 03020307c5380..a3d89c6ec7a8e 100644 --- a/lib/internal/Magento/Framework/View/Layout/Proxy.php +++ b/lib/internal/Magento/Framework/View/Layout/Proxy.php @@ -57,10 +57,17 @@ public function __construct( } /** + * Remove links to objects. + * * @return array + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __sleep() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + return ['_subject', '_isShared']; } @@ -68,9 +75,14 @@ public function __sleep() * Retrieve ObjectManager from global scope * * @return void + * + * @SuppressWarnings(PHPMD.SerializationAware) + * @deprecated Do not use PHP serialization. */ public function __wakeup() { + trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED); + $this->_objectManager = \Magento\Framework\App\ObjectManager::getInstance(); } @@ -100,7 +112,7 @@ protected function _getSubject() } /** - * {@inheritdoc} + * @inheritdoc */ public function setGeneratorPool(\Magento\Framework\View\Layout\GeneratorPool $generatorPool) { @@ -108,7 +120,7 @@ public function setGeneratorPool(\Magento\Framework\View\Layout\GeneratorPool $g } /** - * {@inheritdoc} + * @inheritdoc */ public function setBuilder(\Magento\Framework\View\Layout\BuilderInterface $builder) { @@ -116,7 +128,7 @@ public function setBuilder(\Magento\Framework\View\Layout\BuilderInterface $buil } /** - * {@inheritdoc} + * @inheritdoc */ public function publicBuild() { @@ -124,7 +136,7 @@ public function publicBuild() } /** - * {@inheritdoc} + * @inheritdoc */ public function getUpdate() { @@ -132,7 +144,7 @@ public function getUpdate() } /** - * {@inheritdoc} + * @inheritdoc */ public function generateXml() { @@ -140,7 +152,7 @@ public function generateXml() } /** - * {@inheritdoc} + * @inheritdoc */ public function generateElements() { @@ -148,7 +160,7 @@ public function generateElements() } /** - * {@inheritdoc} + * @inheritdoc */ public function getChildBlock($parentName, $alias) { @@ -156,7 +168,7 @@ public function getChildBlock($parentName, $alias) } /** - * {@inheritdoc} + * @inheritdoc */ public function setChild($parentName, $elementName, $alias) { @@ -164,7 +176,7 @@ public function setChild($parentName, $elementName, $alias) } /** - * {@inheritdoc} + * @inheritdoc */ public function reorderChild($parentName, $childName, $offsetOrSibling, $after = true) { @@ -172,7 +184,7 @@ public function reorderChild($parentName, $childName, $offsetOrSibling, $after = } /** - * {@inheritdoc} + * @inheritdoc */ public function unsetChild($parentName, $alias) { @@ -180,7 +192,7 @@ public function unsetChild($parentName, $alias) } /** - * {@inheritdoc} + * @inheritdoc */ public function getChildNames($parentName) { @@ -188,7 +200,7 @@ public function getChildNames($parentName) } /** - * {@inheritdoc} + * @inheritdoc */ public function getChildBlocks($parentName) { @@ -196,7 +208,7 @@ public function getChildBlocks($parentName) } /** - * {@inheritdoc} + * @inheritdoc */ public function getChildName($parentName, $alias) { @@ -204,7 +216,7 @@ public function getChildName($parentName, $alias) } /** - * {@inheritdoc} + * @inheritdoc */ public function renderElement($name, $useCache = true) { @@ -212,7 +224,7 @@ public function renderElement($name, $useCache = true) } /** - * {@inheritdoc} + * @inheritdoc */ public function renderNonCachedElement($name) { @@ -220,7 +232,7 @@ public function renderNonCachedElement($name) } /** - * {@inheritdoc} + * @inheritdoc */ public function addToParentGroup($blockName, $parentGroupName) { @@ -228,7 +240,7 @@ public function addToParentGroup($blockName, $parentGroupName) } /** - * {@inheritdoc} + * @inheritdoc */ public function getGroupChildNames($blockName, $groupName) { @@ -236,7 +248,7 @@ public function getGroupChildNames($blockName, $groupName) } /** - * {@inheritdoc} + * @inheritdoc */ public function hasElement($name) { @@ -244,7 +256,7 @@ public function hasElement($name) } /** - * {@inheritdoc} + * @inheritdoc */ public function getElementProperty($name, $attribute) { @@ -252,7 +264,7 @@ public function getElementProperty($name, $attribute) } /** - * {@inheritdoc} + * @inheritdoc */ public function isBlock($name) { @@ -260,7 +272,7 @@ public function isBlock($name) } /** - * {@inheritdoc} + * @inheritdoc */ public function isUiComponent($name) { @@ -268,7 +280,7 @@ public function isUiComponent($name) } /** - * {@inheritdoc} + * @inheritdoc */ public function isContainer($name) { @@ -276,7 +288,7 @@ public function isContainer($name) } /** - * {@inheritdoc} + * @inheritdoc */ public function isManipulationAllowed($name) { @@ -284,7 +296,7 @@ public function isManipulationAllowed($name) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBlock($name, $block) { @@ -292,7 +304,7 @@ public function setBlock($name, $block) } /** - * {@inheritdoc} + * @inheritdoc */ public function unsetElement($name) { @@ -300,7 +312,7 @@ public function unsetElement($name) } /** - * {@inheritdoc} + * @inheritdoc */ public function createBlock($type, $name = '', array $arguments = []) { @@ -308,7 +320,7 @@ public function createBlock($type, $name = '', array $arguments = []) } /** - * {@inheritdoc} + * @inheritdoc */ public function addBlock($block, $name = '', $parent = '', $alias = '') { @@ -316,7 +328,7 @@ public function addBlock($block, $name = '', $parent = '', $alias = '') } /** - * {@inheritdoc} + * @inheritdoc */ public function addContainer($name, $label, array $options = [], $parent = '', $alias = '') { @@ -324,7 +336,7 @@ public function addContainer($name, $label, array $options = [], $parent = '', $ } /** - * {@inheritdoc} + * @inheritdoc */ public function renameElement($oldName, $newName) { @@ -332,7 +344,7 @@ public function renameElement($oldName, $newName) } /** - * {@inheritdoc} + * @inheritdoc */ public function getAllBlocks() { @@ -340,7 +352,7 @@ public function getAllBlocks() } /** - * {@inheritdoc} + * @inheritdoc */ public function getBlock($name) { @@ -348,7 +360,7 @@ public function getBlock($name) } /** - * {@inheritdoc} + * @inheritdoc */ public function getUiComponent($name) { @@ -356,7 +368,7 @@ public function getUiComponent($name) } /** - * {@inheritdoc} + * @inheritdoc */ public function getParentName($childName) { @@ -364,7 +376,7 @@ public function getParentName($childName) } /** - * {@inheritdoc} + * @inheritdoc */ public function getElementAlias($name) { @@ -372,7 +384,7 @@ public function getElementAlias($name) } /** - * {@inheritdoc} + * @inheritdoc */ public function addOutputElement($name) { @@ -380,7 +392,7 @@ public function addOutputElement($name) } /** - * {@inheritdoc} + * @inheritdoc */ public function removeOutputElement($name) { @@ -388,7 +400,7 @@ public function removeOutputElement($name) } /** - * {@inheritdoc} + * @inheritdoc */ public function getOutput() { @@ -396,7 +408,7 @@ public function getOutput() } /** - * {@inheritdoc} + * @inheritdoc */ public function getMessagesBlock() { @@ -404,7 +416,7 @@ public function getMessagesBlock() } /** - * {@inheritdoc} + * @inheritdoc */ public function getBlockSingleton($type) { @@ -412,7 +424,7 @@ public function getBlockSingleton($type) } /** - * {@inheritdoc} + * @inheritdoc */ public function addAdjustableRenderer($namespace, $staticType, $dynamicType, $type, $template, $data = []) { @@ -427,7 +439,7 @@ public function addAdjustableRenderer($namespace, $staticType, $dynamicType, $ty } /** - * {@inheritdoc} + * @inheritdoc */ public function getRendererOptions($namespace, $staticType, $dynamicType) { @@ -435,7 +447,7 @@ public function getRendererOptions($namespace, $staticType, $dynamicType) } /** - * {@inheritdoc} + * @inheritdoc */ public function executeRenderer($namespace, $staticType, $dynamicType, $data = []) { @@ -443,7 +455,7 @@ public function executeRenderer($namespace, $staticType, $dynamicType, $data = [ } /** - * {@inheritdoc} + * @inheritdoc */ public function initMessages($messageGroups = []) { @@ -451,7 +463,7 @@ public function initMessages($messageGroups = []) } /** - * {@inheritdoc} + * @inheritdoc */ public function isCacheable() { @@ -459,7 +471,7 @@ public function isCacheable() } /** - * {@inheritdoc} + * @inheritdoc */ public function isPrivate() { @@ -467,7 +479,7 @@ public function isPrivate() } /** - * {@inheritdoc} + * @inheritdoc */ public function setIsPrivate($isPrivate = true) { @@ -475,7 +487,7 @@ public function setIsPrivate($isPrivate = true) } /** - * {@inheritdoc} + * @inheritdoc */ public function getReaderContext() { @@ -483,7 +495,7 @@ public function getReaderContext() } /** - * {@inheritdoc} + * @inheritdoc */ public function setXml(\Magento\Framework\Simplexml\Element $node) { @@ -491,7 +503,7 @@ public function setXml(\Magento\Framework\Simplexml\Element $node) } /** - * {@inheritdoc} + * @inheritdoc */ public function getNode($path = null) { @@ -499,7 +511,7 @@ public function getNode($path = null) } /** - * {@inheritdoc} + * @inheritdoc */ public function getXpath($xpath) { @@ -507,7 +519,7 @@ public function getXpath($xpath) } /** - * {@inheritdoc} + * @inheritdoc */ public function getXmlString() { @@ -515,7 +527,7 @@ public function getXmlString() } /** - * {@inheritdoc} + * @inheritdoc */ public function loadFile($filePath) { @@ -523,7 +535,7 @@ public function loadFile($filePath) } /** - * {@inheritdoc} + * @inheritdoc */ public function loadString($string) { @@ -531,7 +543,7 @@ public function loadString($string) } /** - * {@inheritdoc} + * @inheritdoc */ public function loadDom(\DOMNode $dom) { @@ -539,7 +551,7 @@ public function loadDom(\DOMNode $dom) } /** - * {@inheritdoc} + * @inheritdoc */ public function setNode($path, $value, $overwrite = true) { @@ -547,7 +559,7 @@ public function setNode($path, $value, $overwrite = true) } /** - * {@inheritdoc} + * @inheritdoc */ public function applyExtends() { @@ -555,7 +567,7 @@ public function applyExtends() } /** - * {@inheritdoc} + * @inheritdoc */ public function processFileData($text) { @@ -563,7 +575,7 @@ public function processFileData($text) } /** - * {@inheritdoc} + * @inheritdoc */ public function extend(\Magento\Framework\Simplexml\Config $config, $overwrite = true) { diff --git a/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd b/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd index 6486b39070788..17857c9ab0658 100755 --- a/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd +++ b/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd @@ -17,6 +17,13 @@ </xs:extension> </xs:complexContent> </xs:complexType> + <xs:complexType name="object"> + <xs:complexContent> + <xs:extension base="argumentType"> + <xs:attribute name="shared" use="optional" type="xs:boolean"/> + </xs:extension> + </xs:complexContent> + </xs:complexType> </xs:redefine> <!-- Defined the types of elements --> diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/MergeStrategy/DirectTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/MergeStrategy/DirectTest.php index 823ad5c8c8be7..c23f13745c79f 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/MergeStrategy/DirectTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/MergeStrategy/DirectTest.php @@ -5,13 +5,19 @@ */ namespace Magento\Framework\View\Test\Unit\Asset\MergeStrategy; -use Magento\Framework\Filesystem\Directory\WriteInterface; -use \Magento\Framework\View\Asset\MergeStrategy\Direct; - use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem\Directory\WriteInterface; +use Magento\Framework\View\Asset\MergeStrategy\Direct; +/** + * Test for Magento\Framework\View\Asset\MergeStrategy\Direct. + */ class DirectTest extends \PHPUnit\Framework\TestCase { + /** + * @var \Magento\Framework\Math\Random|\PHPUnit_Framework_MockObject_MockObject + */ + protected $mathRandomMock; /** * @var \Magento\Framework\View\Asset\MergeStrategy\Direct */ @@ -50,33 +56,47 @@ protected function setUp() [DirectoryList::TMP, \Magento\Framework\Filesystem\DriverPool::FILE, $this->tmpDir], ]); $this->resultAsset = $this->createMock(\Magento\Framework\View\Asset\File::class); - $this->object = new Direct($filesystem, $this->cssUrlResolver); + $this->mathRandomMock = $this->getMockBuilder(\Magento\Framework\Math\Random::class) + ->disableOriginalConstructor() + ->getMock(); + $this->object = new Direct($filesystem, $this->cssUrlResolver, $this->mathRandomMock); } public function testMergeNoAssets() { + $uniqId = '_b3bf82fa6e140594420fa90982a8e877'; $this->resultAsset->expects($this->once())->method('getPath')->will($this->returnValue('foo/result')); $this->staticDir->expects($this->never())->method('writeFile'); - $this->tmpDir->expects($this->once())->method('writeFile')->with('foo/result', ''); - $this->tmpDir->expects($this->once())->method('renameFile')->with('foo/result', 'foo/result', $this->staticDir); + $this->mathRandomMock->expects($this->once()) + ->method('getUniqueHash') + ->willReturn($uniqId); + $this->tmpDir->expects($this->once())->method('writeFile')->with('foo/result' . $uniqId, ''); + $this->tmpDir->expects($this->once())->method('renameFile') + ->with('foo/result' . $uniqId, 'foo/result', $this->staticDir); $this->object->merge([], $this->resultAsset); } public function testMergeGeneric() { + $uniqId = '_be50ccf992fd81818c1a2645d1a29e92'; $this->resultAsset->expects($this->once())->method('getPath')->will($this->returnValue('foo/result')); $assets = $this->prepareAssetsToMerge([' one', 'two']); // note leading space intentionally $this->staticDir->expects($this->never())->method('writeFile'); - $this->tmpDir->expects($this->once())->method('writeFile')->with('foo/result', 'onetwo'); - $this->tmpDir->expects($this->once())->method('renameFile')->with('foo/result', 'foo/result', $this->staticDir); + $this->mathRandomMock->expects($this->once()) + ->method('getUniqueHash') + ->willReturn($uniqId); + $this->tmpDir->expects($this->once())->method('writeFile')->with('foo/result' . $uniqId, 'onetwo'); + $this->tmpDir->expects($this->once())->method('renameFile') + ->with('foo/result' . $uniqId, 'foo/result', $this->staticDir); $this->object->merge($assets, $this->resultAsset); } public function testMergeCss() { + $uniqId = '_f929c374767e00712449660ea673f2f5'; $this->resultAsset->expects($this->exactly(3)) ->method('getPath') - ->will($this->returnValue('foo/result')); + ->willReturn('foo/result'); $this->resultAsset->expects($this->any())->method('getContentType')->will($this->returnValue('css')); $assets = $this->prepareAssetsToMerge(['one', 'two']); $this->cssUrlResolver->expects($this->exactly(2)) @@ -85,10 +105,14 @@ public function testMergeCss() $this->cssUrlResolver->expects($this->once()) ->method('aggregateImportDirectives') ->with('12') - ->will($this->returnValue('1020')); + ->willReturn('1020'); + $this->mathRandomMock->expects($this->once()) + ->method('getUniqueHash') + ->willReturn($uniqId); $this->staticDir->expects($this->never())->method('writeFile'); - $this->tmpDir->expects($this->once())->method('writeFile')->with('foo/result', '1020'); - $this->tmpDir->expects($this->once())->method('renameFile')->with('foo/result', 'foo/result', $this->staticDir); + $this->tmpDir->expects($this->once())->method('writeFile')->with('foo/result' . $uniqId, '1020'); + $this->tmpDir->expects($this->once())->method('renameFile') + ->with('foo/result' . $uniqId, 'foo/result', $this->staticDir); $this->object->merge($assets, $this->resultAsset); } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Layout/Argument/Interpreter/ObjectTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Layout/Argument/Interpreter/ObjectTest.php index 7cc280a930d9c..6e3ba94de507d 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Layout/Argument/Interpreter/ObjectTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Layout/Argument/Interpreter/ObjectTest.php @@ -3,10 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\View\Test\Unit\Layout\Argument\Interpreter; -use \Magento\Framework\View\Layout\Argument\Interpreter\DataObject; +use Magento\Framework\View\Layout\Argument\Interpreter\DataObject; +/** + * Tests layout argument interpreter data object. + */ class ObjectTest extends \PHPUnit\Framework\TestCase { const EXPECTED_CLASS = \Magento\Framework\View\Test\Unit\Layout\Argument\Interpreter\ObjectTest::class; @@ -22,19 +27,55 @@ class ObjectTest extends \PHPUnit\Framework\TestCase protected $_interpreter; /** - * @var DataObject + * @var \Magento\Framework\Stdlib\BooleanUtils + */ + protected $_booleanUtils; + + /** + * @var \Magento\Framework\View\Layout\Argument\Interpreter\DataObject */ protected $_model; protected function setUp() { $this->_objectManager = $this->createMock(\Magento\Framework\ObjectManagerInterface::class); - $this->_model = new DataObject($this->_objectManager, self::EXPECTED_CLASS); + $this->_booleanUtils = $this->createMock(\Magento\Framework\Stdlib\BooleanUtils::class); + $this->_model = new DataObject($this->_objectManager, self::EXPECTED_CLASS, $this->_booleanUtils); } public function testEvaluate() { $input = ['name' => 'dataSource', 'value' => self::EXPECTED_CLASS]; + $this->_objectManager->expects($this->once()) + ->method('get') + ->with(self::EXPECTED_CLASS) + ->willReturn($this); + + $actual = $this->_model->evaluate($input); + $this->assertSame($this, $actual); + } + + public function textEvaluateShareEnabled() + { + $input = ['name' => 'dataSource', 'value' => self::EXPECTED_CLASS, 'shared' => true]; + $this->_booleanUtils->expects($this->once()) + ->method('toBoolean') + ->willReturn(true); + $this->_objectManager->expects($this->once()) + ->method('get') + ->with(self::EXPECTED_CLASS) + ->willReturn($this); + + $actual = $this->_model->evaluate($input); + $this->assertSame($this, $actual); + } + + public function textEvaluateShareDisabled() + { + $input = ['name' => 'dataSource', 'value' => self::EXPECTED_CLASS, 'shared' => false]; + $this->_booleanUtils->expects($this->once()) + ->method('toBoolean') + ->willReturn(false); $this->_objectManager->expects($this->once()) ->method('create') ->with(self::EXPECTED_CLASS) @@ -52,12 +93,56 @@ public function testEvaluateWrongClass($input, $expectedException, $expectedExce $this->expectException($expectedException); $this->expectExceptionMessage($expectedExceptionMessage); $self = $this; + $this->_objectManager->expects($this->any())->method('get')->willReturnCallback( + function ($className) use ($self) { + return $self->createMock($className); + } + ); + + $this->_model->evaluate($input); + } + + /** + * @dataProvider evaluateWrongClassDataProvider + */ + public function testEvaluateShareEnabledWrongClass($input, $expectedException, $expectedExceptionMessage) + { + $this->expectException($expectedException); + $this->expectExceptionMessage($expectedExceptionMessage); + $self = $this; + $this->_booleanUtils->expects($this->any()) + ->method('toBoolean') + ->willReturn(true); + $this->_objectManager->expects($this->any())->method('get')->willReturnCallback( + function ($className) use ($self) { + return $self->createMock($className); + } + ); + + $input['shared'] = true; + + $this->_model->evaluate($input); + } + + /** + * @dataProvider evaluateWrongClassDataProvider + */ + public function testEvaluateShareDisabledWrongClass($input, $expectedException, $expectedExceptionMessage) + { + $this->expectException($expectedException); + $this->expectExceptionMessage($expectedExceptionMessage); + $self = $this; + $this->_booleanUtils->expects($this->any()) + ->method('toBoolean') + ->willReturn(false); $this->_objectManager->expects($this->any())->method('create')->willReturnCallback( function ($className) use ($self) { return $self->createMock($className); } ); + $input['shared'] = false; + $this->_model->evaluate($input); } diff --git a/lib/web/fotorama/fotorama.js b/lib/web/fotorama/fotorama.js index 093ee707d3f98..b38e70d915c9d 100644 --- a/lib/web/fotorama/fotorama.js +++ b/lib/web/fotorama/fotorama.js @@ -1399,12 +1399,13 @@ fotoramaVersion = '4.6.4'; touchFLAG, targetIsSelectFLAG, targetIsLinkFlag, + isDisabledSwipe, tolerance, moved; function onStart(e) { $target = $(e.target); - tail.checked = targetIsSelectFLAG = targetIsLinkFlag = moved = false; + tail.checked = targetIsSelectFLAG = targetIsLinkFlag = isDisabledSwipe = moved = false; if (touchEnabledFLAG || tail.flow @@ -1415,6 +1416,7 @@ fotoramaVersion = '4.6.4'; touchFLAG = e.type === 'touchstart'; targetIsLinkFlag = $target.is('a, a *', el); + isDisabledSwipe = $target.hasClass('disableSwipe'); controlTouch = tail.control; tolerance = (tail.noMove || tail.noSwipe || controlTouch) ? 16 : !tail.snap ? 4 : 0; @@ -1428,7 +1430,7 @@ fotoramaVersion = '4.6.4'; touchEnabledFLAG = tail.flow = true; - if (!touchFLAG || tail.go) stopEvent(e); + if (!isDisabledSwipe && (!touchFLAG || tail.go)) stopEvent(e); } function onMove(e) { @@ -1441,6 +1443,12 @@ fotoramaVersion = '4.6.4'; return; } + isDisabledSwipe = $(e.target).hasClass('disableSwipe'); + + if (isDisabledSwipe) { + return; + } + extendEvent(e); var xDiff = Math.abs(e._x - startEvent._x), // opt _x → _pageX diff --git a/lib/web/fotorama/fotorama.min.js b/lib/web/fotorama/fotorama.min.js deleted file mode 100644 index 0a0cbd9db7e74..0000000000000 --- a/lib/web/fotorama/fotorama.min.js +++ /dev/null @@ -1 +0,0 @@ -fotoramaVersion="4.6.4";(function(window,document,location,$,undefined){"use strict";var _fotoramaClass="fotorama",_fullscreenClass="fotorama__fullscreen",wrapClass=_fotoramaClass+"__wrap",wrapCss2Class=wrapClass+"--css2",wrapCss3Class=wrapClass+"--css3",wrapVideoClass=wrapClass+"--video",wrapFadeClass=wrapClass+"--fade",wrapSlideClass=wrapClass+"--slide",wrapNoControlsClass=wrapClass+"--no-controls",wrapNoShadowsClass=wrapClass+"--no-shadows",wrapPanYClass=wrapClass+"--pan-y",wrapRtlClass=wrapClass+"--rtl",wrapOnlyActiveClass=wrapClass+"--only-active",wrapNoCaptionsClass=wrapClass+"--no-captions",wrapToggleArrowsClass=wrapClass+"--toggle-arrows",stageClass=_fotoramaClass+"__stage",stageFrameClass=stageClass+"__frame",stageFrameVideoClass=stageFrameClass+"--video",stageShaftClass=stageClass+"__shaft",grabClass=_fotoramaClass+"__grab",pointerClass=_fotoramaClass+"__pointer",arrClass=_fotoramaClass+"__arr",arrDisabledClass=arrClass+"--disabled",arrPrevClass=arrClass+"--prev",arrNextClass=arrClass+"--next",navClass=_fotoramaClass+"__nav",navWrapClass=navClass+"-wrap",navShaftClass=navClass+"__shaft",navShaftVerticalClass=navWrapClass+"--vertical",navShaftListClass=navWrapClass+"--list",navShafthorizontalClass=navWrapClass+"--horizontal",navDotsClass=navClass+"--dots",navThumbsClass=navClass+"--thumbs",navFrameClass=navClass+"__frame",fadeClass=_fotoramaClass+"__fade",fadeFrontClass=fadeClass+"-front",fadeRearClass=fadeClass+"-rear",shadowClass=_fotoramaClass+"__shadow",shadowsClass=shadowClass+"s",shadowsLeftClass=shadowsClass+"--left",shadowsRightClass=shadowsClass+"--right",shadowsTopClass=shadowsClass+"--top",shadowsBottomClass=shadowsClass+"--bottom",activeClass=_fotoramaClass+"__active",selectClass=_fotoramaClass+"__select",hiddenClass=_fotoramaClass+"--hidden",fullscreenClass=_fotoramaClass+"--fullscreen",fullscreenIconClass=_fotoramaClass+"__fullscreen-icon",errorClass=_fotoramaClass+"__error",loadingClass=_fotoramaClass+"__loading",loadedClass=_fotoramaClass+"__loaded",loadedFullClass=loadedClass+"--full",loadedImgClass=loadedClass+"--img",grabbingClass=_fotoramaClass+"__grabbing",imgClass=_fotoramaClass+"__img",imgFullClass=imgClass+"--full",thumbClass=_fotoramaClass+"__thumb",thumbArrLeft=thumbClass+"__arr--left",thumbArrRight=thumbClass+"__arr--right",thumbBorderClass=thumbClass+"-border",htmlClass=_fotoramaClass+"__html",videoContainerClass=_fotoramaClass+"-video-container",videoClass=_fotoramaClass+"__video",videoPlayClass=videoClass+"-play",videoCloseClass=videoClass+"-close",horizontalImageClass=_fotoramaClass+"_horizontal_ratio",verticalImageClass=_fotoramaClass+"_vertical_ratio",fotoramaSpinnerClass=_fotoramaClass+"__spinner",spinnerShowClass=fotoramaSpinnerClass+"--show";var JQUERY_VERSION=$&&$.fn.jquery.split(".");if(!JQUERY_VERSION||JQUERY_VERSION[0]<1||JQUERY_VERSION[0]==1&&JQUERY_VERSION[1]<8){throw"Fotorama requires jQuery 1.8 or later and will not run without it."}var _={};var Modernizr=function(window,document,undefined){var version="2.8.3",Modernizr={},docElement=document.documentElement,mod="modernizr",modElem=document.createElement(mod),mStyle=modElem.style,inputElem,toString={}.toString,prefixes=" -webkit- -moz- -o- -ms- ".split(" "),omPrefixes="Webkit Moz O ms",cssomPrefixes=omPrefixes.split(" "),domPrefixes=omPrefixes.toLowerCase().split(" "),tests={},inputs={},attrs={},classes=[],slice=classes.slice,featureName,injectElementWithStyles=function(rule,callback,nodes,testnames){var style,ret,node,docOverflow,div=document.createElement("div"),body=document.body,fakeBody=body||document.createElement("body");if(parseInt(nodes,10)){while(nodes--){node=document.createElement("div");node.id=testnames?testnames[nodes]:mod+(nodes+1);div.appendChild(node)}}style=["­",'<style id="s',mod,'">',rule,"</style>"].join("");div.id=mod;(body?div:fakeBody).innerHTML+=style;fakeBody.appendChild(div);if(!body){fakeBody.style.background="";fakeBody.style.overflow="hidden";docOverflow=docElement.style.overflow;docElement.style.overflow="hidden";docElement.appendChild(fakeBody)}ret=callback(div,rule);if(!body){fakeBody.parentNode.removeChild(fakeBody);docElement.style.overflow=docOverflow}else{div.parentNode.removeChild(div)}return!!ret},_hasOwnProperty={}.hasOwnProperty,hasOwnProp;if(!is(_hasOwnProperty,"undefined")&&!is(_hasOwnProperty.call,"undefined")){hasOwnProp=function(object,property){return _hasOwnProperty.call(object,property)}}else{hasOwnProp=function(object,property){return property in object&&is(object.constructor.prototype[property],"undefined")}}if(!Function.prototype.bind){Function.prototype.bind=function bind(that){var target=this;if(typeof target!="function"){throw new TypeError}var args=slice.call(arguments,1),bound=function(){if(this instanceof bound){var F=function(){};F.prototype=target.prototype;var self=new F;var result=target.apply(self,args.concat(slice.call(arguments)));if(Object(result)===result){return result}return self}else{return target.apply(that,args.concat(slice.call(arguments)))}};return bound}}function setCss(str){mStyle.cssText=str}function setCssAll(str1,str2){return setCss(prefixes.join(str1+";")+(str2||""))}function is(obj,type){return typeof obj===type}function contains(str,substr){return!!~(""+str).indexOf(substr)}function testProps(props,prefixed){for(var i in props){var prop=props[i];if(!contains(prop,"-")&&mStyle[prop]!==undefined){return prefixed=="pfx"?prop:true}}return false}function testDOMProps(props,obj,elem){for(var i in props){var item=obj[props[i]];if(item!==undefined){if(elem===false)return props[i];if(is(item,"function")){return item.bind(elem||obj)}return item}}return false}function testPropsAll(prop,prefixed,elem){var ucProp=prop.charAt(0).toUpperCase()+prop.slice(1),props=(prop+" "+cssomPrefixes.join(ucProp+" ")+ucProp).split(" ");if(is(prefixed,"string")||is(prefixed,"undefined")){return testProps(props,prefixed)}else{props=(prop+" "+domPrefixes.join(ucProp+" ")+ucProp).split(" ");return testDOMProps(props,prefixed,elem)}}tests["touch"]=function(){var bool;if("ontouchstart"in window||window.DocumentTouch&&document instanceof DocumentTouch){bool=true}else{injectElementWithStyles(["@media (",prefixes.join("touch-enabled),("),mod,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(node){bool=node.offsetTop===9})}return bool};tests["csstransforms3d"]=function(){var ret=!!testPropsAll("perspective");if(ret&&"webkitPerspective"in docElement.style){injectElementWithStyles("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(node,rule){ret=node.offsetLeft===9&&node.offsetHeight===3})}return ret};tests["csstransitions"]=function(){return testPropsAll("transition")};for(var feature in tests){if(hasOwnProp(tests,feature)){featureName=feature.toLowerCase();Modernizr[featureName]=tests[feature]();classes.push((Modernizr[featureName]?"":"no-")+featureName)}}Modernizr.addTest=function(feature,test){if(typeof feature=="object"){for(var key in feature){if(hasOwnProp(feature,key)){Modernizr.addTest(key,feature[key])}}}else{feature=feature.toLowerCase();if(Modernizr[feature]!==undefined){return Modernizr}test=typeof test=="function"?test():test;if(typeof enableClasses!=="undefined"&&enableClasses){docElement.className+=" "+(test?"":"no-")+feature}Modernizr[feature]=test}return Modernizr};setCss("");modElem=inputElem=null;Modernizr._version=version;Modernizr._prefixes=prefixes;Modernizr._domPrefixes=domPrefixes;Modernizr._cssomPrefixes=cssomPrefixes;Modernizr.testProp=function(prop){return testProps([prop])};Modernizr.testAllProps=testPropsAll;Modernizr.testStyles=injectElementWithStyles;Modernizr.prefixed=function(prop,obj,elem){if(!obj){return testPropsAll(prop,"pfx")}else{return testPropsAll(prop,obj,elem)}};return Modernizr}(window,document);var fullScreenApi={ok:false,is:function(){return false},request:function(){},cancel:function(){},event:"",prefix:""},browserPrefixes="webkit moz o ms khtml".split(" ");if(typeof document.cancelFullScreen!="undefined"){fullScreenApi.ok=true}else{for(var i=0,il=browserPrefixes.length;i<il;i++){fullScreenApi.prefix=browserPrefixes[i];if(typeof document[fullScreenApi.prefix+"CancelFullScreen"]!="undefined"){fullScreenApi.ok=true;break}}}if(fullScreenApi.ok){fullScreenApi.event=fullScreenApi.prefix+"fullscreenchange";fullScreenApi.is=function(){switch(this.prefix){case"":return document.fullScreen;case"webkit":return document.webkitIsFullScreen;default:return document[this.prefix+"FullScreen"]}};fullScreenApi.request=function(el){return this.prefix===""?el.requestFullScreen():el[this.prefix+"RequestFullScreen"]()};fullScreenApi.cancel=function(el){return this.prefix===""?document.cancelFullScreen():document[this.prefix+"CancelFullScreen"]()}}function bez(coOrdArray){var encodedFuncName="bez_"+$.makeArray(arguments).join("_").replace(".","p");if(typeof $["easing"][encodedFuncName]!=="function"){var polyBez=function(p1,p2){var A=[null,null],B=[null,null],C=[null,null],bezCoOrd=function(t,ax){C[ax]=3*p1[ax];B[ax]=3*(p2[ax]-p1[ax])-C[ax];A[ax]=1-C[ax]-B[ax];return t*(C[ax]+t*(B[ax]+t*A[ax]))},xDeriv=function(t){return C[0]+t*(2*B[0]+3*A[0]*t)},xForT=function(t){var x=t,i=0,z;while(++i<14){z=bezCoOrd(x,0)-t;if(Math.abs(z)<.001)break;x-=z/xDeriv(x)}return x};return function(t){return bezCoOrd(xForT(t),1)}};$["easing"][encodedFuncName]=function(x,t,b,c,d){return c*polyBez([coOrdArray[0],coOrdArray[1]],[coOrdArray[2],coOrdArray[3]])(t/d)+b}}return encodedFuncName}var $WINDOW=$(window),$DOCUMENT=$(document),$HTML,$BODY,QUIRKS_FORCE=location.hash.replace("#","")==="quirks",TRANSFORMS3D=Modernizr.csstransforms3d,CSS3=TRANSFORMS3D&&!QUIRKS_FORCE,COMPAT=TRANSFORMS3D||document.compatMode==="CSS1Compat",FULLSCREEN=fullScreenApi.ok,MOBILE=navigator.userAgent.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i),SLOW=!CSS3||MOBILE,MS_POINTER=navigator.msPointerEnabled,WHEEL="onwheel"in document.createElement("div")?"wheel":document.onmousewheel!==undefined?"mousewheel":"DOMMouseScroll",TOUCH_TIMEOUT=250,TRANSITION_DURATION=300,SCROLL_LOCK_TIMEOUT=1400,AUTOPLAY_INTERVAL=5e3,MARGIN=2,THUMB_SIZE=64,WIDTH=500,HEIGHT=333,STAGE_FRAME_KEY="$stageFrame",NAV_DOT_FRAME_KEY="$navDotFrame",NAV_THUMB_FRAME_KEY="$navThumbFrame",AUTO="auto",BEZIER=bez([.1,0,.25,1]),MAX_WIDTH=1200,thumbsPerSlide=1,OPTIONS={width:null,minwidth:null,maxwidth:"100%",height:null,minheight:null,maxheight:null,ratio:null,margin:MARGIN,nav:"dots",navposition:"bottom",navwidth:null,thumbwidth:THUMB_SIZE,thumbheight:THUMB_SIZE,thumbmargin:MARGIN,thumbborderwidth:MARGIN,allowfullscreen:false,transition:"slide",clicktransition:null,transitionduration:TRANSITION_DURATION,captions:true,startindex:0,loop:false,autoplay:false,stopautoplayontouch:true,keyboard:false,arrows:true,click:true,swipe:false,trackpad:false,shuffle:false,direction:"ltr",shadows:true,showcaption:true,navdir:"horizontal",navarrows:true,navtype:"thumbs"},KEYBOARD_OPTIONS={left:true,right:true,down:true,up:true,space:false,home:false,end:false};function noop(){}function minMaxLimit(value,min,max){return Math.max(isNaN(min)?-Infinity:min,Math.min(isNaN(max)?Infinity:max,value))}function readTransform(css,dir){return css.match(/ma/)&&css.match(/-?\d+(?!d)/g)[css.match(/3d/)?dir==="vertical"?13:12:dir==="vertical"?5:4]}function readPosition($el,dir){if(CSS3){return+readTransform($el.css("transform"),dir)}else{return+$el.css(dir==="vertical"?"top":"left").replace("px","")}}function getTranslate(pos,direction){var obj={};if(CSS3){switch(direction){case"vertical":obj.transform="translate3d(0, "+pos+"px,0)";break;case"list":break;default:obj.transform="translate3d("+pos+"px,0,0)";break}}else{direction==="vertical"?obj.top=pos:obj.left=pos}return obj}function getDuration(time){return{"transition-duration":time+"ms"}}function unlessNaN(value,alternative){return isNaN(value)?alternative:value}function numberFromMeasure(value,measure){return unlessNaN(+String(value).replace(measure||"px",""))}function numberFromPercent(value){return/%$/.test(value)?numberFromMeasure(value,"%"):undefined}function numberFromWhatever(value,whole){return unlessNaN(numberFromPercent(value)/100*whole,numberFromMeasure(value))}function measureIsValid(value){return(!isNaN(numberFromMeasure(value))||!isNaN(numberFromMeasure(value,"%")))&&value}function getPosByIndex(index,side,margin,baseIndex){return(index-(baseIndex||0))*(side+(margin||0))}function getIndexByPos(pos,side,margin,baseIndex){return-Math.round(pos/(side+(margin||0))-(baseIndex||0))}function bindTransitionEnd($el){var elData=$el.data();if(elData.tEnd)return;var el=$el[0],transitionEndEvent={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",msTransition:"MSTransitionEnd",transition:"transitionend"};addEvent(el,transitionEndEvent[Modernizr.prefixed("transition")],function(e){elData.tProp&&e.propertyName.match(elData.tProp)&&elData.onEndFn()});elData.tEnd=true}function afterTransition($el,property,fn,time){var ok,elData=$el.data();if(elData){elData.onEndFn=function(){if(ok)return;ok=true;clearTimeout(elData.tT);fn()};elData.tProp=property;clearTimeout(elData.tT);elData.tT=setTimeout(function(){elData.onEndFn()},time*1.5);bindTransitionEnd($el)}}function stop($el,pos){var dir=$el.navdir||"horizontal";if($el.length){var elData=$el.data();if(CSS3){$el.css(getDuration(0));elData.onEndFn=noop;clearTimeout(elData.tT)}else{$el.stop()}var lockedPos=getNumber(pos,function(){return readPosition($el,dir)});$el.css(getTranslate(lockedPos,dir));return lockedPos}}function getNumber(){var number;for(var _i=0,_l=arguments.length;_i<_l;_i++){number=_i?arguments[_i]():arguments[_i];if(typeof number==="number"){break}}return number}function edgeResistance(pos,edge){return Math.round(pos+(edge-pos)/1.5)}function getProtocol(){getProtocol.p=getProtocol.p||(location.protocol==="https:"?"https://":"http://");return getProtocol.p}function parseHref(href){var a=document.createElement("a");a.href=href;return a}function findVideoId(href,forceVideo){if(typeof href!=="string")return href;href=parseHref(href);var id,type;if(href.host.match(/youtube\.com/)&&href.search){id=href.search.split("v=")[1];if(id){var ampersandPosition=id.indexOf("&");if(ampersandPosition!==-1){id=id.substring(0,ampersandPosition)}type="youtube"}}else if(href.host.match(/youtube\.com|youtu\.be/)){id=href.pathname.replace(/^\/(embed\/|v\/)?/,"").replace(/\/.*/,"");type="youtube"}else if(href.host.match(/vimeo\.com/)){type="vimeo";id=href.pathname.replace(/^\/(video\/)?/,"").replace(/\/.*/,"")}if((!id||!type)&&forceVideo){id=href.href;type="custom"}return id?{id:id,type:type,s:href.search.replace(/^\?/,""),p:getProtocol()}:false}function getVideoThumbs(dataFrame,data,fotorama){var img,thumb,video=dataFrame.video;if(video.type==="youtube"){thumb=getProtocol()+"img.youtube.com/vi/"+video.id+"/default.jpg";img=thumb.replace(/\/default.jpg$/,"/hqdefault.jpg");dataFrame.thumbsReady=true}else if(video.type==="vimeo"){$.ajax({url:getProtocol()+"vimeo.com/api/v2/video/"+video.id+".json",dataType:"jsonp",success:function(json){dataFrame.thumbsReady=true;updateData(data,{img:json[0].thumbnail_large,thumb:json[0].thumbnail_small},dataFrame.i,fotorama)}})}else{dataFrame.thumbsReady=true}return{img:img,thumb:thumb}}function updateData(data,_dataFrame,i,fotorama){for(var _i=0,_l=data.length;_i<_l;_i++){var dataFrame=data[_i];if(dataFrame.i===i&&dataFrame.thumbsReady){var clear={videoReady:true};clear[STAGE_FRAME_KEY]=clear[NAV_THUMB_FRAME_KEY]=clear[NAV_DOT_FRAME_KEY]=false;fotorama.splice(_i,1,$.extend({},dataFrame,clear,_dataFrame));break}}}function getDataFromHtml($el){var data=[];function getDataFromImg($img,imgData,checkVideo){var $child=$img.children("img").eq(0),_imgHref=$img.attr("href"),_imgSrc=$img.attr("src"),_thumbSrc=$child.attr("src"),_video=imgData.video,video=checkVideo?findVideoId(_imgHref,_video===true):false;if(video){_imgHref=false}else{video=_video}getDimensions($img,$child,$.extend(imgData,{video:video,img:imgData.img||_imgHref||_imgSrc||_thumbSrc,thumb:imgData.thumb||_thumbSrc||_imgSrc||_imgHref}))}function getDimensions($img,$child,imgData){var separateThumbFLAG=imgData.thumb&&imgData.img!==imgData.thumb,width=numberFromMeasure(imgData.width||$img.attr("width")),height=numberFromMeasure(imgData.height||$img.attr("height"));$.extend(imgData,{width:width,height:height,thumbratio:getRatio(imgData.thumbratio||numberFromMeasure(imgData.thumbwidth||$child&&$child.attr("width")||separateThumbFLAG||width)/numberFromMeasure(imgData.thumbheight||$child&&$child.attr("height")||separateThumbFLAG||height))})}$el.children().each(function(){var $this=$(this),dataFrame=optionsToLowerCase($.extend($this.data(),{id:$this.attr("id")}));if($this.is("a, img")){getDataFromImg($this,dataFrame,true)}else if(!$this.is(":empty")){getDimensions($this,null,$.extend(dataFrame,{html:this,_html:$this.html()}))}else return;data.push(dataFrame)});return data}function isHidden(el){return el.offsetWidth===0&&el.offsetHeight===0}function isDetached(el){return!$.contains(document.documentElement,el)}function waitFor(test,fn,timeout,i){if(!waitFor.i){waitFor.i=1;waitFor.ii=[true]}i=i||waitFor.i;if(typeof waitFor.ii[i]==="undefined"){waitFor.ii[i]=true}if(test()){fn()}else{waitFor.ii[i]&&setTimeout(function(){waitFor.ii[i]&&waitFor(test,fn,timeout,i)},timeout||100)}return waitFor.i++}waitFor.stop=function(i){waitFor.ii[i]=false};function fit($el,measuresToFit){var elData=$el.data(),measures=elData.measures;if(measures&&(!elData.l||elData.l.W!==measures.width||elData.l.H!==measures.height||elData.l.r!==measures.ratio||elData.l.w!==measuresToFit.w||elData.l.h!==measuresToFit.h)){var height=minMaxLimit(measuresToFit.h,0,measures.height),width=height*measures.ratio;UTIL.setRatio($el,width,height);elData.l={W:measures.width,H:measures.height,r:measures.ratio,w:measuresToFit.w,h:measuresToFit.h}}return true}function setStyle($el,style){var el=$el[0];if(el.styleSheet){el.styleSheet.cssText=style}else{$el.html(style)}}function findShadowEdge(pos,min,max,dir){return min===max?false:dir==="vertical"?pos<=min?"top":pos>=max?"bottom":"top bottom":pos<=min?"left":pos>=max?"right":"left right"}function smartClick($el,fn,_options){_options=_options||{};$el.each(function(){var $this=$(this),thisData=$this.data(),startEvent;if(thisData.clickOn)return;thisData.clickOn=true;$.extend(touch($this,{onStart:function(e){startEvent=e;(_options.onStart||noop).call(this,e)},onMove:_options.onMove||noop,onTouchEnd:_options.onTouchEnd||noop,onEnd:function(result){if(result.moved)return;fn.call(this,startEvent)}}),{noMove:true})})}function div(classes,child){return'<div class="'+classes+'">'+(child||"")+"</div>"}function cls(className){return"."+className}function createVideoFrame(videoItem){var frame='<iframe src="'+videoItem.p+videoItem.type+".com/embed/"+videoItem.id+'" frameborder="0" allowfullscreen></iframe>';return frame}function shuffle(array){var l=array.length;while(l){var i=Math.floor(Math.random()*l--);var t=array[l];array[l]=array[i];array[i]=t}return array}function clone(array){return Object.prototype.toString.call(array)=="[object Array]"&&$.map(array,function(frame){return $.extend({},frame)})}function lockScroll($el,left,top){$el.scrollLeft(left||0).scrollTop(top||0)}function optionsToLowerCase(options){if(options){var opts={};$.each(options,function(key,value){opts[key.toLowerCase()]=value});return opts}}function getRatio(_ratio){if(!_ratio)return;var ratio=+_ratio;if(!isNaN(ratio)){return ratio}else{ratio=_ratio.split("/");return+ratio[0]/+ratio[1]||undefined}}function addEvent(el,e,fn,bool){if(!e)return;el.addEventListener?el.addEventListener(e,fn,!!bool):el.attachEvent("on"+e,fn)}function validateRestrictions(position,restriction){if(position>restriction.max){position=restriction.max}else{if(position<restriction.min){position=restriction.min}}return position}function validateSlidePos(opt,navShaftTouchTail,guessIndex,offsetNav,$guessNavFrame,$navWrap,dir){var position,size,wrapSize;if(dir==="horizontal"){size=opt.thumbwidth;wrapSize=$navWrap.width()}else{size=opt.thumbheight;wrapSize=$navWrap.height()}if((size+opt.margin)*(guessIndex+1)>=wrapSize-offsetNav){if(dir==="horizontal"){position=-$guessNavFrame.position().left}else{position=-$guessNavFrame.position().top}}else{if((size+opt.margin)*guessIndex<=Math.abs(offsetNav)){if(dir==="horizontal"){position=-$guessNavFrame.position().left+wrapSize-(size+opt.margin)}else{position=-$guessNavFrame.position().top+wrapSize-(size+opt.margin)}}else{position=offsetNav}}position=validateRestrictions(position,navShaftTouchTail);return position||0}function elIsDisabled(el){return!!el.getAttribute("disabled")}function disableAttr(FLAG,disable){if(disable){return{disabled:FLAG}}else{return{tabindex:FLAG*-1+"",disabled:FLAG}}}function addEnterUp(el,fn){addEvent(el,"keyup",function(e){elIsDisabled(el)||e.keyCode==13&&fn.call(el,e)})}function addFocus(el,fn){addEvent(el,"focus",el.onfocusin=function(e){fn.call(el,e)},true)}function stopEvent(e,stopPropagation){e.preventDefault?e.preventDefault():e.returnValue=false;stopPropagation&&e.stopPropagation&&e.stopPropagation()}function stubEvent($el,eventType){var isIOS=/ip(ad|hone|od)/i.test(window.navigator.userAgent);if(isIOS&&eventType==="touchend"){$el.on("touchend",function(e){$DOCUMENT.trigger("mouseup",e)})}$el.on(eventType,function(e){stopEvent(e,true);return false})}function getDirectionSign(forward){return forward?">":"<"}var UTIL=function(){function setRatioClass($el,wh,ht){var rateImg=wh/ht;if(rateImg<=1){$el.parent().removeClass(horizontalImageClass);$el.parent().addClass(verticalImageClass)}else{$el.parent().removeClass(verticalImageClass);$el.parent().addClass(horizontalImageClass)}}function setThumbAttr($frame,value,searchAttr){var attr=searchAttr;if(!$frame.attr(attr)&&$frame.attr(attr)!==undefined){$frame.attr(attr,value)}if($frame.find("["+attr+"]").length){$frame.find("["+attr+"]").each(function(){$(this).attr(attr,value)})}}function isExpectedCaption(frameItem,isExpected,undefined){var expected=false,frameExpected;frameItem.showCaption===undefined||frameItem.showCaption===true?frameExpected=true:frameExpected=false;if(!isExpected){return false}if(frameItem.caption&&frameExpected){expected=true}return expected}return{setRatio:setRatioClass,setThumbAttr:setThumbAttr,isExpectedCaption:isExpectedCaption}}(UTIL||{},jQuery);function slide($el,options){var elData=$el.data(),elPos=Math.round(options.pos),onEndFn=function(){if(elData&&elData.sliding){elData.sliding=false}(options.onEnd||noop)()};if(typeof options.overPos!=="undefined"&&options.overPos!==options.pos){elPos=options.overPos}var translate=$.extend(getTranslate(elPos,options.direction),options.width&&{width:options.width},options.height&&{height:options.height});if(elData&&elData.sliding){elData.sliding=true}if(CSS3){$el.css($.extend(getDuration(options.time),translate));if(options.time>10){afterTransition($el,"transform",onEndFn,options.time)}else{onEndFn()}}else{$el.stop().animate(translate,options.time,BEZIER,onEndFn)}}function fade($el1,$el2,$frames,options,fadeStack,chain){var chainedFLAG=typeof chain!=="undefined";if(!chainedFLAG){fadeStack.push(arguments);Array.prototype.push.call(arguments,fadeStack.length);if(fadeStack.length>1)return}$el1=$el1||$($el1);$el2=$el2||$($el2);var _$el1=$el1[0],_$el2=$el2[0],crossfadeFLAG=options.method==="crossfade",onEndFn=function(){if(!onEndFn.done){onEndFn.done=true;var args=(chainedFLAG||fadeStack.shift())&&fadeStack.shift();args&&fade.apply(this,args);(options.onEnd||noop)(!!args)}},time=options.time/(chain||1);$frames.removeClass(fadeRearClass+" "+fadeFrontClass);$el1.stop().addClass(fadeRearClass);$el2.stop().addClass(fadeFrontClass);crossfadeFLAG&&_$el2&&$el1.fadeTo(0,0);$el1.fadeTo(crossfadeFLAG?time:0,1,crossfadeFLAG&&onEndFn);$el2.fadeTo(time,0,onEndFn);_$el1&&crossfadeFLAG||_$el2||onEndFn()}var lastEvent,moveEventType,preventEvent,preventEventTimeout,dragDomEl;function extendEvent(e){var touch=(e.touches||[])[0]||e;e._x=touch.pageX||touch.originalEvent.pageX;e._y=touch.clientY||touch.originalEvent.clientY;e._now=$.now()}function touch($el,options){var el=$el[0],tail={},touchEnabledFLAG,startEvent,$target,controlTouch,touchFLAG,targetIsSelectFLAG,targetIsLinkFlag,tolerance,moved;function onStart(e){$target=$(e.target);tail.checked=targetIsSelectFLAG=targetIsLinkFlag=moved=false;if(touchEnabledFLAG||tail.flow||e.touches&&e.touches.length>1||e.which>1||lastEvent&&lastEvent.type!==e.type&&preventEvent||(targetIsSelectFLAG=options.select&&$target.is(options.select,el)))return targetIsSelectFLAG;touchFLAG=e.type==="touchstart";targetIsLinkFlag=$target.is("a, a *",el);controlTouch=tail.control;tolerance=tail.noMove||tail.noSwipe||controlTouch?16:!tail.snap?4:0;extendEvent(e);startEvent=lastEvent=e;moveEventType=e.type.replace(/down|start/,"move").replace(/Down/,"Move");(options.onStart||noop).call(el,e,{control:controlTouch,$target:$target});touchEnabledFLAG=tail.flow=true;if(!touchFLAG||tail.go)stopEvent(e)}function onMove(e){if(e.touches&&e.touches.length>1||MS_POINTER&&!e.isPrimary||moveEventType!==e.type||!touchEnabledFLAG){touchEnabledFLAG&&onEnd();(options.onTouchEnd||noop)();return}extendEvent(e);var xDiff=Math.abs(e._x-startEvent._x),yDiff=Math.abs(e._y-startEvent._y),xyDiff=xDiff-yDiff,xWin=(tail.go||tail.x||xyDiff>=0)&&!tail.noSwipe,yWin=xyDiff<0;if(touchFLAG&&!tail.checked){if(touchEnabledFLAG=xWin){stopEvent(e)}}else{stopEvent(e);if(movedEnough(xDiff,yDiff)){(options.onMove||noop).call(el,e,{touch:touchFLAG})}}if(!moved&&movedEnough(xDiff,yDiff)&&Math.sqrt(Math.pow(xDiff,2)+Math.pow(yDiff,2))>tolerance){moved=true}tail.checked=tail.checked||xWin||yWin}function movedEnough(xDiff,yDiff){return xDiff>yDiff&&xDiff>1.5}function onEnd(e){(options.onTouchEnd||noop)();var _touchEnabledFLAG=touchEnabledFLAG;tail.control=touchEnabledFLAG=false;if(_touchEnabledFLAG){tail.flow=false}if(!_touchEnabledFLAG||targetIsLinkFlag&&!tail.checked)return;e&&stopEvent(e);preventEvent=true;clearTimeout(preventEventTimeout);preventEventTimeout=setTimeout(function(){preventEvent=false},1e3);(options.onEnd||noop).call(el,{moved:moved,$target:$target,control:controlTouch,touch:touchFLAG,startEvent:startEvent,aborted:!e||e.type==="MSPointerCancel"})}function onOtherStart(){if(tail.flow)return;tail.flow=true}function onOtherEnd(){if(!tail.flow)return;tail.flow=false}if(MS_POINTER){addEvent(el,"MSPointerDown",onStart);addEvent(document,"MSPointerMove",onMove);addEvent(document,"MSPointerCancel",onEnd);addEvent(document,"MSPointerUp",onEnd)}else{addEvent(el,"touchstart",onStart);addEvent(el,"touchmove",onMove);addEvent(el,"touchend",onEnd);addEvent(document,"touchstart",onOtherStart);addEvent(document,"touchend",onOtherEnd);addEvent(document,"touchcancel",onOtherEnd);$WINDOW.on("scroll",onOtherEnd);$el.on("mousedown pointerdown",onStart);$DOCUMENT.on("mousemove pointermove",onMove).on("mouseup pointerup",onEnd)}if(Modernizr.touch){dragDomEl="a"}else{dragDomEl="div"}$el.on("click",dragDomEl,function(e){tail.checked&&stopEvent(e)});return tail}function moveOnTouch($el,options){var el=$el[0],elData=$el.data(),tail={},startCoo,coo,startElPos,moveElPos,edge,moveTrack,startTime,endTime,min,max,snap,dir,slowFLAG,controlFLAG,moved,tracked;function startTracking(e,noStop){tracked=true;startCoo=coo=dir==="vertical"?e._y:e._x;startTime=e._now;moveTrack=[[startTime,startCoo]];startElPos=moveElPos=tail.noMove||noStop?0:stop($el,(options.getPos||noop)());(options.onStart||noop).call(el,e)}function onStart(e,result){min=tail.min;max=tail.max;snap=tail.snap,dir=tail.direction||"horizontal",$el.navdir=dir;slowFLAG=e.altKey;tracked=moved=false;controlFLAG=result.control;if(!controlFLAG&&!elData.sliding){startTracking(e)}}function onMove(e,result){if(!tail.noSwipe){if(!tracked){startTracking(e)}coo=dir==="vertical"?e._y:e._x;moveTrack.push([e._now,coo]);moveElPos=startElPos-(startCoo-coo);edge=findShadowEdge(moveElPos,min,max,dir);if(moveElPos<=min){moveElPos=edgeResistance(moveElPos,min)}else if(moveElPos>=max){moveElPos=edgeResistance(moveElPos,max)}if(!tail.noMove){$el.css(getTranslate(moveElPos,dir));if(!moved){moved=true;result.touch||MS_POINTER||$el.addClass(grabbingClass)}(options.onMove||noop).call(el,e,{pos:moveElPos,edge:edge})}}}function onEnd(result){if(tail.noSwipe&&result.moved)return;if(!tracked){startTracking(result.startEvent,true)}result.touch||MS_POINTER||$el.removeClass(grabbingClass);endTime=$.now();var _backTimeIdeal=endTime-TOUCH_TIMEOUT,_backTime,_timeDiff,_timeDiffLast,backTime=null,backCoo,virtualPos,limitPos,newPos,overPos,time=TRANSITION_DURATION,speed,friction=options.friction;for(var _i=moveTrack.length-1;_i>=0;_i--){_backTime=moveTrack[_i][0];_timeDiff=Math.abs(_backTime-_backTimeIdeal);if(backTime===null||_timeDiff<_timeDiffLast){backTime=_backTime;backCoo=moveTrack[_i][1]}else if(backTime===_backTimeIdeal||_timeDiff>_timeDiffLast){break}_timeDiffLast=_timeDiff}newPos=minMaxLimit(moveElPos,min,max);var cooDiff=backCoo-coo,forwardFLAG=cooDiff>=0,timeDiff=endTime-backTime,longTouchFLAG=timeDiff>TOUCH_TIMEOUT,swipeFLAG=!longTouchFLAG&&moveElPos!==startElPos&&newPos===moveElPos;if(snap){newPos=minMaxLimit(Math[swipeFLAG?forwardFLAG?"floor":"ceil":"round"](moveElPos/snap)*snap,min,max);min=max=newPos}if(swipeFLAG&&(snap||newPos===moveElPos)){speed=-(cooDiff/timeDiff);time*=minMaxLimit(Math.abs(speed),options.timeLow,options.timeHigh);virtualPos=Math.round(moveElPos+speed*time/friction);if(!snap){newPos=virtualPos}if(!forwardFLAG&&virtualPos>max||forwardFLAG&&virtualPos<min){limitPos=forwardFLAG?min:max;overPos=virtualPos-limitPos;if(!snap){newPos=limitPos}overPos=minMaxLimit(newPos+overPos*.03,limitPos-50,limitPos+50);time=Math.abs((moveElPos-overPos)/(speed/friction))}}time*=slowFLAG?10:1;(options.onEnd||noop).call(el,$.extend(result,{moved:result.moved||longTouchFLAG&&snap,pos:moveElPos,newPos:newPos,overPos:overPos,time:time,dir:dir}))}tail=$.extend(touch(options.$wrap,$.extend({},options,{onStart:onStart,onMove:onMove,onEnd:onEnd})),tail);return tail}function wheel($el,options){var el=$el[0],lockFLAG,lastDirection,lastNow,tail={prevent:{}};addEvent(el,WHEEL,function(e){var yDelta=e.wheelDeltaY||-1*e.deltaY||0,xDelta=e.wheelDeltaX||-1*e.deltaX||0,xWin=Math.abs(xDelta)&&!Math.abs(yDelta),direction=getDirectionSign(xDelta<0),sameDirection=lastDirection===direction,now=$.now(),tooFast=now-lastNow<TOUCH_TIMEOUT;lastDirection=direction;lastNow=now;if(!xWin||!tail.ok||tail.prevent[direction]&&!lockFLAG){return}else{stopEvent(e,true);if(lockFLAG&&sameDirection&&tooFast){return}}if(options.shift){lockFLAG=true;clearTimeout(tail.t);tail.t=setTimeout(function(){lockFLAG=false},SCROLL_LOCK_TIMEOUT)}(options.onEnd||noop)(e,options.shift?direction:xDelta)});return tail}jQuery.Fotorama=function($fotorama,opts){$HTML=$("html");$BODY=$("body");var that=this,stamp=$.now(),stampClass=_fotoramaClass+stamp,fotorama=$fotorama[0],data,dataFrameCount=1,fotoramaData=$fotorama.data(),size,$style=$("<style></style>"),$anchor=$(div(hiddenClass)),$wrap=$fotorama.find(cls(wrapClass)),$stage=$wrap.find(cls(stageClass)),stage=$stage[0],$stageShaft=$fotorama.find(cls(stageShaftClass)),$stageFrame=$(),$arrPrev=$fotorama.find(cls(arrPrevClass)),$arrNext=$fotorama.find(cls(arrNextClass)),$arrs=$fotorama.find(cls(arrClass)),$navWrap=$fotorama.find(cls(navWrapClass)),$nav=$navWrap.find(cls(navClass)),$navShaft=$nav.find(cls(navShaftClass)),$navFrame,$navDotFrame=$(),$navThumbFrame=$(),stageShaftData=$stageShaft.data(),navShaftData=$navShaft.data(),$thumbBorder=$fotorama.find(cls(thumbBorderClass)),$thumbArrLeft=$fotorama.find(cls(thumbArrLeft)),$thumbArrRight=$fotorama.find(cls(thumbArrRight)),$fullscreenIcon=$fotorama.find(cls(fullscreenIconClass)),fullscreenIcon=$fullscreenIcon[0],$videoPlay=$(div(videoPlayClass)),$videoClose=$fotorama.find(cls(videoCloseClass)),videoClose=$videoClose[0],$spinner=$fotorama.find(cls(fotoramaSpinnerClass)),$videoPlaying,activeIndex=false,activeFrame,activeIndexes,repositionIndex,dirtyIndex,lastActiveIndex,prevIndex,nextIndex,nextAutoplayIndex,startIndex,o_loop,o_nav,o_navThumbs,o_navTop,o_allowFullScreen,o_nativeFullScreen,o_fade,o_thumbSide,o_thumbSide2,o_transitionDuration,o_transition,o_shadows,o_rtl,o_keyboard,lastOptions={},measures={},measuresSetFLAG,stageShaftTouchTail={},stageWheelTail={},navShaftTouchTail={},navWheelTail={},scrollTop,scrollLeft,showedFLAG,pausedAutoplayFLAG,stoppedAutoplayFLAG,toDeactivate={},toDetach={},measuresStash,touchedFLAG,hoverFLAG,navFrameKey,stageLeft=0,fadeStack=[];$wrap[STAGE_FRAME_KEY]=$('<div class="'+stageFrameClass+'"></div>');$wrap[NAV_THUMB_FRAME_KEY]=$($.Fotorama.jst.thumb());$wrap[NAV_DOT_FRAME_KEY]=$($.Fotorama.jst.dots());toDeactivate[STAGE_FRAME_KEY]=[];toDeactivate[NAV_THUMB_FRAME_KEY]=[];toDeactivate[NAV_DOT_FRAME_KEY]=[];toDetach[STAGE_FRAME_KEY]={};$wrap.addClass(CSS3?wrapCss3Class:wrapCss2Class);fotoramaData.fotorama=this;function checkForVideo(){$.each(data,function(i,dataFrame){if(!dataFrame.i){dataFrame.i=dataFrameCount++;var video=findVideoId(dataFrame.video,true);if(video){var thumbs={};dataFrame.video=video;if(!dataFrame.img&&!dataFrame.thumb){thumbs=getVideoThumbs(dataFrame,data,that)}else{dataFrame.thumbsReady=true}updateData(data,{img:thumbs.img,thumb:thumbs.thumb},dataFrame.i,that)}}})}function allowKey(key){return o_keyboard[key]}function setStagePosition(){if($stage!==undefined){if(opts.navdir=="vertical"){var padding=opts.thumbwidth+opts.thumbmargin;$stage.css("left",padding);$arrNext.css("right",padding);$fullscreenIcon.css("right",padding);$wrap.css("width",$wrap.css("width")+padding);$stageShaft.css("max-width",$wrap.width()-padding)}else{$stage.css("left","");$arrNext.css("right","");$fullscreenIcon.css("right","");$wrap.css("width",$wrap.css("width")+padding);$stageShaft.css("max-width","")}}}function bindGlobalEvents(FLAG){var keydownCommon="keydown."+_fotoramaClass,localStamp=_fotoramaClass+stamp,keydownLocal="keydown."+localStamp,keyupLocal="keyup."+localStamp,resizeLocal="resize."+localStamp+" "+"orientationchange."+localStamp,showParams;if(FLAG){$DOCUMENT.on(keydownLocal,function(e){var catched,index;if($videoPlaying&&e.keyCode===27){catched=true;unloadVideo($videoPlaying,true,true)}else if(that.fullScreen||opts.keyboard&&!that.index){if(e.keyCode===27){catched=true;that.cancelFullScreen()}else if(e.shiftKey&&e.keyCode===32&&allowKey("space")||!e.altKey&&!e.metaKey&&e.keyCode===37&&allowKey("left")||e.keyCode===38&&allowKey("up")&&$(":focus").attr("data-gallery-role")){that.longPress.progress();index="<"}else if(e.keyCode===32&&allowKey("space")||!e.altKey&&!e.metaKey&&e.keyCode===39&&allowKey("right")||e.keyCode===40&&allowKey("down")&&$(":focus").attr("data-gallery-role")){that.longPress.progress();index=">"}else if(e.keyCode===36&&allowKey("home")){that.longPress.progress();index="<<"}else if(e.keyCode===35&&allowKey("end")){that.longPress.progress();index=">>"}}(catched||index)&&stopEvent(e);showParams={index:index,slow:e.altKey,user:true};index&&(that.longPress.inProgress?that.showWhileLongPress(showParams):that.show(showParams))});if(FLAG){$DOCUMENT.on(keyupLocal,function(e){if(that.longPress.inProgress){that.showEndLongPress({user:true})}that.longPress.reset()})}if(!that.index){$DOCUMENT.off(keydownCommon).on(keydownCommon,"textarea, input, select",function(e){!$BODY.hasClass(_fullscreenClass)&&e.stopPropagation()})}$WINDOW.on(resizeLocal,that.resize)}else{$DOCUMENT.off(keydownLocal);$WINDOW.off(resizeLocal)}}function appendElements(FLAG){if(FLAG===appendElements.f)return;if(FLAG){$fotorama.addClass(_fotoramaClass+" "+stampClass).before($anchor).before($style);addInstance(that)}else{$anchor.detach();$style.detach();$fotorama.html(fotoramaData.urtext).removeClass(stampClass);hideInstance(that)}bindGlobalEvents(FLAG);appendElements.f=FLAG}function setData(){data=that.data=data||clone(opts.data)||getDataFromHtml($fotorama);size=that.size=data.length;ready.ok&&opts.shuffle&&shuffle(data);checkForVideo();activeIndex=limitIndex(activeIndex);size&&appendElements(true)}function stageNoMove(){var _noMove=size<2||$videoPlaying;stageShaftTouchTail.noMove=_noMove||o_fade;stageShaftTouchTail.noSwipe=_noMove||!opts.swipe;!o_transition&&$stageShaft.toggleClass(grabClass,!opts.click&&!stageShaftTouchTail.noMove&&!stageShaftTouchTail.noSwipe);MS_POINTER&&$wrap.toggleClass(wrapPanYClass,!stageShaftTouchTail.noSwipe)}function setAutoplayInterval(interval){if(interval===true)interval="";opts.autoplay=Math.max(+interval||AUTOPLAY_INTERVAL,o_transitionDuration*1.5)}function updateThumbArrow(opt){if(opt.navarrows&&opt.nav==="thumbs"){$thumbArrLeft.show();$thumbArrRight.show()}else{$thumbArrLeft.hide();$thumbArrRight.hide()}}function getThumbsInSlide($el,opts){return Math.floor($wrap.width()/(opts.thumbwidth+opts.thumbmargin))}function setOptions(){if(!opts.nav||opts.nav==="dots"){opts.navdir="horizontal"}that.options=opts=optionsToLowerCase(opts);thumbsPerSlide=getThumbsInSlide($wrap,opts);o_fade=opts.transition==="crossfade"||opts.transition==="dissolve";o_loop=opts.loop&&(size>2||o_fade&&(!o_transition||o_transition!=="slide"));o_transitionDuration=+opts.transitionduration||TRANSITION_DURATION;o_rtl=opts.direction==="rtl";o_keyboard=$.extend({},opts.keyboard&&KEYBOARD_OPTIONS,opts.keyboard);updateThumbArrow(opts);var classes={add:[],remove:[]};function addOrRemoveClass(FLAG,value){classes[FLAG?"add":"remove"].push(value)}if(size>1){o_nav=opts.nav;o_navTop=opts.navposition==="top";classes.remove.push(selectClass);$arrs.toggle(!!opts.arrows)}else{o_nav=false;$arrs.hide()}arrsUpdate();stageWheelUpdate();thumbArrUpdate();if(opts.autoplay)setAutoplayInterval(opts.autoplay);o_thumbSide=numberFromMeasure(opts.thumbwidth)||THUMB_SIZE;o_thumbSide2=numberFromMeasure(opts.thumbheight)||THUMB_SIZE;stageWheelTail.ok=navWheelTail.ok=opts.trackpad&&!SLOW;stageNoMove();extendMeasures(opts,[measures]);o_navThumbs=o_nav==="thumbs";if($navWrap.filter(":hidden")&&!!o_nav){$navWrap.show()}if(o_navThumbs){frameDraw(size,"navThumb");$navFrame=$navThumbFrame;navFrameKey=NAV_THUMB_FRAME_KEY;setStyle($style,$.Fotorama.jst.style({w:o_thumbSide,h:o_thumbSide2,b:opts.thumbborderwidth,m:opts.thumbmargin,s:stamp,q:!COMPAT}));$nav.addClass(navThumbsClass).removeClass(navDotsClass)}else if(o_nav==="dots"){frameDraw(size,"navDot");$navFrame=$navDotFrame;navFrameKey=NAV_DOT_FRAME_KEY;$nav.addClass(navDotsClass).removeClass(navThumbsClass)}else{$navWrap.hide();o_nav=false;$nav.removeClass(navThumbsClass+" "+navDotsClass)}if(o_nav){if(o_navTop){$navWrap.insertBefore($stage)}else{$navWrap.insertAfter($stage)}frameAppend.nav=false;frameAppend($navFrame,$navShaft,"nav")}o_allowFullScreen=opts.allowfullscreen;if(o_allowFullScreen){$fullscreenIcon.prependTo($stage);o_nativeFullScreen=FULLSCREEN&&o_allowFullScreen==="native";stubEvent($fullscreenIcon,"touchend")}else{$fullscreenIcon.detach();o_nativeFullScreen=false}addOrRemoveClass(o_fade,wrapFadeClass);addOrRemoveClass(!o_fade,wrapSlideClass);addOrRemoveClass(!opts.captions,wrapNoCaptionsClass);addOrRemoveClass(o_rtl,wrapRtlClass);addOrRemoveClass(opts.arrows,wrapToggleArrowsClass);o_shadows=opts.shadows&&!SLOW;addOrRemoveClass(!o_shadows,wrapNoShadowsClass);$wrap.addClass(classes.add.join(" ")).removeClass(classes.remove.join(" "));lastOptions=$.extend({},opts);setStagePosition()}function normalizeIndex(index){return index<0?(size+index%size)%size:index>=size?index%size:index}function limitIndex(index){return minMaxLimit(index,0,size-1)}function edgeIndex(index){return o_loop?normalizeIndex(index):limitIndex(index)}function getPrevIndex(index){return index>0||o_loop?index-1:false}function getNextIndex(index){return index<size-1||o_loop?index+1:false}function setStageShaftMinmaxAndSnap(){stageShaftTouchTail.min=o_loop?-Infinity:-getPosByIndex(size-1,measures.w,opts.margin,repositionIndex);stageShaftTouchTail.max=o_loop?Infinity:-getPosByIndex(0,measures.w,opts.margin,repositionIndex);stageShaftTouchTail.snap=measures.w+opts.margin}function setNavShaftMinMax(){var isVerticalDir=opts.navdir==="vertical";var param=isVerticalDir?$navShaft.height():$navShaft.width();var mainParam=isVerticalDir?measures.h:measures.nw;navShaftTouchTail.min=Math.min(0,mainParam-param);navShaftTouchTail.max=0;navShaftTouchTail.direction=opts.navdir;$navShaft.toggleClass(grabClass,!(navShaftTouchTail.noMove=navShaftTouchTail.min===navShaftTouchTail.max))}function eachIndex(indexes,type,fn){if(typeof indexes==="number"){indexes=new Array(indexes);var rangeFLAG=true}return $.each(indexes,function(i,index){if(rangeFLAG)index=i;if(typeof index==="number"){var dataFrame=data[normalizeIndex(index)];if(dataFrame){var key="$"+type+"Frame",$frame=dataFrame[key];fn.call(this,i,index,dataFrame,$frame,key,$frame&&$frame.data())}}})}function setMeasures(width,height,ratio,index){if(!measuresSetFLAG||measuresSetFLAG==="*"&&index===startIndex){width=measureIsValid(opts.width)||measureIsValid(width)||WIDTH;height=measureIsValid(opts.height)||measureIsValid(height)||HEIGHT;that.resize({width:width,ratio:opts.ratio||ratio||width/height},0,index!==startIndex&&"*")}}function loadImg(indexes,type,specialMeasures,again){eachIndex(indexes,type,function(i,index,dataFrame,$frame,key,frameData){if(!$frame)return;var fullFLAG=that.fullScreen&&!frameData.$full&&type==="stage";if(frameData.$img&&!again&&!fullFLAG)return;var img=new Image,$img=$(img),imgData=$img.data();frameData[fullFLAG?"$full":"$img"]=$img;var srcKey=type==="stage"?fullFLAG?"full":"img":"thumb",src=dataFrame[srcKey],dummy=fullFLAG?dataFrame["img"]:dataFrame[type==="stage"?"thumb":"img"];if(type==="navThumb")$frame=frameData.$wrap;function triggerTriggerEvent(event){var _index=normalizeIndex(index);triggerEvent(event,{index:_index,src:src,frame:data[_index]})}function error(){$img.remove();$.Fotorama.cache[src]="error";if((!dataFrame.html||type!=="stage")&&dummy&&dummy!==src){dataFrame[srcKey]=src=dummy;frameData.$full=null;loadImg([index],type,specialMeasures,true)}else{if(src&&!dataFrame.html&&!fullFLAG){$frame.trigger("f:error").removeClass(loadingClass).addClass(errorClass);triggerTriggerEvent("error")}else if(type==="stage"){$frame.trigger("f:load").removeClass(loadingClass+" "+errorClass).addClass(loadedClass);triggerTriggerEvent("load");setMeasures()}frameData.state="error";if(size>1&&data[index]===dataFrame&&!dataFrame.html&&!dataFrame.deleted&&!dataFrame.video&&!fullFLAG){dataFrame.deleted=true;that.splice(index,1)}}}function loaded(){$.Fotorama.measures[src]=imgData.measures=$.Fotorama.measures[src]||{width:img.width,height:img.height,ratio:img.width/img.height};setMeasures(imgData.measures.width,imgData.measures.height,imgData.measures.ratio,index);$img.off("load error").addClass(""+(fullFLAG?imgFullClass:imgClass)).attr("aria-hidden","false").prependTo($frame);if($frame.hasClass(stageFrameClass)&&!$frame.hasClass(videoContainerClass)){$frame.attr("href",$img.attr("src"))}fit($img,($.isFunction(specialMeasures)?specialMeasures():specialMeasures)||measures);$.Fotorama.cache[src]=frameData.state="loaded";setTimeout(function(){$frame.trigger("f:load").removeClass(loadingClass+" "+errorClass).addClass(loadedClass+" "+(fullFLAG?loadedFullClass:loadedImgClass));if(type==="stage"){triggerTriggerEvent("load")}else if(dataFrame.thumbratio===AUTO||!dataFrame.thumbratio&&opts.thumbratio===AUTO){dataFrame.thumbratio=imgData.measures.ratio;reset()}},0)}if(!src){error();return}function waitAndLoad(){var _i=10;waitFor(function(){return!touchedFLAG||!_i--&&!SLOW},function(){loaded()})}if(!$.Fotorama.cache[src]){$.Fotorama.cache[src]="*";$img.on("load",waitAndLoad).on("error",error)}else{(function justWait(){if($.Fotorama.cache[src]==="error"){error()}else if($.Fotorama.cache[src]==="loaded"){setTimeout(waitAndLoad,0)}else{setTimeout(justWait,100)}})()}frameData.state="";img.src=src;if(frameData.data.caption){img.alt=frameData.data.caption||""}if(frameData.data.full){$(img).data("original",frameData.data.full)}if(UTIL.isExpectedCaption(dataFrame,opts.showcaption)){$(img).attr("aria-labelledby",dataFrame.labelledby)}})}function updateFotoramaState(){var $frame=activeFrame[STAGE_FRAME_KEY];if($frame&&!$frame.data().state){$spinner.addClass(spinnerShowClass);$frame.on("f:load f:error",function(){$frame.off("f:load f:error");$spinner.removeClass(spinnerShowClass)})}}function addNavFrameEvents(frame){addEnterUp(frame,onNavFrameClick);addFocus(frame,function(){setTimeout(function(){lockScroll($nav)},0);slideNavShaft({time:o_transitionDuration,guessIndex:$(this).data().eq,minMax:navShaftTouchTail})})}function frameDraw(indexes,type){eachIndex(indexes,type,function(i,index,dataFrame,$frame,key,frameData){if($frame)return;$frame=dataFrame[key]=$wrap[key].clone();frameData=$frame.data();frameData.data=dataFrame;var frame=$frame[0],labelledbyValue="labelledby"+$.now();if(type==="stage"){if(dataFrame.html){$('<div class="'+htmlClass+'"></div>').append(dataFrame._html?$(dataFrame.html).removeAttr("id").html(dataFrame._html):dataFrame.html).appendTo($frame)}if(dataFrame.id){labelledbyValue=dataFrame.id||labelledbyValue}dataFrame.labelledby=labelledbyValue;if(UTIL.isExpectedCaption(dataFrame,opts.showcaption)){$($.Fotorama.jst.frameCaption({caption:dataFrame.caption,labelledby:labelledbyValue})).appendTo($frame)}dataFrame.video&&$frame.addClass(stageFrameVideoClass).append($videoPlay.clone());addFocus(frame,function(){setTimeout(function(){lockScroll($stage)},0);clickToShow({index:frameData.eq,user:true})});$stageFrame=$stageFrame.add($frame)}else if(type==="navDot"){addNavFrameEvents(frame);$navDotFrame=$navDotFrame.add($frame)}else if(type==="navThumb"){addNavFrameEvents(frame);frameData.$wrap=$frame.children(":first");$navThumbFrame=$navThumbFrame.add($frame);if(dataFrame.video){frameData.$wrap.append($videoPlay.clone())}}})}function callFit($img,measuresToFit){return $img&&$img.length&&fit($img,measuresToFit)}function stageFramePosition(indexes){eachIndex(indexes,"stage",function(i,index,dataFrame,$frame,key,frameData){if(!$frame)return;var normalizedIndex=normalizeIndex(index);frameData.eq=normalizedIndex;toDetach[STAGE_FRAME_KEY][normalizedIndex]=$frame.css($.extend({left:o_fade?0:getPosByIndex(index,measures.w,opts.margin,repositionIndex)},o_fade&&getDuration(0)));if(isDetached($frame[0])){$frame.appendTo($stageShaft);unloadVideo(dataFrame.$video)}callFit(frameData.$img,measures);callFit(frameData.$full,measures);if($frame.hasClass(stageFrameClass)&&!($frame.attr("aria-hidden")==="false"&&$frame.hasClass(activeClass))){$frame.attr("aria-hidden","true")}})}function thumbsDraw(pos,loadFLAG){var leftLimit,rightLimit,exceedLimit;if(o_nav!=="thumbs"||isNaN(pos))return;leftLimit=-pos;rightLimit=-pos+measures.nw;if(opts.navdir==="vertical"){pos=pos-opts.thumbheight;rightLimit=-pos+measures.h}$navThumbFrame.each(function(){var $this=$(this),thisData=$this.data(),eq=thisData.eq,getSpecialMeasures=function(){return{h:o_thumbSide2,w:thisData.w}},specialMeasures=getSpecialMeasures(),exceedLimit=opts.navdir==="vertical"?thisData.t>rightLimit:thisData.l>rightLimit;specialMeasures.w=thisData.w;if(thisData.l+thisData.w<leftLimit||exceedLimit||callFit(thisData.$img,specialMeasures))return;loadFLAG&&loadImg([eq],"navThumb",getSpecialMeasures)})}function frameAppend($frames,$shaft,type){if(!frameAppend[type]){var thumbsFLAG=type==="nav"&&o_navThumbs,left=0,top=0;$shaft.append($frames.filter(function(){var actual,$this=$(this),frameData=$this.data();for(var _i=0,_l=data.length;_i<_l;_i++){if(frameData.data===data[_i]){actual=true;frameData.eq=_i;break}}return actual||$this.remove()&&false}).sort(function(a,b){return $(a).data().eq-$(b).data().eq}).each(function(){var $this=$(this),frameData=$this.data();UTIL.setThumbAttr($this,frameData.data.caption,"aria-label")}).each(function(){if(!thumbsFLAG)return;var $this=$(this),frameData=$this.data(),thumbwidth=Math.round(o_thumbSide2*frameData.data.thumbratio)||o_thumbSide,thumbheight=Math.round(o_thumbSide/frameData.data.thumbratio)||o_thumbSide2;frameData.t=top;frameData.h=thumbheight;frameData.l=left;frameData.w=thumbwidth;$this.css({width:thumbwidth});top+=thumbheight+opts.thumbmargin;left+=thumbwidth+opts.thumbmargin}));frameAppend[type]=true}}function getDirection(x){return x-stageLeft>measures.w/3}function disableDirrection(i){return!o_loop&&(!(activeIndex+i)||!(activeIndex-size+i))&&!$videoPlaying}function arrsUpdate(){var disablePrev=disableDirrection(0),disableNext=disableDirrection(1);$arrPrev.toggleClass(arrDisabledClass,disablePrev).attr(disableAttr(disablePrev,false));$arrNext.toggleClass(arrDisabledClass,disableNext).attr(disableAttr(disableNext,false))}function thumbArrUpdate(){var isLeftDisable=false,isRightDisable=false;if(opts.navtype==="thumbs"&&!opts.loop){activeIndex==0?isLeftDisable=true:isLeftDisable=false;activeIndex==opts.data.length-1?isRightDisable=true:isRightDisable=false}if(opts.navtype==="slides"){var pos=readPosition($navShaft,opts.navdir);pos>=navShaftTouchTail.max?isLeftDisable=true:isLeftDisable=false;pos<=navShaftTouchTail.min?isRightDisable=true:isRightDisable=false}$thumbArrLeft.toggleClass(arrDisabledClass,isLeftDisable).attr(disableAttr(isLeftDisable,true));$thumbArrRight.toggleClass(arrDisabledClass,isRightDisable).attr(disableAttr(isRightDisable,true))}function stageWheelUpdate(){if(stageWheelTail.ok){stageWheelTail.prevent={"<":disableDirrection(0),">":disableDirrection(1)}}}function getNavFrameBounds($navFrame){var navFrameData=$navFrame.data(),left,top,width,height;if(o_navThumbs){left=navFrameData.l;top=navFrameData.t;width=navFrameData.w;height=navFrameData.h}else{left=$navFrame.position().left;width=$navFrame.width()}var horizontalBounds={c:left+width/2,min:-left+opts.thumbmargin*10,max:-left+measures.w-width-opts.thumbmargin*10};var verticalBounds={c:top+height/2,min:-top+opts.thumbmargin*10,max:-top+measures.h-height-opts.thumbmargin*10};return opts.navdir==="vertical"?verticalBounds:horizontalBounds}function slideThumbBorder(time){var navFrameData=activeFrame[navFrameKey].data();slide($thumbBorder,{time:time*1.2,pos:opts.navdir==="vertical"?navFrameData.t:navFrameData.l,width:navFrameData.w,height:navFrameData.h,direction:opts.navdir})}function slideNavShaft(options){var $guessNavFrame=data[options.guessIndex][navFrameKey],typeOfAnimation=opts.navtype;var overflowFLAG,time,minMax,boundTop,boundLeft,l,pos,x;if($guessNavFrame){if(typeOfAnimation==="thumbs"){overflowFLAG=navShaftTouchTail.min!==navShaftTouchTail.max;minMax=options.minMax||overflowFLAG&&getNavFrameBounds(activeFrame[navFrameKey]);boundTop=overflowFLAG&&(options.keep&&slideNavShaft.t?slideNavShaft.l:minMaxLimit((options.coo||measures.nw/2)-getNavFrameBounds($guessNavFrame).c,minMax.min,minMax.max));boundLeft=overflowFLAG&&(options.keep&&slideNavShaft.l?slideNavShaft.l:minMaxLimit((options.coo||measures.nw/2)-getNavFrameBounds($guessNavFrame).c,minMax.min,minMax.max));l=opts.navdir==="vertical"?boundTop:boundLeft;pos=overflowFLAG&&minMaxLimit(l,navShaftTouchTail.min,navShaftTouchTail.max)||0;time=options.time*1.1;slide($navShaft,{time:time,pos:pos,direction:opts.navdir,onEnd:function(){thumbsDraw(pos,true);thumbArrUpdate()}});setShadow($nav,findShadowEdge(pos,navShaftTouchTail.min,navShaftTouchTail.max,opts.navdir));slideNavShaft.l=l}else{x=readPosition($navShaft,opts.navdir);time=options.time*1.11;pos=validateSlidePos(opts,navShaftTouchTail,options.guessIndex,x,$guessNavFrame,$navWrap,opts.navdir);slide($navShaft,{time:time,pos:pos,direction:opts.navdir,onEnd:function(){thumbsDraw(pos,true);thumbArrUpdate()}});setShadow($nav,findShadowEdge(pos,navShaftTouchTail.min,navShaftTouchTail.max,opts.navdir))}}}function navUpdate(){deactivateFrames(navFrameKey);toDeactivate[navFrameKey].push(activeFrame[navFrameKey].addClass(activeClass).attr("data-active",true))}function deactivateFrames(key){var _toDeactivate=toDeactivate[key];while(_toDeactivate.length){_toDeactivate.shift().removeClass(activeClass).attr("data-active",false)}}function detachFrames(key){var _toDetach=toDetach[key];$.each(activeIndexes,function(i,index){delete _toDetach[normalizeIndex(index)]});$.each(_toDetach,function(index,$frame){delete _toDetach[index];$frame.detach()})}function stageShaftReposition(skipOnEnd){repositionIndex=dirtyIndex=activeIndex;var $frame=activeFrame[STAGE_FRAME_KEY];if($frame){deactivateFrames(STAGE_FRAME_KEY);toDeactivate[STAGE_FRAME_KEY].push($frame.addClass(activeClass).attr("data-active",true));if($frame.hasClass(stageFrameClass)){$frame.attr("aria-hidden","false")}skipOnEnd||that.showStage.onEnd(true);stop($stageShaft,0,true);detachFrames(STAGE_FRAME_KEY);stageFramePosition(activeIndexes);setStageShaftMinmaxAndSnap();setNavShaftMinMax();addEnterUp($stageShaft[0],function(){if(!$fotorama.hasClass(fullscreenClass)){that.requestFullScreen();$fullscreenIcon.focus()}})}}function extendMeasures(options,measuresArray){if(!options)return;$.each(measuresArray,function(i,measures){if(!measures)return;$.extend(measures,{width:options.width||measures.width,height:options.height,minwidth:options.minwidth,maxwidth:options.maxwidth,minheight:options.minheight,maxheight:options.maxheight,ratio:getRatio(options.ratio)})})}function triggerEvent(event,extra){$fotorama.trigger(_fotoramaClass+":"+event,[that,extra])}function onTouchStart(){clearTimeout(onTouchEnd.t);touchedFLAG=1;if(opts.stopautoplayontouch){that.stopAutoplay()}else{pausedAutoplayFLAG=true}}function onTouchEnd(){if(!touchedFLAG)return;if(!opts.stopautoplayontouch){releaseAutoplay();changeAutoplay()}onTouchEnd.t=setTimeout(function(){touchedFLAG=0},TRANSITION_DURATION+TOUCH_TIMEOUT)}function releaseAutoplay(){pausedAutoplayFLAG=!!($videoPlaying||stoppedAutoplayFLAG)}function changeAutoplay(){clearTimeout(changeAutoplay.t);waitFor.stop(changeAutoplay.w);if(!opts.autoplay||pausedAutoplayFLAG){if(that.autoplay){that.autoplay=false;triggerEvent("stopautoplay")}return}if(!that.autoplay){that.autoplay=true;triggerEvent("startautoplay")}var _activeIndex=activeIndex;var frameData=activeFrame[STAGE_FRAME_KEY].data();changeAutoplay.w=waitFor(function(){return frameData.state||_activeIndex!==activeIndex},function(){changeAutoplay.t=setTimeout(function(){if(pausedAutoplayFLAG||_activeIndex!==activeIndex)return;var _nextAutoplayIndex=nextAutoplayIndex,nextFrameData=data[_nextAutoplayIndex][STAGE_FRAME_KEY].data();changeAutoplay.w=waitFor(function(){return nextFrameData.state||_nextAutoplayIndex!==nextAutoplayIndex},function(){if(pausedAutoplayFLAG||_nextAutoplayIndex!==nextAutoplayIndex)return;that.show(o_loop?getDirectionSign(!o_rtl):nextAutoplayIndex)})},opts.autoplay)})}that.startAutoplay=function(interval){if(that.autoplay)return this;pausedAutoplayFLAG=stoppedAutoplayFLAG=false;setAutoplayInterval(interval||opts.autoplay);changeAutoplay();return this};that.stopAutoplay=function(){if(that.autoplay){pausedAutoplayFLAG=stoppedAutoplayFLAG=true;changeAutoplay()}return this};that.showSlide=function(slideDir){var currentPosition=readPosition($navShaft,opts.navdir),pos,time=500*1.1,size=opts.navdir==="horizontal"?opts.thumbwidth:opts.thumbheight,onEnd=function(){thumbArrUpdate()};if(slideDir==="next"){pos=currentPosition-(size+opts.margin)*thumbsPerSlide}if(slideDir==="prev"){pos=currentPosition+(size+opts.margin)*thumbsPerSlide}pos=validateRestrictions(pos,navShaftTouchTail);thumbsDraw(pos,true);slide($navShaft,{time:time,pos:pos,direction:opts.navdir,onEnd:onEnd})};that.showWhileLongPress=function(options){if(that.longPress.singlePressInProgress){return}var index=calcActiveIndex(options);calcGlobalIndexes(index);var time=calcTime(options)/50;var _activeFrame=activeFrame;that.activeFrame=activeFrame=data[activeIndex];var silent=_activeFrame===activeFrame&&!options.user;that.showNav(silent,options,time);return this};that.showEndLongPress=function(options){if(that.longPress.singlePressInProgress){return}var index=calcActiveIndex(options);calcGlobalIndexes(index);var time=calcTime(options)/50;var _activeFrame=activeFrame;that.activeFrame=activeFrame=data[activeIndex];var silent=_activeFrame===activeFrame&&!options.user;that.showStage(silent,options,time);showedFLAG=typeof lastActiveIndex!=="undefined"&&lastActiveIndex!==activeIndex;lastActiveIndex=activeIndex;return this};function calcActiveIndex(options){var index;if(typeof options!=="object"){index=options;options={}}else{index=options.index}index=index===">"?dirtyIndex+1:index==="<"?dirtyIndex-1:index==="<<"?0:index===">>"?size-1:index;index=isNaN(index)?undefined:index;index=typeof index==="undefined"?activeIndex||0:index;return index}function calcGlobalIndexes(index){that.activeIndex=activeIndex=edgeIndex(index);prevIndex=getPrevIndex(activeIndex);nextIndex=getNextIndex(activeIndex);nextAutoplayIndex=normalizeIndex(activeIndex+(o_rtl?-1:1));activeIndexes=[activeIndex,prevIndex,nextIndex];dirtyIndex=o_loop?index:activeIndex}function calcTime(options){var diffIndex=Math.abs(lastActiveIndex-dirtyIndex),time=getNumber(options.time,function(){return Math.min(o_transitionDuration*(1+(diffIndex-1)/12),o_transitionDuration*2)});if(options.slow){time*=10}return time}that.showStage=function(silent,options,time){unloadVideo($videoPlaying,activeFrame.i!==data[normalizeIndex(repositionIndex)].i);frameDraw(activeIndexes,"stage");stageFramePosition(SLOW?[dirtyIndex]:[dirtyIndex,getPrevIndex(dirtyIndex),getNextIndex(dirtyIndex)]);updateTouchTails("go",true);silent||triggerEvent("show",{user:options.user,time:time});pausedAutoplayFLAG=true;var overPos=options.overPos;var onEnd=that.showStage.onEnd=function(skipReposition){if(onEnd.ok)return;onEnd.ok=true;skipReposition||stageShaftReposition(true);if(!silent){triggerEvent("showend",{user:options.user})}if(!skipReposition&&o_transition&&o_transition!==opts.transition){that.setOptions({transition:o_transition});o_transition=false;return}updateFotoramaState();loadImg(activeIndexes,"stage");updateTouchTails("go",false);stageWheelUpdate();stageCursor();releaseAutoplay();changeAutoplay();if(that.fullScreen){activeFrame[STAGE_FRAME_KEY].find("."+imgFullClass).attr("aria-hidden",false);activeFrame[STAGE_FRAME_KEY].find("."+imgClass).attr("aria-hidden",true)}else{activeFrame[STAGE_FRAME_KEY].find("."+imgFullClass).attr("aria-hidden",true);activeFrame[STAGE_FRAME_KEY].find("."+imgClass).attr("aria-hidden",false)}};if(!o_fade){slide($stageShaft,{pos:-getPosByIndex(dirtyIndex,measures.w,opts.margin,repositionIndex),overPos:overPos,time:time,onEnd:onEnd})}else{var $activeFrame=activeFrame[STAGE_FRAME_KEY],$prevActiveFrame=data[lastActiveIndex]&&activeIndex!==lastActiveIndex?data[lastActiveIndex][STAGE_FRAME_KEY]:null;fade($activeFrame,$prevActiveFrame,$stageFrame,{time:time,method:opts.transition,onEnd:onEnd},fadeStack)}arrsUpdate()};that.showNav=function(silent,options,time){thumbArrUpdate();if(o_nav){navUpdate();var guessIndex=limitIndex(activeIndex+minMaxLimit(dirtyIndex-lastActiveIndex,-1,1));slideNavShaft({time:time,coo:guessIndex!==activeIndex&&options.coo,guessIndex:typeof options.coo!=="undefined"?guessIndex:activeIndex,keep:silent});if(o_navThumbs)slideThumbBorder(time)}};that.show=function(options){that.longPress.singlePressInProgress=true;var index=calcActiveIndex(options);calcGlobalIndexes(index);var time=calcTime(options);var _activeFrame=activeFrame;that.activeFrame=activeFrame=data[activeIndex];var silent=_activeFrame===activeFrame&&!options.user;that.showStage(silent,options,time);that.showNav(silent,options,time);showedFLAG=typeof lastActiveIndex!=="undefined"&&lastActiveIndex!==activeIndex;lastActiveIndex=activeIndex;that.longPress.singlePressInProgress=false;return this};that.requestFullScreen=function(){if(o_allowFullScreen&&!that.fullScreen){var isVideo=$((that.activeFrame||{}).$stageFrame||{}).hasClass("fotorama-video-container");if(isVideo){return}scrollTop=$WINDOW.scrollTop();scrollLeft=$WINDOW.scrollLeft();lockScroll($WINDOW);updateTouchTails("x",true);measuresStash=$.extend({},measures);$fotorama.addClass(fullscreenClass).appendTo($BODY.addClass(_fullscreenClass));$HTML.addClass(_fullscreenClass);unloadVideo($videoPlaying,true,true);that.fullScreen=true;if(o_nativeFullScreen){fullScreenApi.request(fotorama)}that.resize();loadImg(activeIndexes,"stage");updateFotoramaState();triggerEvent("fullscreenenter");if(!("ontouchstart"in window)){$fullscreenIcon.focus()}}return this};function cancelFullScreen(){if(that.fullScreen){that.fullScreen=false;if(FULLSCREEN){fullScreenApi.cancel(fotorama)}$BODY.removeClass(_fullscreenClass);$HTML.removeClass(_fullscreenClass);$fotorama.removeClass(fullscreenClass).insertAfter($anchor);measures=$.extend({},measuresStash);unloadVideo($videoPlaying,true,true);updateTouchTails("x",false);that.resize();loadImg(activeIndexes,"stage");lockScroll($WINDOW,scrollLeft,scrollTop);triggerEvent("fullscreenexit")}}that.cancelFullScreen=function(){if(o_nativeFullScreen&&fullScreenApi.is()){fullScreenApi.cancel(document)}else{cancelFullScreen()}return this};that.toggleFullScreen=function(){return that[(that.fullScreen?"cancel":"request")+"FullScreen"]()};that.resize=function(options){if(!data)return this;var time=arguments[1]||0,setFLAG=arguments[2];thumbsPerSlide=getThumbsInSlide($wrap,opts);extendMeasures(!that.fullScreen?optionsToLowerCase(options):{width:$(window).width(),maxwidth:null,minwidth:null,height:$(window).height(),maxheight:null,minheight:null},[measures,setFLAG||that.fullScreen||opts]);var width=measures.width,height=measures.height,ratio=measures.ratio,windowHeight=$WINDOW.height()-(o_nav?$nav.height():0);if(measureIsValid(width)){$wrap.css({width:""});$wrap.css({height:""});$stage.css({width:""});$stage.css({height:""});$stageShaft.css({width:""});$stageShaft.css({height:""});$nav.css({width:""});$nav.css({height:""});$wrap.css({minWidth:measures.minwidth||0,maxWidth:measures.maxwidth||MAX_WIDTH});if(o_nav==="dots"){$navWrap.hide()}width=measures.W=measures.w=$wrap.width();measures.nw=o_nav&&numberFromWhatever(opts.navwidth,width)||width;$stageShaft.css({width:measures.w,marginLeft:(measures.W-measures.w)/2});height=numberFromWhatever(height,windowHeight);height=height||ratio&&width/ratio;if(height){width=Math.round(width);height=measures.h=Math.round(minMaxLimit(height,numberFromWhatever(measures.minheight,windowHeight),numberFromWhatever(measures.maxheight,windowHeight)));$stage.css({width:width,height:height});if(opts.navdir==="vertical"&&!that.fullscreen){$nav.width(opts.thumbwidth+opts.thumbmargin*2)}if(opts.navdir==="horizontal"&&!that.fullscreen){$nav.height(opts.thumbheight+opts.thumbmargin*2)}if(o_nav==="dots"){$nav.width(width).height("auto");$navWrap.show()}if(opts.navdir==="vertical"&&that.fullScreen){$stage.css("height",$WINDOW.height())}if(opts.navdir==="horizontal"&&that.fullScreen){$stage.css("height",$WINDOW.height()-$nav.height())}if(o_nav){switch(opts.navdir){case"vertical":$navWrap.removeClass(navShafthorizontalClass);$navWrap.removeClass(navShaftListClass);$navWrap.addClass(navShaftVerticalClass);$nav.stop().animate({height:measures.h,width:opts.thumbwidth},time);break;case"list":$navWrap.removeClass(navShaftVerticalClass);$navWrap.removeClass(navShafthorizontalClass);$navWrap.addClass(navShaftListClass);break;default:$navWrap.removeClass(navShaftVerticalClass);$navWrap.removeClass(navShaftListClass);$navWrap.addClass(navShafthorizontalClass);$nav.stop().animate({width:measures.nw},time);break}stageShaftReposition();slideNavShaft({guessIndex:activeIndex,time:time,keep:true});if(o_navThumbs&&frameAppend.nav)slideThumbBorder(time)}measuresSetFLAG=setFLAG||true;ready.ok=true;ready()}}stageLeft=$stage.offset().left;setStagePosition();return this};that.setOptions=function(options){$.extend(opts,options);reset();return this};that.shuffle=function(){data&&shuffle(data)&&reset();return this};function setShadow($el,edge){if(o_shadows){$el.removeClass(shadowsLeftClass+" "+shadowsRightClass);$el.removeClass(shadowsTopClass+" "+shadowsBottomClass);edge&&!$videoPlaying&&$el.addClass(edge.replace(/^|\s/g," "+shadowsClass+"--"))}}that.longPress={threshold:1,count:0,thumbSlideTime:20,progress:function(){if(!this.inProgress){this.count++;this.inProgress=this.count>this.threshold}},end:function(){if(this.inProgress){this.isEnded=true}},reset:function(){this.count=0;this.inProgress=false;this.isEnded=false}};that.destroy=function(){that.cancelFullScreen();that.stopAutoplay();data=that.data=null;appendElements();activeIndexes=[];detachFrames(STAGE_FRAME_KEY);reset.ok=false;return this};that.playVideo=function(){var dataFrame=activeFrame,video=dataFrame.video,_activeIndex=activeIndex;if(typeof video==="object"&&dataFrame.videoReady){o_nativeFullScreen&&that.fullScreen&&that.cancelFullScreen();waitFor(function(){return!fullScreenApi.is()||_activeIndex!==activeIndex},function(){if(_activeIndex===activeIndex){dataFrame.$video=dataFrame.$video||$(div(videoClass)).append(createVideoFrame(video));dataFrame.$video.appendTo(dataFrame[STAGE_FRAME_KEY]);$wrap.addClass(wrapVideoClass);$videoPlaying=dataFrame.$video;stageNoMove();$arrs.blur();$fullscreenIcon.blur();triggerEvent("loadvideo")}})}return this};that.stopVideo=function(){unloadVideo($videoPlaying,true,true);return this};that.spliceByIndex=function(index,newImgObj){newImgObj.i=index+1;newImgObj.img&&$.ajax({url:newImgObj.img,type:"HEAD",success:function(){data.splice(index,1,newImgObj);reset()}})};function unloadVideo($video,unloadActiveFLAG,releaseAutoplayFLAG){if(unloadActiveFLAG){$wrap.removeClass(wrapVideoClass);$videoPlaying=false;stageNoMove()}if($video&&$video!==$videoPlaying){$video.remove();triggerEvent("unloadvideo")}if(releaseAutoplayFLAG){releaseAutoplay();changeAutoplay()}}function toggleControlsClass(FLAG){$wrap.toggleClass(wrapNoControlsClass,FLAG)}function stageCursor(e){if(stageShaftTouchTail.flow)return;var x=e?e.pageX:stageCursor.x,pointerFLAG=x&&!disableDirrection(getDirection(x))&&opts.click;if(stageCursor.p!==pointerFLAG&&$stage.toggleClass(pointerClass,pointerFLAG)){stageCursor.p=pointerFLAG;stageCursor.x=x}}$stage.on("mousemove",stageCursor);function clickToShow(showOptions){clearTimeout(clickToShow.t);if(opts.clicktransition&&opts.clicktransition!==opts.transition){setTimeout(function(){var _o_transition=opts.transition;that.setOptions({transition:opts.clicktransition});o_transition=_o_transition;clickToShow.t=setTimeout(function(){that.show(showOptions)},10)},0)}else{that.show(showOptions)}}function onStageTap(e,toggleControlsFLAG){var target=e.target,$target=$(target);if($target.hasClass(videoPlayClass)){that.playVideo()}else if(target===fullscreenIcon){that.toggleFullScreen()}else if($videoPlaying){target===videoClose&&unloadVideo($videoPlaying,true,true)}else if(!$fotorama.hasClass(fullscreenClass)){that.requestFullScreen()}}function updateTouchTails(key,value){stageShaftTouchTail[key]=navShaftTouchTail[key]=value}stageShaftTouchTail=moveOnTouch($stageShaft,{onStart:onTouchStart,onMove:function(e,result){setShadow($stage,result.edge)},onTouchEnd:onTouchEnd,onEnd:function(result){var toggleControlsFLAG;setShadow($stage);toggleControlsFLAG=(MS_POINTER&&!hoverFLAG||result.touch)&&opts.arrows;if((result.moved||toggleControlsFLAG&&result.pos!==result.newPos&&!result.control)&&result.$target[0]!==$fullscreenIcon[0]){var index=getIndexByPos(result.newPos,measures.w,opts.margin,repositionIndex);that.show({index:index,time:o_fade?o_transitionDuration:result.time,overPos:result.overPos,user:true})}else if(!result.aborted&&!result.control){onStageTap(result.startEvent,toggleControlsFLAG)}},timeLow:1,timeHigh:1,friction:2,select:"."+selectClass+", ."+selectClass+" *",$wrap:$stage,direction:"horizontal"});navShaftTouchTail=moveOnTouch($navShaft,{onStart:onTouchStart,onMove:function(e,result){setShadow($nav,result.edge)},onTouchEnd:onTouchEnd,onEnd:function(result){function onEnd(){slideNavShaft.l=result.newPos;releaseAutoplay();changeAutoplay();thumbsDraw(result.newPos,true);thumbArrUpdate()}if(!result.moved){var target=result.$target.closest("."+navFrameClass,$navShaft)[0];target&&onNavFrameClick.call(target,result.startEvent)}else if(result.pos!==result.newPos){pausedAutoplayFLAG=true;slide($navShaft,{time:result.time,pos:result.newPos,overPos:result.overPos,direction:opts.navdir,onEnd:onEnd});thumbsDraw(result.newPos);o_shadows&&setShadow($nav,findShadowEdge(result.newPos,navShaftTouchTail.min,navShaftTouchTail.max,result.dir))}else{onEnd()}},timeLow:.5,timeHigh:2,friction:5,$wrap:$nav,direction:opts.navdir});stageWheelTail=wheel($stage,{shift:true,onEnd:function(e,direction){onTouchStart();onTouchEnd();that.show({index:direction,slow:e.altKey})}});navWheelTail=wheel($nav,{onEnd:function(e,direction){onTouchStart();onTouchEnd();var newPos=stop($navShaft)+direction*.25;$navShaft.css(getTranslate(minMaxLimit(newPos,navShaftTouchTail.min,navShaftTouchTail.max),opts.navdir));o_shadows&&setShadow($nav,findShadowEdge(newPos,navShaftTouchTail.min,navShaftTouchTail.max,opts.navdir));navWheelTail.prevent={"<":newPos>=navShaftTouchTail.max,">":newPos<=navShaftTouchTail.min};clearTimeout(navWheelTail.t);navWheelTail.t=setTimeout(function(){slideNavShaft.l=newPos;thumbsDraw(newPos,true)},TOUCH_TIMEOUT);thumbsDraw(newPos)}});$wrap.hover(function(){setTimeout(function(){if(touchedFLAG)return;toggleControlsClass(!(hoverFLAG=true))},0)},function(){if(!hoverFLAG)return;toggleControlsClass(!(hoverFLAG=false))});function onNavFrameClick(e){var index=$(this).data().eq;if(opts.navtype==="thumbs"){clickToShow({index:index,slow:e.altKey,user:true,coo:e._x-$nav.offset().left})}else{clickToShow({index:index,slow:e.altKey,user:true})}}function onArrClick(e){clickToShow({index:$arrs.index(this)?">":"<",slow:e.altKey,user:true})}smartClick($arrs,function(e){stopEvent(e);onArrClick.call(this,e)},{onStart:function(){onTouchStart();stageShaftTouchTail.control=true},onTouchEnd:onTouchEnd});smartClick($thumbArrLeft,function(e){stopEvent(e);if(opts.navtype==="thumbs"){that.show("<")}else{that.showSlide("prev")}});smartClick($thumbArrRight,function(e){stopEvent(e);if(opts.navtype==="thumbs"){that.show(">")}else{that.showSlide("next")}});function addFocusOnControls(el){addFocus(el,function(){setTimeout(function(){lockScroll($stage)},0);toggleControlsClass(false)})}$arrs.each(function(){addEnterUp(this,function(e){onArrClick.call(this,e)});addFocusOnControls(this)});addEnterUp(fullscreenIcon,function(){if($fotorama.hasClass(fullscreenClass)){that.cancelFullScreen();$stageShaft.focus()}else{that.requestFullScreen();$fullscreenIcon.focus()}});addFocusOnControls(fullscreenIcon);function reset(){setData();setOptions();if(!reset.i){reset.i=true;var _startindex=opts.startindex;activeIndex=repositionIndex=dirtyIndex=lastActiveIndex=startIndex=edgeIndex(_startindex)||0}if(size){if(changeToRtl())return;if($videoPlaying){unloadVideo($videoPlaying,true)}activeIndexes=[];detachFrames(STAGE_FRAME_KEY);reset.ok=true;that.show({index:activeIndex,time:0});that.resize()}else{that.destroy()}}function changeToRtl(){if(!changeToRtl.f===o_rtl){changeToRtl.f=o_rtl;activeIndex=size-1-activeIndex;that.reverse();return true}}$.each("load push pop shift unshift reverse sort splice".split(" "),function(i,method){that[method]=function(){data=data||[];if(method!=="load"){Array.prototype[method].apply(data,arguments)}else if(arguments[0]&&typeof arguments[0]==="object"&&arguments[0].length){data=clone(arguments[0])}reset();return that}});function ready(){if(ready.ok){ready.ok=false;triggerEvent("ready")}}reset()};$.fn.fotorama=function(opts){return this.each(function(){var that=this,$fotorama=$(this),fotoramaData=$fotorama.data(),fotorama=fotoramaData.fotorama;if(!fotorama){waitFor(function(){return!isHidden(that)},function(){fotoramaData.urtext=$fotorama.html();new $.Fotorama($fotorama,$.extend({},OPTIONS,window.fotoramaDefaults,opts,fotoramaData))})}else{fotorama.setOptions(opts,true)}})};$.Fotorama.instances=[];function calculateIndexes(){$.each($.Fotorama.instances,function(index,instance){instance.index=index})}function addInstance(instance){$.Fotorama.instances.push(instance);calculateIndexes()}function hideInstance(instance){$.Fotorama.instances.splice(instance.index,1);calculateIndexes()}$.Fotorama.cache={};$.Fotorama.measures={};$=$||{};$.Fotorama=$.Fotorama||{};$.Fotorama.jst=$.Fotorama.jst||{};$.Fotorama.jst.dots=function(v){var __t,__p="",__e=_.escape;__p+='<div class="fotorama__nav__frame fotorama__nav__frame--dot" tabindex="0" role="button" data-gallery-role="nav-frame" data-nav-type="thumb" aria-label>\r\n <div class="fotorama__dot"></div>\r\n</div>';return __p};$.Fotorama.jst.frameCaption=function(v){var __t,__p="",__e=_.escape;__p+='<div class="fotorama__caption" aria-hidden="true">\r\n <div class="fotorama__caption__wrap" id="'+((__t=v.labelledby)==null?"":__t)+'">'+((__t=v.caption)==null?"":__t)+"</div>\r\n</div>\r\n";return __p};$.Fotorama.jst.style=function(v){var __t,__p="",__e=_.escape;__p+=".fotorama"+((__t=v.s)==null?"":__t)+" .fotorama__nav--thumbs .fotorama__nav__frame{\r\npadding:"+((__t=v.m)==null?"":__t)+"px;\r\nheight:"+((__t=v.h)==null?"":__t)+"px}\r\n.fotorama"+((__t=v.s)==null?"":__t)+" .fotorama__thumb-border{\r\nheight:"+((__t=v.h)==null?"":__t)+"px;\r\nborder-width:"+((__t=v.b)==null?"":__t)+"px;\r\nmargin-top:"+((__t=v.m)==null?"":__t)+"px}";return __p};$.Fotorama.jst.thumb=function(v){var __t,__p="",__e=_.escape;__p+='<div class="fotorama__nav__frame fotorama__nav__frame--thumb" tabindex="0" role="button" data-gallery-role="nav-frame" data-nav-type="thumb" aria-label>\r\n <div class="fotorama__thumb">\r\n </div>\r\n</div>';return __p}})(window,document,location,typeof jQuery!=="undefined"&&jQuery); diff --git a/lib/web/mage/adminhtml/wysiwyg/widget.js b/lib/web/mage/adminhtml/wysiwyg/widget.js index 68206fdec6201..aa38e2e1875f6 100644 --- a/lib/web/mage/adminhtml/wysiwyg/widget.js +++ b/lib/web/mage/adminhtml/wysiwyg/widget.js @@ -456,7 +456,7 @@ define([ parameters: params, onComplete: function (transport) { try { - editor = wysiwyg.activeEditor(); + editor = wysiwyg.get(this.widgetTargetId); widgetTools.onAjaxSuccess(transport); widgetTools.dialogWindow.modal('closeModal'); @@ -469,7 +469,7 @@ define([ editor.selection.select(activeNode); editor.selection.setContent(transport.responseText); } else if (this.bMark) { - wysiwyg.activeEditor().selection.moveToBookmark(this.bMark); + editor.selection.moveToBookmark(this.bMark); } } @@ -513,7 +513,7 @@ define([ * @return {null|wysiwyg.Editor|*} */ getWysiwyg: function () { - return wysiwyg.activeEditor(); + return wysiwyg.get(this.widgetTargetId); }, /** diff --git a/lib/web/mage/validation.js b/lib/web/mage/validation.js index a57191fd4aff4..73c5ef4d4f02f 100644 --- a/lib/web/mage/validation.js +++ b/lib/web/mage/validation.js @@ -1958,7 +1958,7 @@ } if (firstActive.length) { - $('html, body').animate({ + $('html, body').stop().animate({ scrollTop: firstActive.offset().top }); firstActive.focus(); diff --git a/pub/health_check.php b/pub/health_check.php index c9a4965876bb7..fc6d73daa2079 100644 --- a/pub/health_check.php +++ b/pub/health_check.php @@ -4,11 +4,17 @@ * See COPYING.txt for license details. */ +/** + * phpcs:disable PSR1.Files.SideEffects + * phpcs:disable Squiz.Functions.GlobalFunction + */ use Magento\Framework\Config\ConfigOptionsListConstants; +// phpcs:ignore Magento2.Functions.DiscouragedFunction register_shutdown_function("fatalErrorHandler"); try { + // phpcs:ignore Magento2.Security.IncludeFile require __DIR__ . '/../app/bootstrap.php'; /** @var \Magento\Framework\App\ObjectManagerFactory $objectManagerFactory */ $objectManagerFactory = \Magento\Framework\App\Bootstrap::createObjectManagerFactory(BP, []); @@ -20,6 +26,7 @@ $logger = $objectManager->get(\Psr\Log\LoggerInterface::class); } catch (\Exception $e) { http_response_code(500); + // phpcs:ignore Magento2.Security.LanguageConstruct exit(1); } @@ -35,6 +42,7 @@ } catch (\Exception $e) { http_response_code(500); $logger->error("MySQL connection failed: " . $e->getMessage()); + // phpcs:ignore Magento2.Security.LanguageConstruct exit(1); } } @@ -47,6 +55,7 @@ !isset($cacheConfig[ConfigOptionsListConstants::CONFIG_PATH_BACKEND_OPTIONS])) { http_response_code(500); $logger->error("Cache configuration is invalid"); + // phpcs:ignore Magento2.Security.LanguageConstruct exit(1); } $cacheBackendClass = $cacheConfig[ConfigOptionsListConstants::CONFIG_PATH_BACKEND]; @@ -57,6 +66,7 @@ } catch (\Exception $e) { http_response_code(500); $logger->error("Cache storage is not accessible"); + // phpcs:ignore Magento2.Security.LanguageConstruct exit(1); } } @@ -70,7 +80,7 @@ function fatalErrorHandler() { $error = error_get_last(); - if ($error !== null) { + if ($error !== null && $error['type'] === E_ERROR) { http_response_code(500); } } diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index 2035da9d58265..0609be4d7d193 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -374,6 +374,26 @@ <stringProp name="Argument.value">${__P(graphqlAddSimpleProductToCartPercentage,0)}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> + <elementProp name="graphqlApplyCouponToCartPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlApplyCouponToCartPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlApplyCouponToCartPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlCatalogBrowsingByGuestPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlCatalogBrowsingByGuestPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlCatalogBrowsingByGuestPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlCheckoutByGuestPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlCheckoutByGuestPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlCheckoutByGuestPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlCreateEmptyCartPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlCreateEmptyCartPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlCreateEmptyCartPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> <elementProp name="graphqlGetCategoryListByCategoryIdPercentage" elementType="Argument"> <stringProp name="Argument.name">graphqlGetCategoryListByCategoryIdPercentage</stringProp> <stringProp name="Argument.value">${__P(graphqlGetCategoryListByCategoryIdPercentage,0)}</stringProp> @@ -399,6 +419,11 @@ <stringProp name="Argument.value">${__P(graphqlGetConfigurableProductDetailsByProductUrlKeyPercentage,0)}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> + <elementProp name="graphqlGetEmptyCartPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlGetEmptyCartPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlGetEmptyCartPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> <elementProp name="graphqlGetListOfProductsByCategoryIdPercentage" elementType="Argument"> <stringProp name="Argument.name">graphqlGetListOfProductsByCategoryIdPercentage</stringProp> <stringProp name="Argument.value">${__P(graphqlGetListOfProductsByCategoryIdPercentage,0)}</stringProp> @@ -409,11 +434,6 @@ <stringProp name="Argument.value">${__P(graphqlGetNavigationMenuByCategoryIdPercentage,0)}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> - <elementProp name="graphqlGetProductSearchByProductNamePercentage" elementType="Argument"> - <stringProp name="Argument.name">graphqlGetProductSearchByProductNamePercentage</stringProp> - <stringProp name="Argument.value">${__P(graphqlGetProductSearchByProductNamePercentage,0)}</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - </elementProp> <elementProp name="graphqlGetProductSearchByTextAndCategoryIdPercentage" elementType="Argument"> <stringProp name="Argument.name">graphqlGetProductSearchByTextAndCategoryIdPercentage</stringProp> <stringProp name="Argument.value">${__P(graphqlGetProductSearchByTextAndCategoryIdPercentage,0)}</stringProp> @@ -429,6 +449,41 @@ <stringProp name="Argument.value">${__P(graphqlGetSimpleProductDetailsByProductUrlKeyPercentage,0)}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> + <elementProp name="graphqlRemoveConfigurableProductFromCartPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlRemoveConfigurableProductFromCartPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlRemoveConfigurableProductFromCartPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlRemoveCouponFromCartPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlRemoveCouponFromCartPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlRemoveCouponFromCartPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlRemoveSimpleProductFromCartPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlRemoveSimpleProductFromCartPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlRemoveSimpleProductFromCartPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlSetBillingAddressOnCartPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlSetBillingAddressOnCartPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlSetBillingAddressOnCartPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlSetShippingAddressOnCartPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlSetShippingAddressOnCartPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlSetShippingAddressOnCartPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlUpdateConfigurableProductQtyInCartPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlUpdateConfigurableProductQtyInCartPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlUpdateConfigurableProductQtyInCartPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlUpdateSimpleProductQtyInCartPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlUpdateSimpleProductQtyInCartPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlUpdateSimpleProductQtyInCartPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> <elementProp name="graphqlUrlInfoByUrlKeyPercentage" elementType="Argument"> <stringProp name="Argument.name">graphqlUrlInfoByUrlKeyPercentage</stringProp> <stringProp name="Argument.value">${__P(graphqlUrlInfoByUrlKeyPercentage,0)}</stringProp> @@ -741,6 +796,7 @@ props.remove("customer_emails_list"); props.remove("categories"); props.remove("cms_pages"); props.remove("cms_blocks"); +props.remove("coupon_codes"); /* This is only used when admin is enabled. */ props.put("activeAdminThread", ""); @@ -2180,6 +2236,59 @@ adminCategoryIdsList.add(vars.get("category_id"));</stringProp> <hashTree/> </hashTree> </hashTree> + + <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract coupon codes" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_coupon_codes.jmx</stringProp> + </TestFragmentController> + <hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - API Get coupon codes" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="searchCriteria[pageSize]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">10</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[pageSize]</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/coupons/search</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + </HTTPSamplerProxy> + <hashTree> + <JSR223PostProcessor guiclass="TestBeanGUI" testclass="JSR223PostProcessor" testname="PostProcessor" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">var data = JSON.parse(prev.getResponseDataAsString()); + +var couponCodes = []; + +for (var i in data.items) { + var coupon = data.items[i]; + couponCodes.push({"coupon_id":coupon.coupon_id, "rule_id":coupon.rule_id, "code": coupon.code}); + } + +props.put("coupon_codes", couponCodes); +</stringProp> + </JSR223PostProcessor> + <hashTree/> + </hashTree> + </hashTree> </hashTree> <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Validate properties and count users" enabled="true"> @@ -39185,7 +39294,7 @@ if (name == null) { <stringProp name="ThreadGroup.delay"/> <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/thread_group.jmx</stringProp></ThreadGroup> <hashTree> - <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get List of Products by category_id" enabled="true"> + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Get List of Products by category_id" enabled="true"> <intProp name="ThroughputController.style">1</intProp> <boolProp name="ThroughputController.perThread">false</boolProp> <intProp name="ThroughputController.maxThroughput">1</intProp> @@ -39210,7 +39319,7 @@ if (testLabel <hashTree/> <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> <stringProp name="BeanShellSampler.query"> - vars.put("testLabel", "Get List of Products by category_id"); + vars.put("testLabel", "GraphQL Get List of Products by category_id"); </stringProp> <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> </BeanShellSampler> @@ -39307,7 +39416,7 @@ vars.putObject("category", categories[number]); </hashTree> - <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Simple Product Details by product_url_key" enabled="true"> + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Get Simple Product Details by product_url_key" enabled="true"> <intProp name="ThroughputController.style">1</intProp> <boolProp name="ThroughputController.perThread">false</boolProp> <intProp name="ThroughputController.maxThroughput">1</intProp> @@ -39332,7 +39441,7 @@ if (testLabel <hashTree/> <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> <stringProp name="BeanShellSampler.query"> - vars.put("testLabel", "Get Simple Product Details by product_url_key"); + vars.put("testLabel", "GraphQL Get Simple Product Details by product_url_key"); </stringProp> <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> </BeanShellSampler> @@ -39431,7 +39540,7 @@ vars.put("product_sku", product.get("sku")); </hashTree> - <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Simple Product Details by name" enabled="true"> + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Get Simple Product Details by name" enabled="true"> <intProp name="ThroughputController.style">1</intProp> <boolProp name="ThroughputController.perThread">false</boolProp> <intProp name="ThroughputController.maxThroughput">1</intProp> @@ -39456,7 +39565,7 @@ if (testLabel <hashTree/> <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> <stringProp name="BeanShellSampler.query"> - vars.put("testLabel", "Get Simple Product Details by name"); + vars.put("testLabel", "GraphQL Get Simple Product Details by name"); </stringProp> <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> </BeanShellSampler> @@ -39555,7 +39664,7 @@ vars.put("product_sku", product.get("sku")); </hashTree> - <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Configurable Product Detail by product_url_key" enabled="true"> + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Get Configurable Product Detail by product_url_key" enabled="true"> <intProp name="ThroughputController.style">1</intProp> <boolProp name="ThroughputController.perThread">false</boolProp> <intProp name="ThroughputController.maxThroughput">1</intProp> @@ -39580,7 +39689,7 @@ if (testLabel <hashTree/> <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> <stringProp name="BeanShellSampler.query"> - vars.put("testLabel", "Get Configurable Product Detail by product_url_key"); + vars.put("testLabel", "GraphQL Get Configurable Product Detail by product_url_key"); </stringProp> <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> </BeanShellSampler> @@ -39679,7 +39788,7 @@ vars.put("product_sku", product.get("sku")); </hashTree> - <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Configurable Product Detail by name" enabled="true"> + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Get Configurable Product Detail by name" enabled="true"> <intProp name="ThroughputController.style">1</intProp> <boolProp name="ThroughputController.perThread">false</boolProp> <intProp name="ThroughputController.maxThroughput">1</intProp> @@ -39704,7 +39813,7 @@ if (testLabel <hashTree/> <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> <stringProp name="BeanShellSampler.query"> - vars.put("testLabel", "Get Configurable Product Detail by name"); + vars.put("testLabel", "GraphQL Get Configurable Product Detail by name"); </stringProp> <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> </BeanShellSampler> @@ -39803,11 +39912,11 @@ vars.put("product_sku", product.get("sku")); </hashTree> - <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Product Search by product_name" enabled="true"> + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Get Product Search by text and category_id" enabled="true"> <intProp name="ThroughputController.style">1</intProp> <boolProp name="ThroughputController.perThread">false</boolProp> <intProp name="ThroughputController.maxThroughput">1</intProp> - <stringProp name="ThroughputController.percentThroughput">${graphqlGetProductSearchByProductNamePercentage}</stringProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlGetProductSearchByTextAndCategoryIdPercentage}</stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> <hashTree> <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> @@ -39828,7 +39937,7 @@ if (testLabel <hashTree/> <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> <stringProp name="BeanShellSampler.query"> - vars.put("testLabel", "Get Product Search by product_name"); + vars.put("testLabel", "GraphQL Get Product Search by text and category_id"); </stringProp> <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> </BeanShellSampler> @@ -39866,33 +39975,31 @@ vars.putObject("randomIntGenerator", random); </BeanShellSampler> <hashTree/> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Simple Product Data" enabled="true"> - <stringProp name="BeanShellSampler.query"> -import java.util.Random; + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Category Data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); -Random random = vars.getObject("randomIntGenerator"); -number = random.nextInt(props.get("simple_products_list").size()); -product = props.get("simple_products_list").get(number); +var categories = props.get("categories"); +number = random.nextInt(categories.length); -vars.put("product_url_key", product.get("url_key")); -vars.put("product_id", product.get("id")); -vars.put("product_name", product.get("title")); -vars.put("product_uenc", product.get("uenc")); -vars.put("product_sku", product.get("sku")); - </stringProp> - <stringProp name="BeanShellSampler.filename"/> - <stringProp name="BeanShellSampler.parameters"/> - <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/simple_products_setup.jmx</stringProp></BeanShellSampler> +vars.put("category_url_key", categories[number].url_key); +vars.put("category_name", categories[number].name); +vars.put("category_id", categories[number].id); +vars.putObject("category", categories[number]); + </stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Product Search by product_name" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Product Search by text and category_id" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetailByName($name: String, $onServer: Boolean!) {\n products(filter: { name: { eq: $name } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.value">{"query":"query productSearch($inputText: String!, $categoryId: String) {\n products(\n pageSize:12\n search: $inputText, filter: { category_id: { eq: $categoryId } }) {\n items {\n id\n name\n small_image {\n label\n url\n }\n url_key\n price {\n regularPrice {\n amount {\n value\n currency\n }\n }\n }\n }\n total_count\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n }\n }\n }\n}","variables":{"inputText":"Product","categoryId":"${category_id}"},"operationName":"productSearch"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -39911,27 +40018,196 @@ vars.put("product_sku", product.get("sku")); <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_product_search_by_product_name.jmx</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_product_search_by_text_and_category_id.jmx</stringProp> </HTTPSamplerProxy> <hashTree> - <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> - <collectionProp name="Asserion.test_strings"> - <stringProp name="1201352014">"sku":"${product_sku}","name":"${product_name}"</stringProp> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> + <stringProp name="VAR">graphql_search_products_query_total_count</stringProp> + <stringProp name="JSONPATH">$.data.products.total_count</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count > 0" enabled="true"> + <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_search_products_query_total_count"); + +if (totalCount == null) { + Failure = true; + FailureMessage = "Not Expected \"totalCount\" to be null"; +} else { + if (Integer.parseInt(totalCount) < 1) { + Failure = true; + FailureMessage = "Expected \"totalCount\" to be greater than zero, Actual: " + totalCount; + } else { + Failure = false; + } +} + + +</stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Get Category List by category_id" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlGetCategoryListByCategoryIdPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "GraphQL Get Category List by category_id"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Category Data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); + +var categories = props.get("categories"); +number = random.nextInt(categories.length); + +vars.put("category_url_key", categories[number].url_key); +vars.put("category_name", categories[number].name); +vars.put("category_id", categories[number].id); +vars.putObject("category", categories[number]); + </stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Category List by category_id" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value"> + {"query":"query categoryList($id: Int!) {\n category(id: $id) {\n id\n children {\n id\n name\n url_key\n url_path\n children_count\n path\n image\n productImagePreview: products(pageSize: 1) {\n items {\n small_image {\n label\n url\n }\n }\n }\n }\n }\n}","variables":{"id":${category_id}},"operationName":"categoryList"} + </stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> </collectionProp> - <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> - <boolProp name="Assertion.assume_success">false</boolProp> - <intProp name="Assertion.test_type">2</intProp> - </ResponseAssertion> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_category_list_by_category_id.jmx</stringProp></HTTPSamplerProxy> + <hashTree> + <JSR223Assertion guiclass="TestBeanGUI" testclass="JSR223Assertion" testname="Assert found categories" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">var category = vars.getObject("category"); +var response = JSON.parse(prev.getResponseDataAsString()); + +assertCategoryId(category, response); +assertCategoryChildren(category, response); + +function assertCategoryId(category, response) { + if (response.data == undefined || response.data.category == undefined || response.data.category.id != category.id) { + AssertionResult.setFailureMessage("Cannot find category with id \"" + category.id + "\""); + AssertionResult.setFailure(true); + } +} + +function assertCategoryChildren(category, response) { + foundCategory = response.data && response.data.category ? response.data.category : null; + if (foundCategory) { + var childrenFound = foundCategory.children.map(function (c) {return parseInt(c.id)}); + var children = category.children.map(function (c) {return parseInt(c)}); + if (JSON.stringify(children.sort()) != JSON.stringify(childrenFound.sort())) { + AssertionResult.setFailureMessage("Cannot math children categories \"" + JSON.stringify(children) + "\" for to found one: \"" + JSON.stringify(childrenFound) + "\""); + AssertionResult.setFailure(true); + } + } + +} + +</stringProp> + </JSR223Assertion> <hashTree/> </hashTree> </hashTree> - <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Product Search by text and category_id" enabled="true"> + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Get Url Info by url_key" enabled="true"> <intProp name="ThroughputController.style">1</intProp> <boolProp name="ThroughputController.perThread">false</boolProp> <intProp name="ThroughputController.maxThroughput">1</intProp> - <stringProp name="ThroughputController.percentThroughput">${graphqlGetProductSearchByTextAndCategoryIdPercentage}</stringProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlUrlInfoByUrlKeyPercentage}</stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> <hashTree> <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> @@ -39952,7 +40228,7 @@ if (testLabel <hashTree/> <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> <stringProp name="BeanShellSampler.query"> - vars.put("testLabel", "Get Product Search by text and category_id"); + vars.put("testLabel", "GraphQL Get Url Info by url_key"); </stringProp> <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> </BeanShellSampler> @@ -40008,13 +40284,15 @@ vars.putObject("category", categories[number]); <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Product Search by text and category_id" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Url Info by url_key" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productSearch($inputText: String!, $categoryId: String) {\n products(search: $inputText, filter: { category_id: { eq: $categoryId } }) {\n items {\n id\n name\n small_image {\n label\n url\n }\n url_key\n price {\n regularPrice {\n amount {\n value\n currency\n }\n }\n }\n }\n total_count\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n }\n }\n }\n}","variables":{"inputText":"Product","categoryId":"${category_id}"},"operationName":"productSearch"}</stringProp> + <stringProp name="Argument.value"> + {"query":"query resolveUrl($urlKey: String!) {\n urlResolver(url: $urlKey) {\n type\n id\n }\n}","variables":{"urlKey":"${category_url_key}${url_suffix}"},"operationName":"resolveUrl"} + </stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -40033,48 +40311,26 @@ vars.putObject("category", categories[number]); <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_product_search_by_text_and_category_id.jmx</stringProp> - </HTTPSamplerProxy> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_url_info_by_url_key.jmx</stringProp></HTTPSamplerProxy> <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> - <stringProp name="VAR">graphql_search_products_query_total_count</stringProp> - <stringProp name="JSONPATH">$.data.products.total_count</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count > 0" enabled="true"> - <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_search_products_query_total_count"); - -if (totalCount == null) { - Failure = true; - FailureMessage = "Not Expected \"totalCount\" to be null"; -} else { - if (Integer.parseInt(totalCount) < 1) { - Failure = true; - FailureMessage = "Expected \"totalCount\" to be greater than zero, Actual: " + totalCount; - } else { - Failure = false; - } -} - - -</stringProp> - <stringProp name="BeanShellAssertion.filename"/> - <stringProp name="BeanShellAssertion.parameters"/> - <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> - </BeanShellAssertion> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="-1062388959">{"type":"CATEGORY","id":${category_id}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> <hashTree/> </hashTree> </hashTree> - <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Category List by category_id" enabled="true"> + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Get Cms Page by id" enabled="true"> <intProp name="ThroughputController.style">1</intProp> <boolProp name="ThroughputController.perThread">false</boolProp> <intProp name="ThroughputController.maxThroughput">1</intProp> - <stringProp name="ThroughputController.percentThroughput">${graphqlGetCategoryListByCategoryIdPercentage}</stringProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlGetCmsPageByIdPercentage}</stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> <hashTree> <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> @@ -40095,7 +40351,7 @@ if (testLabel <hashTree/> <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> <stringProp name="BeanShellSampler.query"> - vars.put("testLabel", "Get Category List by category_id"); + vars.put("testLabel", "GraphQL Get Cms Page by id"); </stringProp> <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> </BeanShellSampler> @@ -40133,32 +40389,29 @@ vars.putObject("randomIntGenerator", random); </BeanShellSampler> <hashTree/> - <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Category Data" enabled="true"> + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare CMS Page" enabled="true"> <stringProp name="scriptLanguage">javascript</stringProp> <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="cacheKey"/> <stringProp name="script">random = vars.getObject("randomIntGenerator"); -var categories = props.get("categories"); -number = random.nextInt(categories.length); +var cmsPages = props.get("cms_pages"); +var number = random.nextInt(cmsPages.length); -vars.put("category_url_key", categories[number].url_key); -vars.put("category_name", categories[number].name); -vars.put("category_id", categories[number].id); -vars.putObject("category", categories[number]); +vars.put("cms_page_id", cmsPages[number].id); </stringProp> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/prepare_cms_page.jmx</stringProp></JSR223Sampler> <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Category List by category_id" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Cms Page by id" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> <stringProp name="Argument.value"> - {"query":"query categoryList($id: Int!) {\n category(id: $id) {\n id\n children {\n id\n name\n url_key\n url_path\n children_count\n path\n image\n productImagePreview: products(pageSize: 1) {\n items {\n small_image {\n label\n url\n }\n }\n }\n }\n }\n}","variables":{"id":${category_id}},"operationName":"categoryList"} + {"query":"query getCmsPage($id: Int!, $onServer: Boolean!) {\n cmsPage(id: $id) {\n url_key\n content\n content_heading\n title\n page_layout\n meta_title @include(if: $onServer)\n meta_keywords @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n}","variables":{"id":${cms_page_id},"onServer":false},"operationName":"getCmsPage"} </stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> @@ -40178,51 +40431,26 @@ vars.putObject("category", categories[number]); <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_category_list_by_category_id.jmx</stringProp></HTTPSamplerProxy> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_get_cms_page_by_id.jmx</stringProp></HTTPSamplerProxy> <hashTree> - <JSR223Assertion guiclass="TestBeanGUI" testclass="JSR223Assertion" testname="Assert found categories" enabled="true"> - <stringProp name="scriptLanguage">javascript</stringProp> - <stringProp name="parameters"/> - <stringProp name="filename"/> - <stringProp name="cacheKey"/> - <stringProp name="script">var category = vars.getObject("category"); -var response = JSON.parse(prev.getResponseDataAsString()); - -assertCategoryId(category, response); -assertCategoryChildren(category, response); - -function assertCategoryId(category, response) { - if (response.data == undefined || response.data.category == undefined || response.data.category.id != category.id) { - AssertionResult.setFailureMessage("Cannot find category with id \"" + category.id + "\""); - AssertionResult.setFailure(true); - } -} - -function assertCategoryChildren(category, response) { - foundCategory = response.data && response.data.category ? response.data.category : null; - if (foundCategory) { - var childrenFound = foundCategory.children.map(function (c) {return parseInt(c.id)}); - var children = category.children.map(function (c) {return parseInt(c)}); - if (JSON.stringify(children.sort()) != JSON.stringify(childrenFound.sort())) { - AssertionResult.setFailureMessage("Cannot math children categories \"" + JSON.stringify(children) + "\" for to found one: \"" + JSON.stringify(childrenFound) + "\""); - AssertionResult.setFailure(true); - } - } - -} - -</stringProp> - </JSR223Assertion> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="jp@gc - JSON Path Assertion" enabled="true"> + <stringProp name="JSON_PATH">$.data.cmsPage.url_key</stringProp> + <stringProp name="EXPECTED_VALUE">${cms_page_id}</stringProp> + <boolProp name="JSONVALIDATION">false</boolProp> + <boolProp name="EXPECT_NULL">false</boolProp> + <boolProp name="INVERT">false</boolProp> + <boolProp name="ISREGEX">false</boolProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> <hashTree/> </hashTree> </hashTree> - <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Url Info by url_key" enabled="true"> + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Get Navigation Menu by category_id" enabled="true"> <intProp name="ThroughputController.style">1</intProp> <boolProp name="ThroughputController.perThread">false</boolProp> <intProp name="ThroughputController.maxThroughput">1</intProp> - <stringProp name="ThroughputController.percentThroughput">${graphqlUrlInfoByUrlKeyPercentage}</stringProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlGetNavigationMenuByCategoryIdPercentage}</stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> <hashTree> <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> @@ -40243,7 +40471,7 @@ if (testLabel <hashTree/> <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> <stringProp name="BeanShellSampler.query"> - vars.put("testLabel", "Get Url Info by url_key"); + vars.put("testLabel", "GraphQL Get Navigation Menu by category_id"); </stringProp> <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> </BeanShellSampler> @@ -40299,15 +40527,13 @@ vars.putObject("category", categories[number]); <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Url Info by url_key" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Navigation Menu by category_id" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value"> - {"query":"query resolveUrl($urlKey: String!) {\n urlResolver(url: $urlKey) {\n type\n id\n }\n}","variables":{"urlKey":"${category_url_key}${url_suffix}"},"operationName":"resolveUrl"} - </stringProp> + <stringProp name="Argument.value">{"query":"query navigationMenu($id: Int!) {\n category(id: $id) {\n id\n name\n product_count\n path\n children {\n id\n name\n position\n level\n url_key\n url_path\n product_count\n children_count\n path\n productImagePreview: products(pageSize: 1) {\n items {\n small_image {\n label\n url\n }\n }\n }\n }\n }\n}","variables":{"id":${category_id}},"operationName":"navigationMenu"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -40326,11 +40552,12 @@ vars.putObject("category", categories[number]); <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_url_info_by_url_key.jmx</stringProp></HTTPSamplerProxy> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_navigation_menu_by_category_id.jmx</stringProp> + </HTTPSamplerProxy> <hashTree> <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> <collectionProp name="Asserion.test_strings"> - <stringProp name="-1062388959">{"type":"CATEGORY","id":${category_id}}</stringProp> + <stringProp name="1201352014">"id":${category_id},"name":"${category_name}","product_count"</stringProp> </collectionProp> <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> <boolProp name="Assertion.assume_success">false</boolProp> @@ -40341,11 +40568,11 @@ vars.putObject("category", categories[number]); </hashTree> - <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Cms Page by id" enabled="true"> + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Create Empty Cart" enabled="true"> <intProp name="ThroughputController.style">1</intProp> <boolProp name="ThroughputController.perThread">false</boolProp> <intProp name="ThroughputController.maxThroughput">1</intProp> - <stringProp name="ThroughputController.percentThroughput">${graphqlGetCmsPageByIdPercentage}</stringProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlCreateEmptyCartPercentage}</stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> <hashTree> <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> @@ -40366,7 +40593,7 @@ if (testLabel <hashTree/> <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> <stringProp name="BeanShellSampler.query"> - vars.put("testLabel", "Get Cms Page by id"); + vars.put("testLabel", "GraphQL Create Empty Cart"); </stringProp> <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> </BeanShellSampler> @@ -40404,30 +40631,13 @@ vars.putObject("randomIntGenerator", random); </BeanShellSampler> <hashTree/> - <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare CMS Page" enabled="true"> - <stringProp name="scriptLanguage">javascript</stringProp> - <stringProp name="parameters"/> - <stringProp name="filename"/> - <stringProp name="cacheKey"/> - <stringProp name="script">random = vars.getObject("randomIntGenerator"); - -var cmsPages = props.get("cms_pages"); -var number = random.nextInt(cmsPages.length); - -vars.put("cms_page_id", cmsPages[number].id); - </stringProp> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/prepare_cms_page.jmx</stringProp></JSR223Sampler> - <hashTree/> - - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Cms Page by id" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Empty Cart" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value"> - {"query":"query getCmsPage($id: Int!, $onServer: Boolean!) {\n cmsPage(id: $id) {\n url_key\n content\n content_heading\n title\n page_layout\n meta_title @include(if: $onServer)\n meta_keywords @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n}","variables":{"id":${cms_page_id},"onServer":false},"operationName":"getCmsPage"} - </stringProp> + <stringProp name="Argument.value">{"query":"mutation {\n createEmptyCart\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -40446,26 +40656,35 @@ vars.put("cms_page_id", cmsPages[number].id); <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_get_cms_page_by_id.jmx</stringProp></HTTPSamplerProxy> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/create_empty_cart.jmx</stringProp> + </HTTPSamplerProxy> <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="jp@gc - JSON Path Assertion" enabled="true"> - <stringProp name="JSON_PATH">$.data.cmsPage.url_key</stringProp> - <stringProp name="EXPECTED_VALUE">${cms_page_id}</stringProp> - <boolProp name="JSONVALIDATION">false</boolProp> - <boolProp name="EXPECT_NULL">false</boolProp> - <boolProp name="INVERT">false</boolProp> - <boolProp name="ISREGEX">false</boolProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract cart id" enabled="true"> + <stringProp name="VAR">quote_id</stringProp> + <stringProp name="JSONPATH">$.data.createEmptyCart</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1404608713">{"data":{"createEmptyCart":"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> <hashTree/> </hashTree> </hashTree> - <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Navigation Menu by category_id" enabled="true"> + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Get Empty Cart" enabled="true"> <intProp name="ThroughputController.style">1</intProp> <boolProp name="ThroughputController.perThread">false</boolProp> <intProp name="ThroughputController.maxThroughput">1</intProp> - <stringProp name="ThroughputController.percentThroughput">${graphqlGetNavigationMenuByCategoryIdPercentage}</stringProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlGetEmptyCartPercentage}</stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> <hashTree> <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> @@ -40486,7 +40705,7 @@ if (testLabel <hashTree/> <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> <stringProp name="BeanShellSampler.query"> - vars.put("testLabel", "Get Navigation Menu by category_id"); + vars.put("testLabel", "GraphQL Get Empty Cart"); </stringProp> <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> </BeanShellSampler> @@ -40524,31 +40743,13 @@ vars.putObject("randomIntGenerator", random); </BeanShellSampler> <hashTree/> - <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Category Data" enabled="true"> - <stringProp name="scriptLanguage">javascript</stringProp> - <stringProp name="parameters"/> - <stringProp name="filename"/> - <stringProp name="cacheKey"/> - <stringProp name="script">random = vars.getObject("randomIntGenerator"); - -var categories = props.get("categories"); -number = random.nextInt(categories.length); - -vars.put("category_url_key", categories[number].url_key); -vars.put("category_name", categories[number].name); -vars.put("category_id", categories[number].id); -vars.putObject("category", categories[number]); - </stringProp> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> - <hashTree/> - - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Navigation Menu by category_id" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Empty Cart" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query navigationMenu($id: Int!) {\n category(id: $id) {\n id\n name\n product_count\n path\n children {\n id\n name\n position\n level\n url_key\n url_path\n product_count\n children_count\n path\n productImagePreview: products(pageSize: 1) {\n items {\n small_image {\n label\n url\n }\n }\n }\n }\n }\n}","variables":{"id":${category_id}},"operationName":"navigationMenu"}</stringProp> + <stringProp name="Argument.value">{"query":"mutation {\n createEmptyCart\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -40567,12 +40768,20 @@ vars.putObject("category", categories[number]); <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_navigation_menu_by_category_id.jmx</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/create_empty_cart.jmx</stringProp> </HTTPSamplerProxy> <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract cart id" enabled="true"> + <stringProp name="VAR">quote_id</stringProp> + <stringProp name="JSONPATH">$.data.createEmptyCart</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> <collectionProp name="Asserion.test_strings"> - <stringProp name="1201352014">"id":${category_id},"name":"${category_name}","product_count"</stringProp> + <stringProp name="1404608713">{"data":{"createEmptyCart":"</stringProp> </collectionProp> <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> <boolProp name="Assertion.assume_success">false</boolProp> @@ -40580,14 +40789,53 @@ vars.putObject("category", categories[number]); </ResponseAssertion> <hashTree/> </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Empty Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n cart(cart_id: \"${quote_id}\") {\n items {\n id\n quantity\n product {\n sku\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_empty_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1901638450">{"data":{"cart":{"items":[]}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> </hashTree> - <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Add Simple Product To Cart" enabled="true"> + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Set Shipping Address On Cart" enabled="true"> <intProp name="ThroughputController.style">1</intProp> <boolProp name="ThroughputController.perThread">false</boolProp> <intProp name="ThroughputController.maxThroughput">1</intProp> - <stringProp name="ThroughputController.percentThroughput">${graphqlAddSimpleProductToCartPercentage}</stringProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlSetShippingAddressOnCartPercentage}</stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> <hashTree> <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> @@ -40608,7 +40856,7 @@ if (testLabel <hashTree/> <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> <stringProp name="BeanShellSampler.query"> - vars.put("testLabel", "Add Simple Product To Cart"); + vars.put("testLabel", "GraphQL Set Shipping Address On Cart"); </stringProp> <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> </BeanShellSampler> @@ -40646,26 +40894,6 @@ vars.putObject("randomIntGenerator", random); </BeanShellSampler> <hashTree/> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Simple Product Data" enabled="true"> - <stringProp name="BeanShellSampler.query"> -import java.util.Random; - -Random random = vars.getObject("randomIntGenerator"); -number = random.nextInt(props.get("simple_products_list").size()); -product = props.get("simple_products_list").get(number); - -vars.put("product_url_key", product.get("url_key")); -vars.put("product_id", product.get("id")); -vars.put("product_name", product.get("title")); -vars.put("product_uenc", product.get("uenc")); -vars.put("product_sku", product.get("sku")); - </stringProp> - <stringProp name="BeanShellSampler.filename"/> - <stringProp name="BeanShellSampler.parameters"/> - <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/simple_products_setup.jmx</stringProp></BeanShellSampler> - <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Empty Cart" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> @@ -40702,15 +40930,24 @@ vars.put("product_sku", product.get("sku")); <stringProp name="SUBJECT">BODY</stringProp> </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1404608713">{"data":{"createEmptyCart":"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> </hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Simple Product To Cart" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Empty Cart" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"mutation { \n addSimpleProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cartItems: [\n {\n data: {\n qty: 2\n sku: \"${product_sku}\"\n }\n }\n ]\n }\n ) {\n cart {\n items {\n qty\n product {\n sku\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n cart(cart_id: \"${quote_id}\") {\n items {\n id\n quantity\n product {\n sku\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -40729,28 +40966,27 @@ vars.put("product_sku", product.get("sku")); <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/add_simple_product_to_cart.jmx</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_empty_cart.jmx</stringProp> </HTTPSamplerProxy> <hashTree> <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> <collectionProp name="Asserion.test_strings"> - <stringProp name="1421843282">addSimpleProductsToCart</stringProp> - <stringProp name="-1173443935">"sku":"${product_sku}"</stringProp> + <stringProp name="1901638450">{"data":{"cart":{"items":[]}}}</stringProp> </collectionProp> <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> <boolProp name="Assertion.assume_success">false</boolProp> - <intProp name="Assertion.test_type">2</intProp> + <intProp name="Assertion.test_type">8</intProp> </ResponseAssertion> <hashTree/> </hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Cart" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Set Shipping Address On Cart" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n cart(cart_id: \"${quote_id}\") {\n items {\n id\n qty\n product {\n sku\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"mutation {\n setShippingAddressesOnCart(\n input: {\n cart_id: \"${quote_id}\"\n shipping_addresses: [\n {\n address: {\n firstname: \"test firstname\"\n lastname: \"test lastname\"\n company: \"test company\"\n street: [\"test street 1\", \"test street 2\"]\n city: \"test city\"\n region: \"test region\"\n postcode: \"887766\"\n country_code: \"US\"\n telephone: \"88776655\"\n save_in_address_book: false\n }\n }\n ]\n }\n ) {\n cart {\n shipping_addresses {\n firstname\n lastname\n company\n street\n city\n postcode\n telephone\n country {\n code\n label\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -40769,26 +41005,139 @@ vars.put("product_sku", product.get("sku")); <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_cart.jmx</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/set_shipping_address_on_cart.jmx</stringProp> </HTTPSamplerProxy> <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract item id" enabled="true"> - <stringProp name="VAR">item_id</stringProp> - <stringProp name="JSONPATH">$.data.cart.items[0].id</stringProp> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="-1671866339">{"data":{"setShippingAddressesOnCart":{"cart":{"shipping_addresses":[{"firstname":"test firstname","lastname":"test lastname","company":"test company","street":["test street 1","test street 2"],"city":"test city","postcode":"887766","telephone":"88776655","country":{"code":"US","label":"US"}}]}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Set Billing Address On Cart" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlSetBillingAddressOnCartPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "GraphQL Set Billing Address On Cart"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Empty Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n createEmptyCart\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/create_empty_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract cart id" enabled="true"> + <stringProp name="VAR">quote_id</stringProp> + <stringProp name="JSONPATH">$.data.createEmptyCart</stringProp> <stringProp name="DEFAULT"/> <stringProp name="VARIABLE"/> <stringProp name="SUBJECT">BODY</stringProp> </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1404608713">{"data":{"createEmptyCart":"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> </hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Remove Simple Product From Cart" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Empty Cart" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"mutation {\n removeItemFromCart(\n input: {\n cart_id: \"${quote_id}\"\n cart_item_id: ${item_id}\n }\n ) {\n cart {\n items {\n qty\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n cart(cart_id: \"${quote_id}\") {\n items {\n id\n quantity\n product {\n sku\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -40807,12 +41156,51 @@ vars.put("product_sku", product.get("sku")); <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/remove_simple_product_from_cart.jmx</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_empty_cart.jmx</stringProp> </HTTPSamplerProxy> <hashTree> <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> <collectionProp name="Asserion.test_strings"> - <stringProp name="1452665323">{"data":{"removeItemFromCart":{"cart":{"items":[]}}}}</stringProp> + <stringProp name="1901638450">{"data":{"cart":{"items":[]}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Set Billing Address On Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n setBillingAddressOnCart(\n input: {\n cart_id: \"${quote_id}\"\n billing_address: {\n address: {\n firstname: \"test firstname\"\n lastname: \"test lastname\"\n company: \"test company\"\n street: [\"test street 1\", \"test street 2\"]\n city: \"test city\"\n region: \"test region\"\n postcode: \"887766\"\n country_code: \"US\"\n telephone: \"88776655\"\n save_in_address_book: false\n }\n }\n }\n ) {\n cart {\n billing_address {\n firstname\n lastname\n company\n street\n city\n postcode\n telephone\n country {\n code\n label\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/set_billing_address_on_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1147076914">{"data":{"setBillingAddressOnCart":{"cart":{"billing_address":{"firstname":"test firstname","lastname":"test lastname","company":"test company","street":["test street 1","test street 2"],"city":"test city","postcode":"887766","telephone":"88776655","country":{"code":"US","label":"US"}}}}}}</stringProp> </collectionProp> <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> <boolProp name="Assertion.assume_success">false</boolProp> @@ -40823,11 +41211,11 @@ vars.put("product_sku", product.get("sku")); </hashTree> - <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Add Configurable Product To Cart" enabled="true"> + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Add Simple Product To Cart" enabled="true"> <intProp name="ThroughputController.style">1</intProp> <boolProp name="ThroughputController.perThread">false</boolProp> <intProp name="ThroughputController.maxThroughput">1</intProp> - <stringProp name="ThroughputController.percentThroughput">${graphqlAddConfigurableProductToCartPercentage}</stringProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlAddSimpleProductToCartPercentage}</stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> <hashTree> <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> @@ -40848,7 +41236,7 @@ if (testLabel <hashTree/> <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> <stringProp name="BeanShellSampler.query"> - vars.put("testLabel", "Add Configurable Product To Cart"); + vars.put("testLabel", "GraphQL Add Simple Product To Cart"); </stringProp> <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> </BeanShellSampler> @@ -40886,13 +41274,60 @@ vars.putObject("randomIntGenerator", random); </BeanShellSampler> <hashTree/> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Configurable Product Data" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Empty Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n createEmptyCart\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/create_empty_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract cart id" enabled="true"> + <stringProp name="VAR">quote_id</stringProp> + <stringProp name="JSONPATH">$.data.createEmptyCart</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1404608713">{"data":{"createEmptyCart":"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Simple Product Data" enabled="true"> <stringProp name="BeanShellSampler.query"> import java.util.Random; Random random = vars.getObject("randomIntGenerator"); -number = random.nextInt(props.get("configurable_products_list").size()); -product = props.get("configurable_products_list").get(number); +number = random.nextInt(props.get("simple_products_list").size()); +product = props.get("simple_products_list").get(number); vars.put("product_url_key", product.get("url_key")); vars.put("product_id", product.get("id")); @@ -40903,16 +41338,16 @@ vars.put("product_sku", product.get("sku")); <stringProp name="BeanShellSampler.filename"/> <stringProp name="BeanShellSampler.parameters"/> <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/configurable_products_setup.jmx</stringProp></BeanShellSampler> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/simple_products_setup.jmx</stringProp></BeanShellSampler> <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Configurable Product Details by name" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Simple Product To Cart" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetailByName($name: String, $onServer: Boolean!) {\n products(filter: { name: { eq: $name } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.value">{"query":"mutation { \n addSimpleProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cartItems: [\n {\n data: {\n quantity: 2\n sku: \"${product_sku}\"\n }\n }\n ]\n }\n ) {\n cart {\n items {\n quantity\n product {\n sku\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -40931,29 +41366,86 @@ vars.put("product_sku", product.get("sku")); <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_configurable_product_details_by_name.jmx</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/add_simple_product_to_cart.jmx</stringProp> </HTTPSamplerProxy> <hashTree> <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> <collectionProp name="Asserion.test_strings"> - <stringProp name="1201352014">"sku":"${product_sku}","name":"${product_name}"</stringProp> + <stringProp name="1421843282">addSimpleProductsToCart</stringProp> + <stringProp name="-1173443935">"sku":"${product_sku}"</stringProp> </collectionProp> <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> <boolProp name="Assertion.assume_success">false</boolProp> <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Add Configurable Product To Cart" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlAddConfigurableProductToCartPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "GraphQL Add Configurable Product To Cart"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract Configurable Product option" enabled="true"> - <stringProp name="VAR">product_option</stringProp> - <stringProp name="JSONPATH">$.data.products.items[0].variants[0].product.sku</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/extract_configurable_product_option.jmx</stringProp></com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> <hashTree/> - </hashTree> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Empty Cart" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> @@ -40990,15 +41482,92 @@ vars.put("product_sku", product.get("sku")); <stringProp name="SUBJECT">BODY</stringProp> </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1404608713">{"data":{"createEmptyCart":"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> </hashTree> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Configurable Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("configurable_products_list").size()); +product = props.get("configurable_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/configurable_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Configurable Product Details by name" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($name: String, $onServer: Boolean!) {\n products(filter: { name: { eq: $name } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_configurable_product_details_by_name.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"sku":"${product_sku}","name":"${product_name}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract Configurable Product option" enabled="true"> + <stringProp name="VAR">product_option</stringProp> + <stringProp name="JSONPATH">$.data.products.items[0].variants[0].product.sku</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/extract_configurable_product_option.jmx</stringProp></com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + </hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Configurable Product To Cart" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"mutation {\n addConfigurableProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cartItems: [\n {\n variant_sku: \"${product_option}\"\n data: {\n qty: 2\n sku: \"${product_option}\"\n }\n }\n ]\n }\n ) {\n cart {\n items {\n id\n qty\n product {\n name\n sku\n }\n ... on ConfigurableCartItem {\n configurable_options {\n option_label\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"mutation {\n addConfigurableProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cartItems: [\n {\n variant_sku: \"${product_option}\"\n data: {\n quantity: 2\n sku: \"${product_option}\"\n }\n }\n ]\n }\n ) {\n cart {\n items {\n id\n quantity\n product {\n name\n sku\n }\n ... on ConfigurableCartItem {\n configurable_options {\n option_label\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -41031,14 +41600,79 @@ vars.put("product_sku", product.get("sku")); </ResponseAssertion> <hashTree/> </hashTree> + </hashTree> + - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Cart" enabled="true"> + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Update Simple Product Qty In Cart" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlUpdateSimpleProductQtyInCartPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "GraphQL Update Simple Product Qty In Cart"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Empty Cart" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n cart(cart_id: \"${quote_id}\") {\n items {\n id\n qty\n product {\n sku\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"mutation {\n createEmptyCart\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -41057,26 +41691,55 @@ vars.put("product_sku", product.get("sku")); <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_cart.jmx</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/create_empty_cart.jmx</stringProp> </HTTPSamplerProxy> <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract item id" enabled="true"> - <stringProp name="VAR">item_id</stringProp> - <stringProp name="JSONPATH">$.data.cart.items[0].id</stringProp> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract cart id" enabled="true"> + <stringProp name="VAR">quote_id</stringProp> + <stringProp name="JSONPATH">$.data.createEmptyCart</stringProp> <stringProp name="DEFAULT"/> <stringProp name="VARIABLE"/> <stringProp name="SUBJECT">BODY</stringProp> </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1404608713">{"data":{"createEmptyCart":"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> </hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Remove Configurable Product From Cart" enabled="true"> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Simple Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("simple_products_list").size()); +product = props.get("simple_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/simple_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Simple Product To Cart" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"mutation {\n removeItemFromCart(\n input: {\n cart_id: \"${quote_id}\"\n cart_item_id: ${item_id}\n }\n ) {\n cart {\n items {\n qty\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"mutation { \n addSimpleProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cartItems: [\n {\n data: {\n quantity: 2\n sku: \"${product_sku}\"\n }\n }\n ]\n }\n ) {\n cart {\n items {\n quantity\n product {\n sku\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -41095,12 +41758,2572 @@ vars.put("product_sku", product.get("sku")); <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/remove_configurable_product_from_cart.jmx</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/add_simple_product_to_cart.jmx</stringProp> </HTTPSamplerProxy> <hashTree> <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> <collectionProp name="Asserion.test_strings"> - <stringProp name="1452665323">{"data":{"removeItemFromCart":{"cart":{"items":[]}}}}</stringProp> + <stringProp name="1421843282">addSimpleProductsToCart</stringProp> + <stringProp name="-1173443935">"sku":"${product_sku}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n cart(cart_id: \"${quote_id}\") {\n items {\n id\n quantity\n product {\n sku\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract item id" enabled="true"> + <stringProp name="VAR">item_id</stringProp> + <stringProp name="JSONPATH">$.data.cart.items[0].id</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="-1486007127">{"data":{"cart":{"items":</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Update Simple Product qty In Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n updateCartItems(input: {\n cart_id: \"${quote_id}\"\n cart_items: [\n {\n cart_item_id: ${item_id}\n quantity: 5\n }\n ]\n }) {\n cart {\n items {\n id\n quantity\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/update_simple_product_qty_in_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="664196114">{"data":{"updateCartItems":{"cart":{"items":[{"id":"${item_id}","quantity":5}]}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Update Configurable Product Qty In Cart" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlUpdateConfigurableProductQtyInCartPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "GraphQL Update Configurable Product Qty In Cart"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Empty Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n createEmptyCart\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/create_empty_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract cart id" enabled="true"> + <stringProp name="VAR">quote_id</stringProp> + <stringProp name="JSONPATH">$.data.createEmptyCart</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1404608713">{"data":{"createEmptyCart":"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Configurable Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("configurable_products_list").size()); +product = props.get("configurable_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/configurable_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Configurable Product Details by name" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($name: String, $onServer: Boolean!) {\n products(filter: { name: { eq: $name } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_configurable_product_details_by_name.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"sku":"${product_sku}","name":"${product_name}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract Configurable Product option" enabled="true"> + <stringProp name="VAR">product_option</stringProp> + <stringProp name="JSONPATH">$.data.products.items[0].variants[0].product.sku</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/extract_configurable_product_option.jmx</stringProp></com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Configurable Product To Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n addConfigurableProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cartItems: [\n {\n variant_sku: \"${product_option}\"\n data: {\n quantity: 2\n sku: \"${product_option}\"\n }\n }\n ]\n }\n ) {\n cart {\n items {\n id\n quantity\n product {\n name\n sku\n }\n ... on ConfigurableCartItem {\n configurable_options {\n option_label\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/add_configurable_product_to_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1421843282">addConfigurableProductsToCart</stringProp> + <stringProp name="675049292">"sku":"${product_option}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n cart(cart_id: \"${quote_id}\") {\n items {\n id\n quantity\n product {\n sku\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract item id" enabled="true"> + <stringProp name="VAR">item_id</stringProp> + <stringProp name="JSONPATH">$.data.cart.items[0].id</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="-1486007127">{"data":{"cart":{"items":</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Update Configurable Product qty In Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n updateCartItems(input: {\n cart_id: \"${quote_id}\"\n cart_items: [\n {\n cart_item_id: ${item_id}\n quantity: 5\n }\n ]\n }) {\n cart {\n items {\n id\n quantity\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/update_configurable_product_qty_in_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="664196114">{"data":{"updateCartItems":{"cart":{"items":[{"id":"${item_id}","quantity":5}]}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Remove Simple Product From Cart" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlRemoveSimpleProductFromCartPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "GraphQL Remove Simple Product From Cart"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Empty Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n createEmptyCart\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/create_empty_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract cart id" enabled="true"> + <stringProp name="VAR">quote_id</stringProp> + <stringProp name="JSONPATH">$.data.createEmptyCart</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1404608713">{"data":{"createEmptyCart":"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Simple Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("simple_products_list").size()); +product = props.get("simple_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/simple_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Simple Product To Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation { \n addSimpleProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cartItems: [\n {\n data: {\n quantity: 2\n sku: \"${product_sku}\"\n }\n }\n ]\n }\n ) {\n cart {\n items {\n quantity\n product {\n sku\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/add_simple_product_to_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1421843282">addSimpleProductsToCart</stringProp> + <stringProp name="-1173443935">"sku":"${product_sku}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n cart(cart_id: \"${quote_id}\") {\n items {\n id\n quantity\n product {\n sku\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract item id" enabled="true"> + <stringProp name="VAR">item_id</stringProp> + <stringProp name="JSONPATH">$.data.cart.items[0].id</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="-1486007127">{"data":{"cart":{"items":</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Remove Simple Product From Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n removeItemFromCart(\n input: {\n cart_id: \"${quote_id}\"\n cart_item_id: ${item_id}\n }\n ) {\n cart {\n items {\n quantity\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/remove_simple_product_from_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1452665323">{"data":{"removeItemFromCart":{"cart":{"items":[]}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Remove Configurable Product From Cart" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlRemoveConfigurableProductFromCartPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "GraphQL Remove Configurable Product From Cart"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Empty Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n createEmptyCart\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/create_empty_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract cart id" enabled="true"> + <stringProp name="VAR">quote_id</stringProp> + <stringProp name="JSONPATH">$.data.createEmptyCart</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1404608713">{"data":{"createEmptyCart":"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Configurable Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("configurable_products_list").size()); +product = props.get("configurable_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/configurable_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Configurable Product Details by name" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($name: String, $onServer: Boolean!) {\n products(filter: { name: { eq: $name } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_configurable_product_details_by_name.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"sku":"${product_sku}","name":"${product_name}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract Configurable Product option" enabled="true"> + <stringProp name="VAR">product_option</stringProp> + <stringProp name="JSONPATH">$.data.products.items[0].variants[0].product.sku</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/extract_configurable_product_option.jmx</stringProp></com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Configurable Product To Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n addConfigurableProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cartItems: [\n {\n variant_sku: \"${product_option}\"\n data: {\n quantity: 2\n sku: \"${product_option}\"\n }\n }\n ]\n }\n ) {\n cart {\n items {\n id\n quantity\n product {\n name\n sku\n }\n ... on ConfigurableCartItem {\n configurable_options {\n option_label\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/add_configurable_product_to_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1421843282">addConfigurableProductsToCart</stringProp> + <stringProp name="675049292">"sku":"${product_option}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n cart(cart_id: \"${quote_id}\") {\n items {\n id\n quantity\n product {\n sku\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract item id" enabled="true"> + <stringProp name="VAR">item_id</stringProp> + <stringProp name="JSONPATH">$.data.cart.items[0].id</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="-1486007127">{"data":{"cart":{"items":</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Remove Configurable Product From Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n removeItemFromCart(\n input: {\n cart_id: \"${quote_id}\"\n cart_item_id: ${item_id}\n }\n ) {\n cart {\n items {\n quantity\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/remove_configurable_product_from_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1452665323">{"data":{"removeItemFromCart":{"cart":{"items":[]}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Apply Coupon To Cart" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlApplyCouponToCartPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "GraphQL Apply Coupon To Cart"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Empty Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n createEmptyCart\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/create_empty_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract cart id" enabled="true"> + <stringProp name="VAR">quote_id</stringProp> + <stringProp name="JSONPATH">$.data.createEmptyCart</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1404608713">{"data":{"createEmptyCart":"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Simple Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("simple_products_list").size()); +product = props.get("simple_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/simple_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Simple Product To Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation { \n addSimpleProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cartItems: [\n {\n data: {\n quantity: 2\n sku: \"${product_sku}\"\n }\n }\n ]\n }\n ) {\n cart {\n items {\n quantity\n product {\n sku\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/add_simple_product_to_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1421843282">addSimpleProductsToCart</stringProp> + <stringProp name="-1173443935">"sku":"${product_sku}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Coupon Code Data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); + +var coupons = props.get("coupon_codes"); +number = random.nextInt(coupons.length); + +vars.put("coupon_code", coupons[number].code); + </stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_coupon_code_setup.jmx</stringProp> + </JSR223Sampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Apply Coupon To Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n applyCouponToCart(input: {cart_id: \"${quote_id}\", coupon_code: \"${coupon_code}\"}) {\n cart {\n applied_coupon {\n code\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/apply_coupon_to_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1026466978">{"data":{"applyCouponToCart":{"cart":{"applied_coupon":{"code":"${coupon_code}"}}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Remove Coupon From Cart" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlRemoveCouponFromCartPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "GraphQL Remove Coupon From Cart"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Empty Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n createEmptyCart\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/create_empty_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract cart id" enabled="true"> + <stringProp name="VAR">quote_id</stringProp> + <stringProp name="JSONPATH">$.data.createEmptyCart</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1404608713">{"data":{"createEmptyCart":"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Simple Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("simple_products_list").size()); +product = props.get("simple_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/simple_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Simple Product To Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation { \n addSimpleProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cartItems: [\n {\n data: {\n quantity: 2\n sku: \"${product_sku}\"\n }\n }\n ]\n }\n ) {\n cart {\n items {\n quantity\n product {\n sku\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/add_simple_product_to_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1421843282">addSimpleProductsToCart</stringProp> + <stringProp name="-1173443935">"sku":"${product_sku}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Coupon Code Data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); + +var coupons = props.get("coupon_codes"); +number = random.nextInt(coupons.length); + +vars.put("coupon_code", coupons[number].code); + </stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_coupon_code_setup.jmx</stringProp> + </JSR223Sampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Apply Coupon To Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n applyCouponToCart(input: {cart_id: \"${quote_id}\", coupon_code: \"${coupon_code}\"}) {\n cart {\n applied_coupon {\n code\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/apply_coupon_to_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1026466978">{"data":{"applyCouponToCart":{"cart":{"applied_coupon":{"code":"${coupon_code}"}}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Remove Coupon From Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n removeCouponFromCart(input: {cart_id: \"${quote_id}\"}) {\n cart {\n applied_coupon {\n code\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/remove_coupon_from_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="-76201335">{"data":{"removeCouponFromCart":{"cart":{"applied_coupon":null}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Catalog Browsing By Guest" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlCatalogBrowsingByGuestPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "GraphQL Catalog Browsing By Guest"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Category Data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); + +var categories = props.get("categories"); +number = random.nextInt(categories.length); + +vars.put("category_url_key", categories[number].url_key); +vars.put("category_name", categories[number].name); +vars.put("category_id", categories[number].id); +vars.putObject("category", categories[number]); + </stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Navigation Menu by category_id" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query navigationMenu($id: Int!) {\n category(id: $id) {\n id\n name\n product_count\n path\n children {\n id\n name\n position\n level\n url_key\n url_path\n product_count\n children_count\n path\n productImagePreview: products(pageSize: 1) {\n items {\n small_image {\n label\n url\n }\n }\n }\n }\n }\n}","variables":{"id":${category_id}},"operationName":"navigationMenu"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_navigation_menu_by_category_id.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"id":${category_id},"name":"${category_name}","product_count"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Product Search by text and category_id" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query productSearch($inputText: String!, $categoryId: String) {\n products(\n pageSize:12\n search: $inputText, filter: { category_id: { eq: $categoryId } }) {\n items {\n id\n name\n small_image {\n label\n url\n }\n url_key\n price {\n regularPrice {\n amount {\n value\n currency\n }\n }\n }\n }\n total_count\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n }\n }\n }\n}","variables":{"inputText":"Product","categoryId":"${category_id}"},"operationName":"productSearch"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_product_search_by_text_and_category_id.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> + <stringProp name="VAR">graphql_search_products_query_total_count</stringProp> + <stringProp name="JSONPATH">$.data.products.total_count</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count > 0" enabled="true"> + <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_search_products_query_total_count"); + +if (totalCount == null) { + Failure = true; + FailureMessage = "Not Expected \"totalCount\" to be null"; +} else { + if (Integer.parseInt(totalCount) < 1) { + Failure = true; + FailureMessage = "Expected \"totalCount\" to be greater than zero, Actual: " + totalCount; + } else { + Failure = false; + } +} + + +</stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Url Info by url_key" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value"> + {"query":"query resolveUrl($urlKey: String!) {\n urlResolver(url: $urlKey) {\n type\n id\n }\n}","variables":{"urlKey":"${category_url_key}${url_suffix}"},"operationName":"resolveUrl"} + </stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_url_info_by_url_key.jmx</stringProp></HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="-1062388959">{"type":"CATEGORY","id":${category_id}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get List of Products by category_id" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query category($id: Int!, $currentPage: Int, $pageSize: Int) {\n category(id: $id) {\n product_count\n description\n url_key\n name\n id\n breadcrumbs {\n category_name\n category_url_key\n __typename\n }\n products(pageSize: $pageSize, currentPage: $currentPage) {\n total_count\n items {\n id\n name\n # small_image\n # short_description\n url_key\n special_price\n special_from_date\n special_to_date\n price {\n regularPrice {\n amount {\n value\n currency\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n}\n","variables":{"id":${category_id},"currentPage":1,"pageSize":12},"operationName":"category"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_list_of_products_by_category_id.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"name":"${category_name}","id":${category_id},</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Configurable Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("configurable_products_list").size()); +product = props.get("configurable_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/configurable_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Configurable Product Details by name" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($name: String, $onServer: Boolean!) {\n products(filter: { name: { eq: $name } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_configurable_product_details_by_name.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"sku":"${product_sku}","name":"${product_name}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Configurable Product Details by product_url_key" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query productDetail($urlKey: String, $onServer: Boolean!) {\n productDetail: products(filter: { url_key: { eq: $urlKey } }) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"urlKey":"${product_url_key}","onServer":false},"operationName":"productDetail"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_configurable_product_details_by_product_url_key.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"sku":"${product_sku}","name":"${product_name}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Simple Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("simple_products_list").size()); +product = props.get("simple_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/simple_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Simple Product Details by name" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query productDetail($name: String, $onServer: Boolean!) {\n productDetail: products(filter: { name: { eq: $name } }) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetail"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_simple_product_details_by_name.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"sku":"${product_sku}","name":"${product_name}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Simple Product Details by product_url_key" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query productDetail($urlKey: String, $onServer: Boolean!) {\n productDetail: products(filter: { url_key: { eq: $urlKey } }) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"urlKey":"${product_url_key}","onServer":false},"operationName":"productDetail"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_simple_product_details_by_product_url_key.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"sku":"${product_sku}","name":"${product_name}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare CMS Page" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); + +var cmsPages = props.get("cms_pages"); +var number = random.nextInt(cmsPages.length); + +vars.put("cms_page_id", cmsPages[number].id); + </stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/prepare_cms_page.jmx</stringProp></JSR223Sampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Cms Page by id" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value"> + {"query":"query getCmsPage($id: Int!, $onServer: Boolean!) {\n cmsPage(id: $id) {\n url_key\n content\n content_heading\n title\n page_layout\n meta_title @include(if: $onServer)\n meta_keywords @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n}","variables":{"id":${cms_page_id},"onServer":false},"operationName":"getCmsPage"} + </stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_get_cms_page_by_id.jmx</stringProp></HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="jp@gc - JSON Path Assertion" enabled="true"> + <stringProp name="JSON_PATH">$.data.cmsPage.url_key</stringProp> + <stringProp name="EXPECTED_VALUE">${cms_page_id}</stringProp> + <boolProp name="JSONVALIDATION">false</boolProp> + <boolProp name="EXPECT_NULL">false</boolProp> + <boolProp name="INVERT">false</boolProp> + <boolProp name="ISREGEX">false</boolProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Checkout By Guest" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlCheckoutByGuestPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "GraphQL Checkout By Guest"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Empty Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n createEmptyCart\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/create_empty_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract cart id" enabled="true"> + <stringProp name="VAR">quote_id</stringProp> + <stringProp name="JSONPATH">$.data.createEmptyCart</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1404608713">{"data":{"createEmptyCart":"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Empty Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n cart(cart_id: \"${quote_id}\") {\n items {\n id\n quantity\n product {\n sku\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_empty_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1901638450">{"data":{"cart":{"items":[]}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Configurable Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("configurable_products_list").size()); +product = props.get("configurable_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/configurable_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Configurable Product Details by name" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($name: String, $onServer: Boolean!) {\n products(filter: { name: { eq: $name } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_configurable_product_details_by_name.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"sku":"${product_sku}","name":"${product_name}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract Configurable Product option" enabled="true"> + <stringProp name="VAR">product_option</stringProp> + <stringProp name="JSONPATH">$.data.products.items[0].variants[0].product.sku</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/extract_configurable_product_option.jmx</stringProp></com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Configurable Product To Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n addConfigurableProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cartItems: [\n {\n variant_sku: \"${product_option}\"\n data: {\n quantity: 2\n sku: \"${product_option}\"\n }\n }\n ]\n }\n ) {\n cart {\n items {\n id\n quantity\n product {\n name\n sku\n }\n ... on ConfigurableCartItem {\n configurable_options {\n option_label\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/add_configurable_product_to_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1421843282">addConfigurableProductsToCart</stringProp> + <stringProp name="675049292">"sku":"${product_option}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Simple Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("simple_products_list").size()); +product = props.get("simple_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/simple_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Simple Product To Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation { \n addSimpleProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cartItems: [\n {\n data: {\n quantity: 2\n sku: \"${product_sku}\"\n }\n }\n ]\n }\n ) {\n cart {\n items {\n quantity\n product {\n sku\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/add_simple_product_to_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1421843282">addSimpleProductsToCart</stringProp> + <stringProp name="-1173443935">"sku":"${product_sku}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Set Billing Address On Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n setBillingAddressOnCart(\n input: {\n cart_id: \"${quote_id}\"\n billing_address: {\n address: {\n firstname: \"test firstname\"\n lastname: \"test lastname\"\n company: \"test company\"\n street: [\"test street 1\", \"test street 2\"]\n city: \"test city\"\n region: \"test region\"\n postcode: \"887766\"\n country_code: \"US\"\n telephone: \"88776655\"\n save_in_address_book: false\n }\n }\n }\n ) {\n cart {\n billing_address {\n firstname\n lastname\n company\n street\n city\n postcode\n telephone\n country {\n code\n label\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/set_billing_address_on_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1147076914">{"data":{"setBillingAddressOnCart":{"cart":{"billing_address":{"firstname":"test firstname","lastname":"test lastname","company":"test company","street":["test street 1","test street 2"],"city":"test city","postcode":"887766","telephone":"88776655","country":{"code":"US","label":"US"}}}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Set Shipping Address On Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n setShippingAddressesOnCart(\n input: {\n cart_id: \"${quote_id}\"\n shipping_addresses: [\n {\n address: {\n firstname: \"test firstname\"\n lastname: \"test lastname\"\n company: \"test company\"\n street: [\"test street 1\", \"test street 2\"]\n city: \"test city\"\n region: \"test region\"\n postcode: \"887766\"\n country_code: \"US\"\n telephone: \"88776655\"\n save_in_address_book: false\n }\n }\n ]\n }\n ) {\n cart {\n shipping_addresses {\n firstname\n lastname\n company\n street\n city\n postcode\n telephone\n country {\n code\n label\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/set_shipping_address_on_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="-1671866339">{"data":{"setShippingAddressesOnCart":{"cart":{"shipping_addresses":[{"firstname":"test firstname","lastname":"test lastname","company":"test company","street":["test street 1","test street 2"],"city":"test city","postcode":"887766","telephone":"88776655","country":{"code":"US","label":"US"}}]}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Set Payment Method On Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n setPaymentMethodOnCart(input: {\n cart_id: \"${quote_id}\", \n payment_method: {\n code: \"checkmo\"\n }\n }) {\n cart {\n selected_payment_method {\n code\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/set_payment_method_on_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="-1830199373">{"data":{"setPaymentMethodOnCart":{"cart":{"selected_payment_method":{"code":"checkmo"}}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Current Shipping Address" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n cart(cart_id: \"${quote_id}\") {\n shipping_addresses {\n postcode\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_current_shipping_address.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Set Shipping Method On Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n setShippingMethodsOnCart(input: \n {\n cart_id: \"${quote_id}\", \n shipping_methods: [{\n carrier_code: \"flatrate\"\n method_code: \"flatrate\"\n }]\n }) {\n cart {\n shipping_addresses {\n selected_shipping_method {\n carrier_code\n method_code\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/set_shipping_method_on_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="644143859">{"data":{"setShippingMethodsOnCart":{"cart":{"shipping_addresses":[{"selected_shipping_method":{"carrier_code":"flatrate","method_code":"flatrate"}}]}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Coupon Code Data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); + +var coupons = props.get("coupon_codes"); +number = random.nextInt(coupons.length); + +vars.put("coupon_code", coupons[number].code); + </stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_coupon_code_setup.jmx</stringProp> + </JSR223Sampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Apply Coupon To Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n applyCouponToCart(input: {cart_id: \"${quote_id}\", coupon_code: \"${coupon_code}\"}) {\n cart {\n applied_coupon {\n code\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/apply_coupon_to_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1026466978">{"data":{"applyCouponToCart":{"cart":{"applied_coupon":{"code":"${coupon_code}"}}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Remove Coupon From Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n removeCouponFromCart(input: {cart_id: \"${quote_id}\"}) {\n cart {\n applied_coupon {\n code\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/remove_coupon_from_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="-76201335">{"data":{"removeCouponFromCart":{"cart":{"applied_coupon":null}}}}</stringProp> </collectionProp> <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> <boolProp name="Assertion.assume_success">false</boolProp> diff --git a/setup/performance-toolkit/profiles/ce/extra_large.xml b/setup/performance-toolkit/profiles/ce/extra_large.xml index 390bf7fb12003..911ac7fe06d3b 100644 --- a/setup/performance-toolkit/profiles/ce/extra_large.xml +++ b/setup/performance-toolkit/profiles/ce/extra_large.xml @@ -39,6 +39,7 @@ <catalog_price_rules>20</catalog_price_rules> <!-- Number of catalog price rules --> <cart_price_rules>20</cart_price_rules> <!-- Number of cart price rules --> <cart_price_rules_floor>2</cart_price_rules_floor> + <coupon_codes>20</coupon_codes> <!-- Number of coupon codes --> <product_attribute_sets>100</product_attribute_sets> <!-- Number of product attribute sets --> <product_attribute_sets_attributes>30</product_attribute_sets_attributes> <!-- Number of attributes per set --> diff --git a/setup/performance-toolkit/profiles/ce/large.xml b/setup/performance-toolkit/profiles/ce/large.xml index ed91b22930af5..79abab0ba4b95 100644 --- a/setup/performance-toolkit/profiles/ce/large.xml +++ b/setup/performance-toolkit/profiles/ce/large.xml @@ -39,6 +39,7 @@ <catalog_price_rules>20</catalog_price_rules> <!-- Number of catalog price rules --> <cart_price_rules>20</cart_price_rules> <!-- Number of cart price rules --> <cart_price_rules_floor>2</cart_price_rules_floor> + <coupon_codes>20</coupon_codes> <!-- Number of coupon codes --> <product_attribute_sets>50</product_attribute_sets> <!-- Number of product attribute sets --> <product_attribute_sets_attributes>20</product_attribute_sets_attributes> <!-- Number of attributes per set --> diff --git a/setup/performance-toolkit/profiles/ce/medium.xml b/setup/performance-toolkit/profiles/ce/medium.xml index f01eabb7898f3..d02370a7770b3 100644 --- a/setup/performance-toolkit/profiles/ce/medium.xml +++ b/setup/performance-toolkit/profiles/ce/medium.xml @@ -39,6 +39,7 @@ <catalog_price_rules>20</catalog_price_rules> <!-- Number of catalog price rules --> <cart_price_rules>20</cart_price_rules> <!-- Number of cart price rules --> <cart_price_rules_floor>2</cart_price_rules_floor> + <coupon_codes>20</coupon_codes> <!-- Number of coupon codes --> <product_attribute_sets>30</product_attribute_sets> <!-- Number of product attribute sets --> <product_attribute_sets_attributes>10</product_attribute_sets_attributes> <!-- Number of attributes per set --> diff --git a/setup/performance-toolkit/profiles/ce/medium_msite.xml b/setup/performance-toolkit/profiles/ce/medium_msite.xml index a57fcad0779fe..2f9310c5242cb 100644 --- a/setup/performance-toolkit/profiles/ce/medium_msite.xml +++ b/setup/performance-toolkit/profiles/ce/medium_msite.xml @@ -45,6 +45,7 @@ <catalog_price_rules>20</catalog_price_rules> <!-- Number of catalog price rules --> <cart_price_rules>20</cart_price_rules> <!-- Number of cart price rules --> <cart_price_rules_floor>2</cart_price_rules_floor> + <coupon_codes>20</coupon_codes> <!-- Number of coupon codes --> <product_attribute_sets>30</product_attribute_sets> <!-- Number of product attribute sets --> <product_attribute_sets_attributes>10</product_attribute_sets_attributes> <!-- Number of attributes per set --> diff --git a/setup/performance-toolkit/profiles/ce/small.xml b/setup/performance-toolkit/profiles/ce/small.xml index 60ae901d8f5e0..cf7768328bd69 100644 --- a/setup/performance-toolkit/profiles/ce/small.xml +++ b/setup/performance-toolkit/profiles/ce/small.xml @@ -39,6 +39,7 @@ <catalog_price_rules>20</catalog_price_rules> <!-- Number of catalog price rules --> <cart_price_rules>20</cart_price_rules> <!-- Number of cart price rules --> <cart_price_rules_floor>2</cart_price_rules_floor> + <coupon_codes>20</coupon_codes> <!-- Number of coupon codes --> <product_attribute_sets>10</product_attribute_sets> <!-- Number of product attribute sets --> <product_attribute_sets_attributes>5</product_attribute_sets_attributes> <!-- Number of attributes per set --> diff --git a/setup/src/Magento/Setup/Fixtures/CouponCodesFixture.php b/setup/src/Magento/Setup/Fixtures/CouponCodesFixture.php new file mode 100644 index 0000000000000..e60a2c9f30765 --- /dev/null +++ b/setup/src/Magento/Setup/Fixtures/CouponCodesFixture.php @@ -0,0 +1,168 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Setup\Fixtures; + +/** + * Fixture for generating coupon codes + * + * Support the following format: + * <!-- Number of coupon codes --> + * <coupon_codes>{int}</coupon_codes> + * + * @see setup/performance-toolkit/profiles/ce/small.xml + */ +class CouponCodesFixture extends Fixture +{ + /** + * @var int + */ + protected $priority = 129; + + /** + * @var int + */ + protected $couponCodesCount = 0; + + /** + * @var \Magento\SalesRule\Model\RuleFactory + */ + private $ruleFactory; + + /** + * @var \Magento\SalesRule\Model\CouponFactory + */ + private $couponCodeFactory; + + /** + * Constructor + * + * @param FixtureModel $fixtureModel + * @param \Magento\SalesRule\Model\RuleFactory|null $ruleFactory + * @param \Magento\SalesRule\Model\CouponFactory|null $couponCodeFactory + */ + public function __construct( + FixtureModel $fixtureModel, + \Magento\SalesRule\Model\RuleFactory $ruleFactory = null, + \Magento\SalesRule\Model\CouponFactory $couponCodeFactory = null + ) { + parent::__construct($fixtureModel); + $this->ruleFactory = $ruleFactory ?: $this->fixtureModel->getObjectManager() + ->get(\Magento\SalesRule\Model\RuleFactory::class); + $this->couponCodeFactory = $couponCodeFactory ?: $this->fixtureModel->getObjectManager() + ->get(\Magento\SalesRule\Model\CouponFactory::class); + } + + /** + * @inheritdoc + * + * @SuppressWarnings(PHPMD) + */ + public function execute() + { + $this->fixtureModel->resetObjectManager(); + $this->couponCodesCount = $this->fixtureModel->getValue('coupon_codes', 0); + if (!$this->couponCodesCount) { + return; + } + + /** @var \Magento\Store\Model\StoreManager $storeManager */ + $storeManager = $this->fixtureModel->getObjectManager()->create(\Magento\Store\Model\StoreManager::class); + /** @var $category \Magento\Catalog\Model\Category */ + $category = $this->fixtureModel->getObjectManager()->get(\Magento\Catalog\Model\Category::class); + + //Get all websites + $categoriesArray = []; + $websites = $storeManager->getWebsites(); + foreach ($websites as $website) { + //Get all groups + $websiteGroups = $website->getGroups(); + foreach ($websiteGroups as $websiteGroup) { + $websiteGroupRootCategory = $websiteGroup->getRootCategoryId(); + $category->load($websiteGroupRootCategory); + $categoryResource = $category->getResource(); + //Get all categories + $resultsCategories = $categoryResource->getAllChildren($category); + foreach ($resultsCategories as $resultsCategory) { + $category->load($resultsCategory); + $structure = explode('/', $category->getPath()); + if (count($structure) > 2) { + $categoriesArray[] = [$category->getId(), $website->getId()]; + } + } + } + } + asort($categoriesArray); + $categoriesArray = array_values($categoriesArray); + + $this->generateCouponCodes($this->ruleFactory, $this->couponCodeFactory, $categoriesArray); + } + + /** + * Generate Coupon Codes + * + * @param \Magento\SalesRule\Model\RuleFactory $ruleFactory + * @param \Magento\SalesRule\Model\CouponFactory $couponCodeFactory + * @param array $categoriesArray + * + * @return void + */ + public function generateCouponCodes($ruleFactory, $couponCodeFactory, $categoriesArray) + { + for ($i = 0; $i < $this->couponCodesCount; $i++) { + $ruleName = sprintf('Coupon Code %1$d', $i); + $data = [ + 'rule_id' => null, + 'name' => $ruleName, + 'is_active' => '1', + 'website_ids' => $categoriesArray[$i % count($categoriesArray)][1], + 'customer_group_ids' => [ + 0 => '0', + 1 => '1', + 2 => '2', + 3 => '3', + ], + 'coupon_type' => \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC, + 'conditions' => [], + 'simple_action' => \Magento\SalesRule\Model\Rule::BY_PERCENT_ACTION, + 'discount_amount' => 5, + 'discount_step' => 0, + 'stop_rules_processing' => 1, + ]; + + $model = $ruleFactory->create(); + $model->loadPost($data); + $useAutoGeneration = (int)!empty($data['use_auto_generation']); + $model->setUseAutoGeneration($useAutoGeneration); + $model->save(); + + $coupon = $couponCodeFactory->create(); + $coupon->setRuleId($model->getId()) + ->setCode('CouponCode' . $i) + ->setIsPrimary(true) + ->setType(0); + $coupon->save(); + } + } + + /** + * @inheritdoc + */ + public function getActionTitle() + { + return 'Generating coupon codes'; + } + + /** + * @inheritdoc + */ + public function introduceParamLabels() + { + return [ + 'coupon_codes' => 'Coupon Codes' + ]; + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/CouponCodesFixtureTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/CouponCodesFixtureTest.php new file mode 100644 index 0000000000000..e28415961f26a --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/CouponCodesFixtureTest.php @@ -0,0 +1,198 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Setup\Test\Unit\Fixtures; + +use \Magento\Setup\Fixtures\CouponCodesFixture; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CouponCodesFixtureTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Setup\Fixtures\CartPriceRulesFixture + */ + private $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Setup\Fixtures\FixtureModel + */ + private $fixtureModelMock; + + /** + * @var \Magento\SalesRule\Model\RuleFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $ruleFactoryMock; + + /** + * @var \Magento\SalesRule\Model\CouponFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $couponCodeFactoryMock; + + /** + * setUp + */ + public function setUp() + { + $this->fixtureModelMock = $this->createMock(\Magento\Setup\Fixtures\FixtureModel::class); + $this->ruleFactoryMock = $this->createPartialMock(\Magento\SalesRule\Model\RuleFactory::class, ['create']); + $this->couponCodeFactoryMock = $this->createPartialMock( + \Magento\SalesRule\Model\CouponFactory::class, + ['create'] + ); + $this->model = new CouponCodesFixture( + $this->fixtureModelMock, + $this->ruleFactoryMock, + $this->couponCodeFactoryMock + ); + } + + /** + * testExecute + */ + public function testExecute() + { + $storeMock = $this->createMock(\Magento\Store\Model\Store::class); + $storeMock->expects($this->once()) + ->method('getRootCategoryId') + ->will($this->returnValue(2)); + + $websiteMock = $this->createMock(\Magento\Store\Model\Website::class); + $websiteMock->expects($this->once()) + ->method('getGroups') + ->will($this->returnValue([$storeMock])); + $websiteMock->expects($this->once()) + ->method('getId') + ->will($this->returnValue('website_id')); + + $contextMock = $this->createMock(\Magento\Framework\Model\ResourceModel\Db\Context::class); + $abstractDbMock = $this->getMockForAbstractClass( + \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + [$contextMock], + '', + true, + true, + true, + ['getAllChildren'] + ); + $abstractDbMock->expects($this->once()) + ->method('getAllChildren') + ->will($this->returnValue([1])); + + $storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManager::class); + $storeManagerMock->expects($this->once()) + ->method('getWebsites') + ->will($this->returnValue([$websiteMock])); + + $categoryMock = $this->createMock(\Magento\Catalog\Model\Category::class); + $categoryMock->expects($this->once()) + ->method('getResource') + ->will($this->returnValue($abstractDbMock)); + $categoryMock->expects($this->once()) + ->method('getPath') + ->will($this->returnValue('path/to/file')); + $categoryMock->expects($this->once()) + ->method('getId') + ->will($this->returnValue('category_id')); + + $objectValueMap = [ + [\Magento\Catalog\Model\Category::class, $categoryMock] + ]; + + $objectManagerMock = $this->createMock(\Magento\Framework\ObjectManager\ObjectManager::class); + $objectManagerMock->expects($this->once()) + ->method('create') + ->will($this->returnValue($storeManagerMock)); + $objectManagerMock->expects($this->once()) + ->method('get') + ->will($this->returnValueMap($objectValueMap)); + + $valueMap = [ + ['coupon_codes', 0, 1] + ]; + + $this->fixtureModelMock + ->expects($this->exactly(1)) + ->method('getValue') + ->will($this->returnValueMap($valueMap)); + $this->fixtureModelMock + ->expects($this->exactly(2)) + ->method('getObjectManager') + ->will($this->returnValue($objectManagerMock)); + + $ruleMock = $this->createMock(\Magento\SalesRule\Model\Rule::class); + $this->ruleFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($ruleMock); + + $couponMock = $this->createMock(\Magento\SalesRule\Model\Coupon::class); + $couponMock->expects($this->once()) + ->method('setRuleId') + ->willReturnSelf(); + $couponMock->expects($this->once()) + ->method('setCode') + ->willReturnSelf(); + $couponMock->expects($this->once()) + ->method('setIsPrimary') + ->willReturnSelf(); + $couponMock->expects($this->once()) + ->method('setType') + ->willReturnSelf(); + $couponMock->expects($this->once()) + ->method('save') + ->willReturnSelf(); + $this->couponCodeFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($couponMock); + + $this->model->execute(); + } + + /** + * testNoFixtureConfigValue + */ + public function testNoFixtureConfigValue() + { + $ruleMock = $this->createMock(\Magento\SalesRule\Model\Rule::class); + $ruleMock->expects($this->never())->method('save'); + + $objectManagerMock = $this->createMock(\Magento\Framework\ObjectManager\ObjectManager::class); + $objectManagerMock->expects($this->never()) + ->method('get') + ->with($this->equalTo(\Magento\SalesRule\Model\Rule::class)) + ->willReturn($ruleMock); + + $this->fixtureModelMock + ->expects($this->never()) + ->method('getObjectManager') + ->willReturn($objectManagerMock); + $this->fixtureModelMock + ->expects($this->once()) + ->method('getValue') + ->willReturn(false); + + $this->model->execute(); + } + + /** + * testGetActionTitle + */ + public function testGetActionTitle() + { + $this->assertSame('Generating coupon codes', $this->model->getActionTitle()); + } + + /** + * testIntroduceParamLabels + */ + public function testIntroduceParamLabels() + { + $this->assertSame([ + 'coupon_codes' => 'Coupon Codes' + ], $this->model->introduceParamLabels()); + } +}