diff --git a/CHANGELOG.md b/CHANGELOG.md index 996d1d1352efc..0999bee6ea415 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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/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 @@ notificationGrid - Magento\AdminNotification\Model\ResourceModel\Grid\Collection + Magento\AdminNotification\Model\ResourceModel\Grid\Collection DESC date_added 1 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 @@ catalog_search_grid - Magento\AdvancedSearch\Model\ResourceModel\Search\Grid\Collection + Magento\AdvancedSearch\Model\ResourceModel\Search\Grid\Collection name ASC 1 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/Block/Store/Switcher.php b/app/code/Magento/Backend/Block/Store/Switcher.php index 1468df2b0b442..9c35cfb5df81d 100644 --- a/app/code/Magento/Backend/Block/Store/Switcher.php +++ b/app/code/Magento/Backend/Block/Store/Switcher.php @@ -17,7 +17,7 @@ class Switcher extends \Magento\Backend\Block\Template /** * URL for store switcher hint */ - const HINT_URL = 'http://docs.magento.com/m2/ce/user_guide/configuration/scope.html'; + const HINT_URL = 'https://docs.magento.com/m2/ce/user_guide/configuration/scope.html'; /** * Name of website variable @@ -86,6 +86,8 @@ class Switcher extends \Magento\Backend\Block\Template protected $_storeFactory; /** + * Switcher constructor. + * * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Store\Model\WebsiteFactory $websiteFactory * @param \Magento\Store\Model\GroupFactory $storeGroupFactory @@ -106,7 +108,7 @@ public function __construct( } /** - * @return void + * @inheritdoc */ protected function _construct() { @@ -130,6 +132,8 @@ protected function _construct() } /** + * Get website collection. + * * @return \Magento\Store\Model\ResourceModel\Website\Collection */ public function getWebsiteCollection() @@ -169,6 +173,8 @@ public function isWebsiteSwitchEnabled() } /** + * Set website variable name. + * * @param string $varName * @return $this */ @@ -179,6 +185,8 @@ public function setWebsiteVarName($varName) } /** + * Get website variable name. + * * @return string */ public function getWebsiteVarName() @@ -191,6 +199,8 @@ public function getWebsiteVarName() } /** + * Check if current website selected. + * * @param \Magento\Store\Model\Website $website * @return bool */ @@ -200,6 +210,8 @@ public function isWebsiteSelected(\Magento\Store\Model\Website $website) } /** + * Return website Id. + * * @return int|null */ public function getWebsiteId() @@ -211,6 +223,8 @@ public function getWebsiteId() } /** + * Return group collection provided website. + * * @param int|\Magento\Store\Model\Website $website * @return \Magento\Store\Model\ResourceModel\Group\Collection */ @@ -247,6 +261,8 @@ public function isStoreGroupSwitchEnabled() } /** + * Sets store group variable name. + * * @param string $varName * @return $this */ @@ -257,6 +273,8 @@ public function setStoreGroupVarName($varName) } /** + * Return store group variable name. + * * @return string */ public function getStoreGroupVarName() @@ -269,6 +287,8 @@ public function getStoreGroupVarName() } /** + * Is provided group selected. + * * @param \Magento\Store\Model\Group $group * @return bool */ @@ -278,6 +298,8 @@ public function isStoreGroupSelected(\Magento\Store\Model\Group $group) } /** + * Return store group Id. + * * @return int|null */ public function getStoreGroupId() @@ -289,6 +311,8 @@ public function getStoreGroupId() } /** + * Return store collection. + * * @param \Magento\Store\Model\Group|int $group * @return \Magento\Store\Model\ResourceModel\Store\Collection */ @@ -328,6 +352,8 @@ public function getStores($group) } /** + * Return store Id. + * * @return int|null */ public function getStoreId() @@ -339,6 +365,8 @@ public function getStoreId() } /** + * Check is provided store selected. + * * @param \Magento\Store\Model\Store $store * @return bool */ @@ -358,6 +386,8 @@ public function isStoreSwitchEnabled() } /** + * Sets store variable name. + * * @param string $varName * @return $this */ @@ -368,6 +398,8 @@ public function setStoreVarName($varName) } /** + * Return store variable name. + * * @return mixed|string */ public function getStoreVarName() @@ -380,6 +412,8 @@ public function getStoreVarName() } /** + * Return switch url. + * * @return string */ public function getSwitchUrl() @@ -399,6 +433,8 @@ public function getSwitchUrl() } /** + * Checks if scope selected. + * * @return bool */ public function hasScopeSelected() @@ -472,6 +508,8 @@ public function getCurrentStoreName() } /** + * Sets store ids. + * * @param array $storeIds * @return $this */ @@ -482,6 +520,8 @@ public function setStoreIds($storeIds) } /** + * Return store ids. + * * @return array */ public function getStoreIds() @@ -490,6 +530,8 @@ public function getStoreIds() } /** + * Check if system is run in the single store mode. + * * @return bool */ public function isShow() @@ -498,6 +540,8 @@ public function isShow() } /** + * Render block. + * * @return string */ protected function _toHtml() 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 @@ + $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 @@ +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 @@ + + + + + + + + + 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 @@ + + 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 @@ + + + + + +
+ + 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 @@ +
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 @@ + + + + +
+ + + + + + +
+
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 @@ + + + + +
+ + +
+
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 @@ +
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 @@ + + + + + + + + + <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/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/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 f066c88b12fcc..a781841e0a77b 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml @@ -81,7 +81,7 @@ <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"/> 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/etc/adminhtml/system.xml b/app/code/Magento/Braintree/etc/adminhtml/system.xml index 5215dbc00b7ef..67c47f8ea9dc3 100644 --- a/app/code/Magento/Braintree/etc/adminhtml/system.xml +++ b/app/code/Magento/Braintree/etc/adminhtml/system.xml @@ -41,7 +41,7 @@ </requires> </field> <group id="configuration_details" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="4"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/braintree.html</comment> + <comment>https://docs.magento.com/m2/ce/user_guide/payment/braintree.html</comment> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Hint</frontend_model> </group> <group id="braintree_required" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="5"> 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/Test/Mftf/ActionGroup/StorefrontSelectBundleProductDropDownOptionActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectBundleProductDropDownOptionActionGroup.xml new file mode 100644 index 0000000000000..e6afdce6ab697 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectBundleProductDropDownOptionActionGroup.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="StorefrontSelectBundleProductDropDownOptionActionGroup"> + <arguments> + <argument name="productName" type="string"/> + </arguments> + <click selector="{{StorefrontBundleProductActionSection.dropdownSelectOption}}" stepKey="clickOnSelectOption"/> + <click selector="{{StorefrontBundleProductActionSection.dropdownProductSelection(productName)}}" stepKey="selectProduct"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup.xml new file mode 100644 index 0000000000000..cf2ccfac47023 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup.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="StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup"> + <waitForElementVisible selector="{{StorefrontBundleProductActionSection.customizeAndAddToCartButton}}" stepKey="waitForCustomizeAndAddToCartButton"/> + <click selector="{{StorefrontBundleProductActionSection.customizeAndAddToCartButton}}" stepKey="clickOnCustomizeAndAddToCartButton"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml index 0a0c77755fc7a..5034c72b9ee3f 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml @@ -91,4 +91,20 @@ <requiredEntity type="custom_attribute">CustomAttributeFixPrice</requiredEntity> <requiredEntity type="custom_attribute">CustomAttributePriceView</requiredEntity> </entity> + <entity name="BundleProductPriceViewRange" type="product2"> + <data key="name" unique="suffix">BundleProduct</data> + <data key="sku" unique="suffix">bundle-product</data> + <data key="type_id">bundle</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="status">1</data> + <data key="urlKey" unique="suffix">bundle-product</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributeCategoryIds</requiredEntity> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute">ApiProductDescription</requiredEntity> + <requiredEntity type="custom_attribute">ApiProductShortDescription</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributeDynamicPrice</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributePriceViewRange</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml index 30a7e8b777f3b..c47cf6095c777 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml @@ -37,5 +37,6 @@ <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"/> + <element name="multiSelectOption" type="select" selector="//div[@class='field option required']//select"/> </section> </sections> diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductActionSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductActionSection.xml index 9dc4aed26bef0..eb92fd3756497 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductActionSection.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductActionSection.xml @@ -8,8 +8,10 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontBundleProductActionSection"> - <element name="customizeAndAddToCartButton" type="button" selector="#bundle-slide"/> + <element name="customizeAndAddToCartButton" type="button" selector="#bundle-slide" timeout="30"/> <element name="quantityField" type="input" selector="#qty"/> - <element name="addToCartButton" type="button" selector="#product-addtocart-button"/> + <element name="addToCartButton" type="button" selector="#product-addtocart-button" timeout="30"/> + <element name="dropdownSelectOption" type="select" selector="//div[@class='control']/select"/> + <element name="dropdownProductSelection" type="select" selector="//div[@class='control']/select/option[contains(.,'{{productName}}')]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml index 4c39cbc4ab0a4..532af1ea76dca 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml @@ -13,8 +13,10 @@ <features value="Bundle"/> <stories value="View bundle products"/> <title value="Check tier prices for bundle options"/> + <description value="Check tier prices for bundle options"/> <testCaseId value="MAGETWO-98968"/> <useCaseId value="MAGETWO-98603"/> + <severity value="AVERAGE"/> <group value="catalog"/> <group value="bundle"/> </annotations> 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/Gallery/Processor.php b/app/code/Magento/Catalog/Model/Product/Gallery/Processor.php index 0912324745360..e1b788bc3941b 100644 --- a/app/code/Magento/Catalog/Model/Product/Gallery/Processor.php +++ b/app/code/Magento/Catalog/Model/Product/Gallery/Processor.php @@ -157,6 +157,7 @@ public function addImage( throw new LocalizedException(__("The image doesn't exist.")); } + // phpcs:ignore Magento2.Functions.DiscouragedFunction $pathinfo = pathinfo($file); $imgExtensions = ['jpg', 'jpeg', 'gif', 'png']; if (!isset($pathinfo['extension']) || !in_array(strtolower($pathinfo['extension']), $imgExtensions)) { @@ -196,7 +197,7 @@ public function addImage( $mediaGalleryData = $product->getData($attrCode); $position = 0; - $absoluteFilePath = $this->mediaDirectory->getAbsolutePath($file); + $absoluteFilePath = $this->mediaDirectory->getAbsolutePath($destinationFile); $imageMimeType = $this->mime->getMimeType($absoluteFilePath); $imageContent = $this->mediaDirectory->readFile($absoluteFilePath); $imageBase64 = base64_encode($imageContent); @@ -452,6 +453,7 @@ protected function getUniqueFileName($file, $forTmp = false) $destinationFile = $forTmp ? $this->mediaDirectory->getAbsolutePath($this->mediaConfig->getTmpMediaPath($file)) : $this->mediaDirectory->getAbsolutePath($this->mediaConfig->getMediaPath($file)); + // phpcs:ignore Magento2.Functions.DiscouragedFunction $destFile = dirname($file) . '/' . \Magento\MediaStorage\Model\File\Uploader::getNewFileName($destinationFile); } @@ -494,7 +496,7 @@ protected function getNotDuplicatedFilename($fileName, $dispretionPath) /** * Retrieve data for update attribute * - * @param \Magento\Catalog\Model\Product $object + * @param \Magento\Catalog\Model\Product $object * @return array * @since 101.0.0 */ 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/AddWebsiteToProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddWebsiteToProductActionGroup.xml new file mode 100644 index 0000000000000..9a4d15a0909fd --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddWebsiteToProductActionGroup.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="AddWebsiteToProductActionGroup"> + <arguments> + <argument name="website" type="string"/> + </arguments> + <scrollTo selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="scrollToProductInWebsiteSectionHeader"/> + <click selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="clickProductInWebsiteSectionHeader"/> + <checkOption selector="{{ProductInWebsitesSection.website(website)}}" stepKey="checkWebsite"/> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForSimpleProductSaved"/> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertProductSaveSuccessMessage"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddAdvancedPricingToTheProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddAdvancedPricingToTheProductActionGroup.xml new file mode 100644 index 0000000000000..a8d2f7d9860f3 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddAdvancedPricingToTheProductActionGroup.xml @@ -0,0 +1,34 @@ +<?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"> + <!-- You must already be on the product form > Advanced Inventory; + Action group can be used for customer group price and tier price --> + <actionGroup name="AdminAddAdvancedPricingToTheProductActionGroup"> + <arguments> + <argument name="index" type="string"/> + <argument name="groupPrice" type="entity"/> + </arguments> + <waitForPageLoad stepKey="waitForPageLoad"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink"/> + <click selector="{{AdminProductFormAdvancedPricingSection.addCustomerGroupPrice}}" stepKey="clickCustomerGroupPriceAddButton"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceWebsiteSelect(index)}}" userInput="{{groupPrice.website_id}}" stepKey="selectProductTierPriceWebsiteInput"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect(index)}}" userInput="{{groupPrice.customer_group}}" stepKey="selectProductTierPriceCustomerGroupInput"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput(index)}}" userInput="{{groupPrice.quantity}}" stepKey="fillProductTierPriceQuantityInput"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceFixedPriceInput(index)}}" userInput="{{groupPrice.price}}" stepKey="selectProductTierPriceFixedPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton"/> + </actionGroup> + + <!-- Customer group is selected in different way for B2B --> + <actionGroup name="AdminAddAdvancedPricingToTheProductExtendedActionGroup" extends="AdminAddAdvancedPricingToTheProductActionGroup"> + <remove keyForRemoval="selectProductTierPriceCustomerGroupInput"/> + <click selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect(index)}}" stepKey="clickProductTierPriceCustGroupSelect" after="selectProductTierPriceWebsiteInput"/> + <waitForElement selector="{{AdminProductFormAdvancedPricingSection.productTierPriceGroupOrCatalogOption(groupPrice.customer_group)}}" time="30" stepKey="waitProductTierPriceGroupOrCatalogOption" after="clickProductTierPriceCustGroupSelect"/> + <click selector="{{AdminProductFormAdvancedPricingSection.productTierPriceGroupOrCatalogOption(groupPrice.customer_group)}}" stepKey="clickAllGroupsOption" after="waitProductTierPriceGroupOrCatalogOption"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddMinimumAdvertisedPriceActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddMinimumAdvertisedPriceActionGroup.xml new file mode 100644 index 0000000000000..f0eef98748f8d --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddMinimumAdvertisedPriceActionGroup.xml @@ -0,0 +1,21 @@ +<?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="AdminAddMinimumAdvertisedPriceActionGroup"> + <arguments> + <argument name="msrpData" type="entity"/> + </arguments> + <waitForPageLoad stepKey="waitForPageLoad"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink"/> + <waitForElementVisible selector="{{AdminProductFormAdvancedPricingSection.msrp}}" stepKey="waitSpecialPrice"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.msrp}}" userInput="{{msrpData.msrp}}" stepKey="fillMinimumAdvertisedPrice"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.msrpType}}" userInput="{{msrpData.msrp_display_actual_price_type}}" stepKey="selectPriceType"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDone"/> + <waitForElementNotVisible selector="{{AdminProductFormAdvancedPricingSection.msrp}}" stepKey="waitForCloseModalWindow"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddProductCustomOptionActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddProductCustomOptionActionGroup.xml new file mode 100644 index 0000000000000..8e50ac6edc68e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddProductCustomOptionActionGroup.xml @@ -0,0 +1,24 @@ +<?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"> + <!-- Add custom option, title and type --> + <actionGroup name="AdminAddProductCustomOptionActionGroup"> + <arguments> + <argument name="customOptionTitle" type="string"/> + <argument name="customOptionType" type="string"/> + </arguments> + <scrollTo selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" stepKey="scrollToCustomizableOptionsSection"/> + <waitForPageLoad stepKey="waitForScrolling"/> + <click stepKey="clickAddOptions" selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}"/> + <waitForPageLoad stepKey="waitForAddProductPageLoad"/> + <fillField stepKey="fillInOptionTitle" selector="{{AdminProductCustomizableOptionsSection.lastOptionTitle}}" userInput="{{customOptionTitle}}"/> + <click stepKey="clickOptionTypeParent" selector="{{AdminProductCustomizableOptionsSection.lastOptionTypeParent}}"/> + <waitForPageLoad stepKey="waitForDropdownOpen"/> + <click stepKey="clickOptionType" selector="{{AdminProductCustomizableOptionsSection.optionType(customOptionType)}}"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddTitleAndPriceValueToCustomOptionActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddTitleAndPriceValueToCustomOptionActionGroup.xml new file mode 100644 index 0000000000000..3a03eb60555a3 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddTitleAndPriceValueToCustomOptionActionGroup.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"> + <!-- Add value, value title and value price to custom options (type: drop-down, checkbox, multiple select, radio buttons) --> + <actionGroup name="AdminAddTitleAndPriceValueToCustomOptionActionGroup"> + <arguments> + <argument name="optionValue" type="entity"/> + </arguments> + <click stepKey="clickAddValue" selector="{{AdminProductCustomizableOptionsSection.addValue}}"/> + <fillField stepKey="fillInValueTitle" selector="{{AdminProductCustomizableOptionsSection.valueTitle}}" userInput="{{optionValue.title}}"/> + <fillField stepKey="fillInValuePrice" selector="{{AdminProductCustomizableOptionsSection.valuePrice}}" userInput="{{optionValue.price}}"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminClickOnAdvancedInventoryLinkActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminClickOnAdvancedInventoryLinkActionGroup.xml new file mode 100644 index 0000000000000..58164ce5f8989 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminClickOnAdvancedInventoryLinkActionGroup.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 click on Advanced Inventory in product form; + You must already be on the product form page --> + <actionGroup name="AdminClickOnAdvancedInventoryLinkActionGroup"> + <click selector="{{AdminProductFormSection.advancedInventoryLink}}" stepKey="clickOnAdvancedInventoryLink"/> + <waitForPageLoad stepKey="waitForAdvancedInventoryPageToLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateCustomDropDownOptionsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateCustomDropDownOptionsActionGroup.xml new file mode 100644 index 0000000000000..a674647a5c85b --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateCustomDropDownOptionsActionGroup.xml @@ -0,0 +1,33 @@ +<?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="AdminCreateCustomDropDownOptionsActionGroup"> + <!-- ActionGroup will add a single custom option to a product + You must already be on the product creation page --> + <arguments> + <argument name="customOptionName" type="string"/> + <argument name="firstOption" type="entity"/> + <argument name="secondOption" type="entity"/> + </arguments> + <click stepKey="clickAddOptions" selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}"/> + <waitForPageLoad stepKey="waitForAddProductPageLoad"/> + <!-- Fill in the option and select the type of drop down --> + <fillField stepKey="fillInOptionTitle" selector="{{AdminProductCustomizableOptionsSection.lastOptionTitle}}" userInput="{{customOptionName}}"/> + <click stepKey="clickOptionTypeParent" selector="{{AdminProductCustomizableOptionsSection.lastOptionTypeParent}}"/> + <waitForPageLoad stepKey="waitForDropdownOpen"/> + <click stepKey="clickOptionType" selector="{{AdminProductCustomizableOptionsSection.optionType('Drop-down')}}"/> + <!-- Add option based on the parameter --> + <click stepKey="clickAddFirstValue" selector="{{AdminProductCustomizableOptionsSection.addValue}}"/> + <fillField stepKey="fillInFirstOptionValueTitle" selector="{{AdminProductCustomizableOptionsSection.valueTitle}}" userInput="{{firstOption.title}}"/> + <fillField stepKey="fillInFirstOptionValuePrice" selector="{{AdminProductCustomizableOptionsSection.valuePrice}}" userInput="{{firstOption.price}}"/> + <click stepKey="clickAddSecondValue" selector="{{AdminProductCustomizableOptionsSection.addValue}}"/> + <fillField stepKey="fillInSecondOptionValueTitle" selector="{{AdminProductCustomizableOptionsSection.valueTitle}}" userInput="{{secondOption.title}}"/> + <fillField stepKey="fillInSecondOptionValuePrice" selector="{{AdminProductCustomizableOptionsSection.valuePrice}}" userInput="{{secondOption.price}}"/> + </actionGroup> +</actionGroups> 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/AdminFillAdvancedInventoryOutOfStockThresholdActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryOutOfStockThresholdActionGroup.xml new file mode 100644 index 0000000000000..d0116467be5ea --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryOutOfStockThresholdActionGroup.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"> + <!-- You must already be on the product form > Advanced Inventory --> + <actionGroup name="AdminFillAdvancedInventoryOutOfStockThresholdActionGroup"> + <arguments> + <argument name="qty" type="string"/> + </arguments> + <uncheckOption selector="{{AdminProductFormAdvancedInventorySection.minQtyConfigSetting}}" stepKey="uncheckMiniQtyCheckBox"/> + <fillField selector="{{AdminProductFormAdvancedInventorySection.outOfStockThreshold}}" userInput="{{qty}}" stepKey="fillMiniAllowedQty"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryQtyActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryQtyActionGroup.xml new file mode 100644 index 0000000000000..1faa3f04d366e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillAdvancedInventoryQtyActionGroup.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"> + <!-- You must already be on the product form > Advanced Inventory --> + <actionGroup name="AdminFillAdvancedInventoryQtyActionGroup"> + <arguments> + <argument name="qty" type="string"/> + </arguments> + <fillField selector="{{AdminProductFormAdvancedInventorySection.advancedInventoryQty}}" userInput="{{qty}}" stepKey="fillQty"/> + </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/AdminSubmitAdvancedInventoryFormActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSubmitAdvancedInventoryFormActionGroup.xml new file mode 100644 index 0000000000000..e27454fb60491 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSubmitAdvancedInventoryFormActionGroup.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"> + <!-- Click done button; + You must already be on the product form > Advanced Inventory --> + <actionGroup name="AdminSubmitAdvancedInventoryFormActionGroup"> + <click stepKey="clickOnDoneButton" selector="{{AdminProductFormAdvancedInventorySection.doneButton}}"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertDontSeeProductDetailsOnStorefrontActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertDontSeeProductDetailsOnStorefrontActionGroup.xml new file mode 100644 index 0000000000000..3d0d16d105025 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertDontSeeProductDetailsOnStorefrontActionGroup.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="AssertDontSeeProductDetailsOnStorefrontActionGroup"> + <arguments> + <argument name="productNumber" type="string"/> + <argument name="productInfo" type="string"/> + </arguments> + <dontSee selector="{{StorefrontCategoryProductSection.ProductInfoByNumber(productNumber)}}" userInput="{{productInfo}}" stepKey="seeProductInfo"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertErrorMessageAfterDeletingWebsiteActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertErrorMessageAfterDeletingWebsiteActionGroup.xml new file mode 100644 index 0000000000000..2ae224c71a9bd --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertErrorMessageAfterDeletingWebsiteActionGroup.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="AssertErrorMessageAfterDeletingWebsiteActionGroup"> + <arguments> + <argument name="errorMessage" type="string"/> + </arguments> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> + <waitForPageLoad stepKey="waitForProductCatalogPageLoad"/> + <click selector="{{AdminCategoryModalSection.ok}}" stepKey="clickOkButtonFromAdminCategoryModalSection"/> + <see selector="{{AdminCategoryMessagesSection.errorMessage}}" userInput="{{errorMessage}}" stepKey="seeAssertErrorMessage"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductDetailsOnStorefrontActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductDetailsOnStorefrontActionGroup.xml new file mode 100644 index 0000000000000..68c6e92b93fa0 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductDetailsOnStorefrontActionGroup.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="AssertProductDetailsOnStorefrontActionGroup"> + <arguments> + <argument name="productNumber" type="string"/> + <argument name="productInfo" type="string"/> + </arguments> + <see selector="{{StorefrontCategoryProductSection.ProductInfoByNumber(productNumber)}}" userInput="{{productInfo}}" stepKey="seeProductInfo"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPricesActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPricesActionGroup.xml new file mode 100644 index 0000000000000..6e90fe7324dc4 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductPricesActionGroup.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="AssertStorefrontProductPricesActionGroup"> + <arguments> + <argument name="productPrice" type="string"/> + <argument name="productFinalPrice" type="string"/> + </arguments> + <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="productPriceAmount" userInput="{{productPrice}}"/> + <see selector="{{StorefrontProductInfoMainSection.updatedPrice}}" stepKey="productFinalPriceAmount" userInput="{{productFinalPrice}}"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertSubTotalOnStorefrontMinicartActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertSubTotalOnStorefrontMinicartActionGroup.xml new file mode 100644 index 0000000000000..26693771bd1fd --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertSubTotalOnStorefrontMinicartActionGroup.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="AssertSubTotalOnStorefrontMiniCartActionGroup"> + <arguments> + <argument name="subTotal" type="string"/> + </arguments> + <waitForElementVisible selector="{{StorefrontMinicartSection.showCart}}" stepKey="waitForShowCartButtonVisible"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <grabTextFrom selector="{{StorefrontMinicartSection.miniCartSubtotalField}}" stepKey="grabTextFromMiniCartSubtotalField"/> + <assertEquals message="Mini shopping cart should contain subtotal {{subTotal}}" stepKey="assertSubtotalFieldFromMiniShoppingCart1"> + <expectedResult type="string">{{subTotal}}</expectedResult> + <actualResult type="variable">grabTextFromMiniCartSubtotalField</actualResult> + </assertEquals> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/FilterProductInGridByStoreViewAndNameActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/FilterProductInGridByStoreViewAndNameActionGroup.xml new file mode 100644 index 0000000000000..d5d378ad11bd9 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/FilterProductInGridByStoreViewAndNameActionGroup.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"> + <actionGroup name="FilterProductInGridByStoreViewAndNameActionGroup"> + <arguments> + <argument name="storeView" type="string"/> + <argument name="productName" type="string"/> + </arguments> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchProduct"/> + <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> + <click selector="{{AdminProductGridFilterSection.storeViewDropdown(storeView)}}" stepKey="clickStoreViewDropdown"/> + <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{productName}}" stepKey="fillProductNameInNameFilter"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <see selector="{{AdminProductGridFilterSection.nthRow('1')}}" userInput="{{productName}}" stepKey="seeFirstRowToVerifyProductVisibleInGrid"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddProductToCartWithQtyActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddProductToCartWithQtyActionGroup.xml new file mode 100644 index 0000000000000..816085cf0ca26 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddProductToCartWithQtyActionGroup.xml @@ -0,0 +1,21 @@ +<?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="StorefrontAddProductToCartWithQtyActionGroup"> + <arguments> + <argument name="productQty" type="string"/> + </arguments> + <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> + <fillField selector="{{StorefrontProductPageSection.qtyInput}}" userInput="{{productQty}}" stepKey="fillProduct1Quantity"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="clickOnAddToCartButton"/> + <waitForPageLoad stepKey="waitForProductToAddInCart"/> + <seeElement selector="{{StorefrontProductPageSection.successMsg}}" stepKey="seeSuccessSaveMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertCustomOptionByTitleActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertCustomOptionByTitleActionGroup.xml new file mode 100644 index 0000000000000..bc922a40b05b7 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertCustomOptionByTitleActionGroup.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="StorefrontAssertCustomOptionByTitleActionGroup"> + <arguments> + <argument name="title" type="string"/> + </arguments> + <seeElement selector="{{StorefrontProductInfoMainSection.customOptionByTitle(title)}}" stepKey="seeCustomOption"/> + </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/StorefrontAssertProductNameOnProductMainPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductMainPageActionGroup.xml new file mode 100644 index 0000000000000..43a34448c8a68 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductMainPageActionGroup.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="StorefrontAssertProductNameOnProductMainPageActionGroup"> + <arguments> + <argument name="productName" type="string"/> + </arguments> + <waitForPageLoad stepKey="waitForTheProductPageToLoad"/> + <see selector="{{StorefrontCategoryMainSection.productName}}" userInput="{{productName}}" stepKey="seeProductName"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductPriceInCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductPriceInCategoryActionGroup.xml new file mode 100644 index 0000000000000..5c975998ab92e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductPriceInCategoryActionGroup.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"> + <!-- You must already be on the category page --> + <actionGroup name="StorefrontCheckProductPriceInCategoryActionGroup" extends="StorefrontCheckCategorySimpleProduct"> + <remove keyForRemoval="AssertProductPrice"/> + <see userInput="{{product.price}}" selector="{{StorefrontCategoryProductSection.ProductPriceByName(product.name)}}" stepKey="AssertProductPrice"/> + </actionGroup> +</actionGroups> 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/ActionGroup/StorefrontOpenHomePageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenHomePageActionGroup.xml new file mode 100644 index 0000000000000..692d1f4266b98 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenHomePageActionGroup.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="StorefrontOpenHomePageActionGroup"> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStorefrontPage"/> + <waitForPageLoad stepKey="waitForStorefrontPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionDropDownAndAssertPricesActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionDropDownAndAssertPricesActionGroup.xml new file mode 100644 index 0000000000000..39793cb8f68de --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionDropDownAndAssertPricesActionGroup.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="StorefrontSelectCustomOptionDropDownAndAssertPricesActionGroup" extends="AssertStorefrontProductPricesActionGroup"> + <arguments> + <argument name="customOption" type="string"/> + <argument name="productPrice" type="string"/> + <argument name="productFinalPrice" type="string"/> + </arguments> + <selectOption selector="{{StorefrontProductPageSection.customOptionDropDown}}" userInput="{{customOption}}" stepKey="selectCustomOption" before="productPriceAmount"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionRadioAndAssertPricesActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionRadioAndAssertPricesActionGroup.xml new file mode 100644 index 0000000000000..6f7bdc46640b7 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontSelectCustomOptionRadioAndAssertPricesActionGroup.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="StorefrontSelectCustomOptionRadioAndAssertPricesActionGroup" extends="AssertStorefrontProductPricesActionGroup"> + <arguments> + <argument name="customOption" type="entity"/> + <argument name="customOptionValue" type="entity"/> + <argument name="productPrice" type="string"/> + <argument name="productFinalPrice" type="string"/> + </arguments> + <click selector="{{StorefrontProductInfoMainSection.productAttributeOptionsRadioButtons(customOption.title, customOptionValue.price)}}" stepKey="clickRadioButtonsProductOption" before="productPriceAmount"/> + </actionGroup> +</actionGroups> 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/GroupPriceData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/GroupPriceData.xml new file mode 100644 index 0000000000000..ae4736a1ca234 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/GroupPriceData.xml @@ -0,0 +1,18 @@ +<?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="simpleGroupPrice" type="data"> + <data key="price">90.00</data> + <data key="price_type">fixed</data> + <data key="website_id">0</data> + <data key="customer_group">ALL GROUPS</data> + <data key="quantity">1</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 3492dffd7cc7d..eea9aa91ff5e5 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -167,6 +167,28 @@ <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> <requiredEntity type="custom_attribute">CustomAttributeProductAttribute</requiredEntity> </entity> + <entity name="SimpleProductDisabled" type="product"> + <data key="sku" unique="suffix">simple_product_disabled</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="name" unique="suffix">Simple Product Disabled</data> + <data key="price">123.00</data> + <data key="visibility">4</data> + <data key="status">2</data> + <data key="quantity">1001</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + </entity> + <entity name="SimpleProductNotVisibleIndividually" type="product"> + <data key="sku" unique="suffix">simple_product_not_visible_individually</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="name" unique="suffix">Simple Product Not Visible Individually</data> + <data key="price">123.00</data> + <data key="visibility">1</data> + <data key="status">1</data> + <data key="quantity">1000</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + </entity> <entity name="NewSimpleProduct" type="product"> <data key="price">321.00</data> </entity> @@ -305,6 +327,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> @@ -428,10 +459,28 @@ <requiredEntity type="product_option">ProductOptionDateTime</requiredEntity> <requiredEntity type="product_option">ProductOptionTime</requiredEntity> </entity> + <entity name="productWithOptionRadiobutton" type="product"> + <var key="sku" entityType="product" entityKey="sku" /> + <requiredEntity type="product_option">ProductOptionRadiobuttonWithTwoFixedOptions</requiredEntity> + </entity> + <entity name="productWithCustomOptions" type="product"> + <var key="sku" entityType="product" entityKey="sku" /> + <data key="file">magento.jpg</data> + <requiredEntity type="product_option">ProductOptionDropDown2</requiredEntity> + </entity> + <entity name="productWithFixedOptions" type="product"> + <var key="sku" entityType="product" entityKey="sku" /> + <data key="file">magento.jpg</data> + <requiredEntity type="product_option">ProductOptionRadioButton2</requiredEntity> + </entity> <entity name="productWithOptions2" type="product"> <var key="sku" entityType="product" entityKey="sku" /> <requiredEntity type="product_option">ProductOptionDropDownWithLongValuesTitle</requiredEntity> </entity> + <entity name="productWithDropdownOption" type="product"> + <var key="sku" entityType="product" entityKey="sku" /> + <requiredEntity type="product_option">ProductOptionValueDropdown</requiredEntity> + </entity> <entity name="ProductWithTextFieldAndAreaOptions" type="product"> <var key="sku" entityType="product" entityKey="sku" /> <requiredEntity type="product_option">ProductOptionField</requiredEntity> @@ -986,4 +1035,38 @@ <data key="tax_class_id">2</data> <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> </entity> + <entity name="defaultSimpleProductWeight200" type="product"> + <data key="name" unique="suffix">Testp</data> + <data key="sku" unique="suffix">testsku</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="price">560.00</data> + <data key="urlKey" unique="suffix">testurl-</data> + <data key="status">1</data> + <data key="quantity">25</data> + <data key="weight">200</data> + <requiredEntity type="product_extension_attribute">EavStock100</requiredEntity> + </entity> + <entity name="SimpleProductWithTwoOption" type="product"> + <var key="sku" entityType="product" entityKey="sku" /> + <requiredEntity type="product_option">ProductOptionMultiSelect</requiredEntity> + </entity> + <entity name="SimpleProductWithOption" type="product"> + <var key="sku" entityType="product" entityKey="sku" /> + <requiredEntity type="product_option">ProductOptionPercentPriceDropDown</requiredEntity> + </entity> + <entity name="SimpleProductWithDescription" type="product"> + <data key="sku" unique="suffix">productwithdescription</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="name" unique="suffix">ProductWithDescription</data> + <data key="price">123.00</data> + <data key="urlKey" unique="suffix">productwithdescription</data> + <data key="status">1</data> + <data key="quantity">100</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductDescription</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionData.xml index ca5024920ad40..d1b0e1e53aea8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionData.xml @@ -38,6 +38,16 @@ <data key="price_type">percent</data> <data key="max_characters">0</data> </entity> + <entity name="ProductOptionAreaFixed" type="product_option"> + <var key="product_sku" entityType="product" entityKey="sku" /> + <data key="title">OptionArea</data> + <data key="type">area</data> + <data key="is_require">false</data> + <data key="sort_order">2</data> + <data key="price">100</data> + <data key="price_type">fixed</data> + <data key="max_characters">0</data> + </entity> <entity name="ProductOptionFile" type="product_option"> <var key="product_sku" entityType="product" entityKey="sku" /> <data key="title">OptionFile</data> @@ -59,6 +69,16 @@ <requiredEntity type="product_option_value">ProductOptionValueDropdown1</requiredEntity> <requiredEntity type="product_option_value">ProductOptionValueDropdown2</requiredEntity> </entity> + <entity name="ProductOptionDropDown2" type="product_option"> + <var key="product_sku" entityType="product" entityKey="sku" /> + <data key="title">OptionDropDown</data> + <data key="type">drop_down</data> + <data key="sort_order">4</data> + <data key="is_require">false</data> + <requiredEntity type="product_option_value">ProductOptionValueDropdown1</requiredEntity> + <requiredEntity type="product_option_value">ProductOptionValueDropdown3</requiredEntity> + </entity> + <entity name="ProductOptionDropDownWithLongValuesTitle" type="product_option"> <var key="product_sku" entityType="product" entityKey="sku" /> <data key="title">OptionDropDownWithLongTitles</data> @@ -68,6 +88,15 @@ <requiredEntity type="product_option_value">ProductOptionValueDropdownLongTitle1</requiredEntity> <requiredEntity type="product_option_value">ProductOptionValueDropdownLongTitle2</requiredEntity> </entity> + <entity name="ProductOptionValueDropdown" type="product_option"> + <var key="product_sku" entityType="product" entityKey="sku" /> + <data key="title">OptionDropDown</data> + <data key="type">drop_down</data> + <data key="sort_order">4</data> + <data key="is_require">true</data> + <requiredEntity type="product_option_value">ProductOptionValueWithSkuDropdown1</requiredEntity> + <requiredEntity type="product_option_value">ProductOptionValueWithSkuDropdown2</requiredEntity> + </entity> <entity name="ProductOptionRadiobutton" type="product_option"> <var key="product_sku" entityType="product" entityKey="sku" /> <data key="title">OptionRadioButtons</data> @@ -77,6 +106,24 @@ <requiredEntity type="product_option_value">ProductOptionValueRadioButtons1</requiredEntity> <requiredEntity type="product_option_value">ProductOptionValueRadioButtons2</requiredEntity> </entity> + <entity name="ProductOptionRadioButton2" type="product_option"> + <var key="product_sku" entityType="product" entityKey="sku" /> + <data key="title">OptionRadioButtons</data> + <data key="type">radio</data> + <data key="sort_order">4</data> + <data key="is_require">false</data> + <requiredEntity type="product_option_value">ProductOptionValueRadioButtons1</requiredEntity> + <requiredEntity type="product_option_value">ProductOptionValueRadioButtons4</requiredEntity> + </entity> + <entity name="ProductOptionRadiobuttonWithTwoFixedOptions" type="product_option"> + <var key="product_sku" entityType="product" entityKey="sku" /> + <data key="title">OptionRadioButtons</data> + <data key="type">radio</data> + <data key="sort_order">5</data> + <data key="is_require">true</data> + <requiredEntity type="product_option_value">ProductOptionValueRadioButtons1</requiredEntity> + <requiredEntity type="product_option_value">ProductOptionValueRadioButtons3</requiredEntity> + </entity> <entity name="ProductOptionCheckbox" type="product_option"> <var key="product_sku" entityType="product" entityKey="sku" /> <data key="title">OptionCheckbox</data> @@ -121,4 +168,12 @@ <data key="price">0.00</data> <data key="price_type">percent</data> </entity> + <entity name="ProductOptionPercentPriceDropDown" type="product_option"> + <var key="product_sku" entityType="product" entityKey="sku" /> + <data key="title">OptionDropDown</data> + <data key="type">drop_down</data> + <data key="sort_order">4</data> + <data key="is_require">true</data> + <requiredEntity type="product_option_value">ProductOptionPercentPriceValueDropdown</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionValueData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionValueData.xml index d16a201cd9ecc..e738994357366 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionValueData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionValueData.xml @@ -32,6 +32,24 @@ <data key="price">99.99</data> <data key="price_type">percent</data> </entity> + <entity name="ProductOptionValueDropdown3" type="product_option_value"> + <data key="title">OptionValueDropDown3</data> + <data key="sort_order">2</data> + <data key="price">10</data> + <data key="price_type">percent</data> + </entity> + <entity name="ProductOptionValueRadioButtons4" type="product_option_value"> + <data key="title">OptionValueRadioButtons4</data> + <data key="sort_order">1</data> + <data key="price">9.99</data> + <data key="price_type">fixed</data> + </entity> + <entity name="ProductOptionValueRadioButtons3" type="product_option_value"> + <data key="title">OptionValueRadioButtons3</data> + <data key="sort_order">3</data> + <data key="price">10</data> + <data key="price_type">fixed</data> + </entity> <entity name="ProductOptionValueCheckbox" type="product_option_value"> <data key="title">OptionValueCheckbox</data> <data key="sort_order">1</data> @@ -62,4 +80,25 @@ <data key="price">20</data> <data key="price_type">percent</data> </entity> + <entity name="ProductOptionPercentPriceValueDropdown" type="product_option_value"> + <data key="title">40 Percent</data> + <data key="sort_order">0</data> + <data key="price">40</data> + <data key="price_type">percent</data> + <data key="sku">sku_drop_down_row_1</data> + </entity> + <entity name="ProductOptionValueWithSkuDropdown1" type="product_option_value"> + <data key="title">ProductOptionValueWithSkuDropdown1</data> + <data key="sort_order">1</data> + <data key="price">10</data> + <data key="price_type">fixed</data> + <data key="sku">product_option_value_sku_dropdown_1_</data> + </entity> + <entity name="ProductOptionValueWithSkuDropdown2" type="product_option_value"> + <data key="title">ProductOptionValueWithSkuDropdown2</data> + <data key="sort_order">1</data> + <data key="price">10</data> + <data key="price_type">fixed</data> + <data key="sku">product_option_value_sku_dropdown_2_</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml index e5070340421a9..0c88c666a20ca 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml @@ -56,4 +56,12 @@ <data key="quantity">2</data> <var key="sku" entityType="product2" entityKey="sku" /> </entity> + <entity name="tierProductPriceDefault" type="catalogTierPrice"> + <data key="price">90.00</data> + <data key="price_type">fixed</data> + <data key="website_id">0</data> + <data key="customer_group">ALL GROUPS</data> + <data key="quantity">30</data> + <var key="sku" entityType="product" entityKey="sku" /> + </entity> </entities> \ No newline at end of file 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/AdminProductCustomizableOptionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml index 352d219351fb8..e9f0de14da34d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml @@ -36,6 +36,8 @@ <!-- Elements that make it easier to select the most recently added element --> <element name="lastOptionTitle" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[contains(@class, '_required')]//input" /> <element name="lastOptionTypeParent" type="block" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[contains(@class, 'admin__action-multiselect-text')]" /> + <element name="lastOptionPrice" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[contains(@name, '[price]')]"/> + <element name="lastOptionPriceType" type="select" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[contains(@name, '[price_type]')]"/> <!-- var 1 represents the option type that you want to select, i.e "radio buttons" --> <element name="optionType" type="block" selector="//*[@data-index='custom_options']//label[text()='{{var1}}'][ancestor::*[contains(@class, '_active')]]" parameterized="true" /> <element name="addValue" type="button" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[@data-action='add_new_row']" timeout="30"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml index bc7c472df6eac..a4517cfd46168 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml @@ -28,5 +28,7 @@ <element name="notifyBelowQty" type="input" selector="//*[@name='product[stock_data][notify_stock_qty]']"/> <element name="advancedInventoryQty" type="input" selector="//div[@class='modal-inner-wrap']//input[@name='product[quantity_and_stock_status][qty]']"/> <element name="advancedInventoryStockStatus" type="select" selector="//div[@class='modal-inner-wrap']//select[@name='product[quantity_and_stock_status][is_in_stock]']"/> + <element name="outOfStockThreshold" type="select" selector="//*[@name='product[stock_data][min_qty]']" timeout="30"/> + <element name="minQtyConfigSetting" type="checkbox" selector="//input[@name='product[stock_data][use_config_min_qty]']" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml index 697648cedb7ba..a17bd3794cffd 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml @@ -10,10 +10,12 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductFormAdvancedPricingSection"> <element name="customerGroupPriceAddButton" type="button" selector="[data-action='add_new_row']" timeout="30"/> + <element name="addCustomerGroupPrice" type="button" selector="//span[text()='Add']/ancestor::button" timeout="30"/> <element name="customerGroupPriceDeleteButton" type="button" selector="[data-action='remove_row']" timeout="30"/> <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"/> @@ -21,6 +23,7 @@ <element name="specialPrice" type="input" selector="input[name='product[special_price]']"/> <element name="doneButton" type="button" selector=".product_form_product_form_advanced_pricing_modal button.action-primary" timeout="5"/> <element name="msrp" type="input" selector="//input[@name='product[msrp]']" timeout="30"/> + <element name="msrpType" type="select" selector="//select[@name='product[msrp_display_actual_price_type]']" timeout="30"/> <element name="save" type="button" selector="#save-button"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml index 939974248aabf..3b6f24c0f259d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml @@ -35,5 +35,6 @@ <element name="nthRow" type="block" selector=".data-row:nth-of-type({{var}})" parameterized="true" timeout="30"/> <element name="productCount" type="text" selector="#catalog_category_products-total-count"/> <element name="productPerPage" type="select" selector="#catalog_category_products_page-limit"/> + <element name="storeViewDropdown" type="text" selector="//select[@name='store_id']/option[contains(.,'{{storeView}}')]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml index c6ea96715cf82..b2cd0f5f9af9f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml @@ -12,5 +12,6 @@ <element name="quantity" type="button" selector="span.counter-number"/> <element name="show" type="button" selector="a.showcart"/> <element name="goToCheckout" type="button" selector="#top-cart-btn-checkout" timeout="30"/> + <element name="emptyMiniCart" type="text" selector="//div[@class='minicart-wrapper']//span[@class='counter qty empty']/../.."/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml index 8393cee57996f..fd412d3c7dee1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -40,6 +40,7 @@ <!-- The 1st parameter is the nth custom option, the 2nd parameter is the nth value in the option --> <element name="nthCustomOptionInput" type="radio" selector="//*[@id='product-options-wrapper']/*[@class='fieldset']/*[contains(@class, 'field')][{{customOptionNum}}]//*[contains(@class, 'admin__field-option')][{{customOptionValueNum}}]//input" parameterized="true" /> + <element name="customOptionByTitle" type="text" selector="//span[text()='{{title}}']/ancestor::div[contains(@class, 'field ') and contains(@class, 'required')]" parameterized="true" timeout="30"/> <element name="productOptionRadioButtonsCheckbox" type="checkbox" selector="//*[@id='product-options-wrapper']//div[@class='fieldset']//label[contains(.,'{{var1}}')]/../div[@class='control']//input[@price='{{var2}}']" parameterized="true"/> <element name="productOptionDataMonth" type="date" selector="//*[@id='product-options-wrapper']//div[@class='fieldset']//legend[contains(.,'{{var1}}')]/../div[@class='control']//select[@data-calendar-role='month']" parameterized="true"/> <element name="productOptionDataDay" type="date" selector="//*[@id='product-options-wrapper']//div[@class='fieldset']//legend[contains(.,'{{var1}}')]/../div[@class='control']//select[@data-calendar-role='day']" parameterized="true"/> @@ -86,5 +87,21 @@ <element name="customSelectOptions" type="select" selector="#{{selectId}} option" parameterized="true"/> <element name="requiredCustomInput" type="text" selector="//div[contains(.,'{{customOptionTitle}}') and contains(@class, 'required') and .//input[@aria-required='true']]" parameterized="true"/> <element name="requiredCustomSelect" type="select" selector="//div[contains(.,'{{customOptionTitle}}') and contains(@class, 'required') and .//select[@aria-required='true']]" parameterized="true"/> + <element name="requiredCustomField" type="text" selector="//div[@class='field required']/label/span[contains(.,'{{optionTitle}}')]//../../div/div[contains(.,'This is a required field.')]" parameterized="true"/> + <element name="requiredCustomFile" type="text" selector="//div[@class='field file required']/label/span[contains(.,'{{OptionFileTitle}}')]//../../div/div[contains(.,'This is a required field.')]" parameterized="true"/> + <element name="requiredCustomTextArea" type="text" selector="//div[@class='field textarea required']/label/span[contains(.,'{{OptionAreaTitle}}')]//../../div/div[contains(.,'This is a required field.')]" parameterized="true"/> + <element name="requiredCustomDate" type="text" selector="//div[@class='field date required']//span[text()='{{OptionDateTitle}}']//../../div/div[contains(.,'This is a required field.')]" parameterized="true"/> + <element name="customOptionField" type="input" selector="//input[contains(@class,'input-text product-custom-option')]"/> + <element name="customOptionTextArea" type="textarea" selector="//textarea[contains(@class,'product-custom-option')]"/> + <element name="customOptionDropDown" type="select" selector="//select[contains(@class,' required product-custom-option admin__control-select')]/option[contains(.,'{{option}}')]" parameterized="true"/> + <element name="customRadioOption" type="checkbox" selector="//div/input[@type='radio']/../label/span"/> + <element name="customOptionCheckBox" type="checkbox" selector="//div/input[@type='checkbox']/../label/span[contains(.,'{{option}}')]" parameterized="true"/> + <element name="customMultiSelectOption" type="select" selector="//select[contains(@class,'multiselect admin__control-multiselect required product-custom-option')]/option[contains(.,'{{option'}})]" parameterized="true"/> + <element name="customOptionMonth" type="date" selector="//div[@class='field date required']//span[text()='{{option}}']/../..//div/select[@data-calendar-role='month']" parameterized="true"/> + <element name="customOptionDay" type="date" selector="//div[@class='field date required']//span[text()='{{option}}']/../..//div/select[@data-calendar-role='day']" parameterized="true"/> + <element name="customOptionYear" type="date" selector="//div[@class='field date required']//span[text()='{{option}}']/../..//div/select[@data-calendar-role='year']" parameterized="true"/> + <element name="customOptionHour" type="date" selector="//div[@class='field date required']//span[text()='{{option}}']/../..//div/select[@data-calendar-role='hour']" parameterized="true"/> + <element name="customOptionMinute" type="date" selector="//div[@class='field date required']//span[text()='{{option}}']/../..//div/select[@data-calendar-role='minute']" parameterized="true"/> + <element name="customOptionDayPart" type="date" selector="//div[@class='field date required']//span[text()='{{option}}']/../..//div/select[@data-calendar-role='day_part']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml index 8055ecfe00cde..78818dd37a5d4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml @@ -23,5 +23,7 @@ <element name="shipping" type="input" selector="span[data-th='Shipping']"/> <element name="orderTotal" type="input" selector=".grand.totals .amount .price"/> <element name="customOptionDropDown" type="select" selector="//*[@id='product-options-wrapper']//select[contains(@class, 'product-custom-option admin__control-select')]"/> + <element name="qtyInputWithProduct" type="input" selector="//tr//strong[contains(.,'{{productName}}')]/../../td[@class='col qty']//input" parameterized="true"/> + <element name="customOptionRadio" type="input" selector="//span[contains(text(),'{{customOption}}')]/../../input" parameterized="true"/> </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/AdminCreateNewAttributeFromProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml index 282331924bca3..02615ca5dd254 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml @@ -11,6 +11,7 @@ <test name="AdminCreateNewAttributeFromProductTest"> <annotations> <features value="Catalog"/> + <stories value="Product attributes"/> <title value="Check that New Attribute from Product is create"/> <description value="Check that New Attribute from Product is create"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteAttributeSetTest.xml index 4d28ccbd44d2c..2f0482b3b36d5 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteAttributeSetTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteAttributeSetTest.xml @@ -10,6 +10,7 @@ <test name="AdminDeleteAttributeSetTest"> <annotations> <features value="Catalog"/> + <stories value="Attribute sets"/> <title value="Delete Attribute Set"/> <description value="Admin should be able to delete an attribute set"/> <testCaseId value="MC-4413"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml index 54b83e034fb11..d0036a2adea5a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml @@ -10,6 +10,7 @@ <test name="DeleteProductAttributeTest"> <annotations> <features value="Catalog"/> + <stories value="Product attributes"/> <title value="Delete Product Attribute"/> <description value="Admin should able to delete a product attribute"/> <testCaseId value="MC-10887"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml index b24ed7f9c9a81..053f165c8745c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml @@ -11,6 +11,7 @@ <test name="AdminGridPageNumberAfterSaveAndCloseActionTest"> <annotations> <features value="Catalog"/> + <stories value="Catalog grid"/> <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"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml index 79ff7bcade77b..4e182c2e0e5ac 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml @@ -11,6 +11,7 @@ <test name="AdminImportCustomizableOptionToProductWithSKUTest"> <annotations> <features value="Catalog"/> + <stories value="Customizable options"/> <title value="Import customizable options to a product with existing SKU"/> <description value="Import customizable options to a product with existing SKU"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml index 234a7c69913c9..eb014ca7f884d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml @@ -13,6 +13,7 @@ <stories value="View sorting by websites"/> <title value="Sorting by websites in Admin"/> <description value="Sorting products by websites in Admin"/> + <severity value="AVERAGE"/> </annotations> <before> <createData entity="_defaultCategory" stepKey="createCategory"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml index 51af9d78dfd46..4bcb82372e801 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml @@ -11,6 +11,7 @@ <test name="AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest"> <annotations> <features value="Catalog"/> + <stories value="Tier price"/> <title value="Check that 'tier price' block not available for simple product from options without 'tier price'"/> <description value="Check that 'tier price' block not available for simple product from options without 'tier price'"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml index 3dd55a9dfee92..461ebde29fcad 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml @@ -11,6 +11,7 @@ <test name="ProductAvailableAfterEnablingSubCategoriesTest"> <annotations> <features value="Catalog"/> + <stories value="Categories"/> <title value="Check that parent categories are showing products after enabling subcategories after fully reindex"/> <description value="Check that parent categories are showing products after enabling subcategories after fully reindex"/> <severity value="MAJOR"/> 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/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml index 268e18d2b4efa..514e12bb355a8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml @@ -11,6 +11,7 @@ <test name="StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest"> <annotations> <features value="Catalog"/> + <stories value="Special price"/> <title value="Check that special price displayed when 'default config' scope timezone does not match 'website' scope timezone"/> <description value="Check that special price displayed when 'default config' scope timezone does not match 'website' scope timezone"/> <severity value="MAJOR"/> 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/Ui/DataProvider/Product/Form/Modifier/Websites.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php index 9cbbb86a2c555..7d1d5d4d77d1b 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php @@ -165,7 +165,7 @@ protected function getFieldsForFieldset() $websitesList = $this->getWebsitesList(); $isNewProduct = !$this->locator->getProduct()->getId(); $tooltip = [ - 'link' => 'http://docs.magento.com/m2/ce/user_guide/configuration/scope.html', + 'link' => 'https://docs.magento.com/m2/ce/user_guide/configuration/scope.html', 'description' => __( 'If your Magento installation has multiple websites, ' . 'you can edit the scope to use the product on specific sites.' 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/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/AdminCreateNewCatalogPriceRuleActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCreateNewCatalogPriceRuleActionGroup.xml new file mode 100644 index 0000000000000..4599e325e39cb --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCreateNewCatalogPriceRuleActionGroup.xml @@ -0,0 +1,30 @@ +<?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="AdminCreateNewCatalogPriceRuleActionGroup"> + <arguments> + <argument name="catalogRule" defaultValue="_defaultCatalogRule"/> + <argument name="customerGroup" type="string"/> + </arguments> + <amOnPage url="{{AdminNewCatalogPriceRulePage.url}}" stepKey="openNewCatalogPriceRulePage"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <fillField stepKey="fillName" selector="{{AdminNewCatalogPriceRule.ruleName}}" userInput="{{catalogRule.name}}"/> + <fillField stepKey="fillDescription" selector="{{AdminNewCatalogPriceRule.description}}" userInput="{{catalogRule.description}}"/> + <selectOption selector="{{AdminNewCatalogPriceRule.status}}" userInput="{{catalogRule.is_active}}" stepKey="selectStatus"/> + <selectOption stepKey="selectWebSite" selector="{{AdminNewCatalogPriceRule.websites}}" userInput="{{catalogRule.website_ids[0]}}"/> + <selectOption selector="{{AdminNewCatalogPriceRule.customerGroups}}" userInput="{{customerGroup}}" stepKey="selectCustomerGroup"/> + <scrollTo selector="{{AdminNewCatalogPriceRule.actionsTab}}" stepKey="scrollToActionTab"/> + <click stepKey="openActionDropdown" selector="{{AdminNewCatalogPriceRule.actionsTab}}"/> + <selectOption stepKey="discountType" selector="{{AdminNewCatalogPriceRuleActions.apply}}" userInput="{{catalogRule.simple_action}}"/> + <fillField stepKey="fillDiscountValue" selector="{{AdminNewCatalogPriceRuleActions.discountAmount}}" userInput="{{catalogRule.discount_amount}}"/> + <selectOption stepKey="discardSubsequentRules" selector="{{AdminNewCatalogPriceRuleActions.disregardRules}}" userInput="Yes"/> + <waitForPageLoad stepKey="waitForApplied"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminDeleteCatalogRuleActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminDeleteCatalogRuleActionGroup.xml new file mode 100644 index 0000000000000..ea2d6821917e3 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminDeleteCatalogRuleActionGroup.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="AdminDeleteCatalogRuleActionGroup"> + <click selector="{{AdminNewCatalogPriceRule.delete}}" stepKey="clickOnDeleteButton"/> + <waitForElementVisible selector="{{AdminNewCatalogPriceRule.okButton}}" stepKey="waitForOkButtonToBeVisible"/> + <click selector="{{AdminNewCatalogPriceRule.okButton}}" stepKey="clickOnOkButton"/> + <waitForPageLoad stepKey="waitForPagetoLoad"/> + <see selector="{{AdminNewCatalogPriceRule.successMessage}}" userInput="You deleted the rule." stepKey="seeSuccessDeleteMessage"/> + </actionGroup> +</actionGroups> \ No newline at end of file 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/AdminSaveAndApplyRulesActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSaveAndApplyRulesActionGroup.xml new file mode 100644 index 0000000000000..2584b8b36b769 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSaveAndApplyRulesActionGroup.xml @@ -0,0 +1,21 @@ +<?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="AdminSaveAndApplyRulesActionGroup"> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <scrollToTopOfPage stepKey="scrollToTop"/> + <click selector="{{AdminNewCatalogPriceRule.save}}" stepKey="saveTheCatalogRule"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <seeElement selector="{{AdminCatalogPriceRuleGrid.updateMessage}}" stepKey="seeMessageToUpdateTheCatalogRules"/> + <see selector="{{AdminNewCatalogPriceRule.successMessage}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> + <click stepKey="applyRules" selector="{{AdminCatalogPriceRuleGrid.applyRules}}"/> + <see stepKey="assertSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="Updated rules applied."/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSearchCatalogRuleInGridActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSearchCatalogRuleInGridActionGroup.xml new file mode 100644 index 0000000000000..c7d5d853642c0 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSearchCatalogRuleInGridActionGroup.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="AdminSearchCatalogRuleInGridActionGroup"> + <arguments> + <argument name="catalogRuleName" type="string"/> + </arguments> + <amOnPage stepKey="goToPriceRulePage" url="{{CatalogRulePage.url}}"/> + <waitForPageLoad stepKey="waitForPriceRulePage"/> + <click selector="{{AdminCatalogPriceRuleGrid.resetFilter}}" stepKey="clickOnResetFilter"/> + <waitForPageLoad stepKey="waitForTheGridPageToLoad"/> + <fillField selector="{{AdminCatalogPriceRuleGrid.ruleFilter}}" userInput="{{catalogRuleName}}" stepKey="fillTheRuleFilter"/> + <click selector="{{AdminCatalogPriceRuleGrid.search}}" stepKey="clickOnSearchButton"/> + <waitForPageLoad stepKey="waitForTheSearchResult"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSelectCatalogRuleFromGridActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSelectCatalogRuleFromGridActionGroup.xml new file mode 100644 index 0000000000000..f0e927ea84048 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSelectCatalogRuleFromGridActionGroup.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="AdminSelectCatalogRuleFromGridActionGroup"> + <arguments> + <argument name="catalogRuleName" type="string"/> + </arguments> + <click selector="{{AdminCatalogPriceRuleGrid.selectRowByRuleName(catalogRuleName)}}" stepKey="selectRow"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogPriceRuleFormActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogPriceRuleFormActionGroup.xml new file mode 100644 index 0000000000000..330c2ad7e15f6 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogPriceRuleFormActionGroup.xml @@ -0,0 +1,29 @@ +<?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="AssertCatalogPriceRuleFormActionGroup"> + <arguments> + <argument name="catalogRule" defaultValue="inactiveCatalogRule" /> + <argument name="status" type="string" defaultValue=""/> + <argument name="websites" type="string"/> + <argument name="customerGroup" type="string"/> + </arguments> + <seeInField stepKey="fillName" selector="{{AdminNewCatalogPriceRule.ruleName}}" userInput="{{catalogRule.name}}"/> + <seeInField stepKey="fillDescription" selector="{{AdminNewCatalogPriceRule.description}}" userInput="{{catalogRule.description}}"/> + <seeOptionIsSelected selector="{{AdminNewCatalogPriceRule.status}}" userInput="{{status}}" stepKey="selectStatus"/> + <see stepKey="seeWebSite" selector="{{AdminNewCatalogPriceRule.websites}}" userInput="{{websites}}"/> + <seeOptionIsSelected selector="{{AdminNewCatalogPriceRule.customerGroups}}" userInput="{{customerGroup}}" stepKey="selectCustomerGroup"/> + <scrollTo selector="{{AdminNewCatalogPriceRule.actionsTab}}" stepKey="scrollToActionTab"/> + <click stepKey="openActionDropdown" selector="{{AdminNewCatalogPriceRule.actionsTab}}"/> + <seeInField stepKey="discountType" selector="{{AdminNewCatalogPriceRuleActions.apply}}" userInput="{{catalogRule.simple_action}}"/> + <seeInField stepKey="fillDiscountValue" selector="{{AdminNewCatalogPriceRuleActions.discountAmount}}" userInput="{{catalogRule.discount_amount}}"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogRuleInGridActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogRuleInGridActionGroup.xml new file mode 100644 index 0000000000000..8be41c3b07af6 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AssertCatalogRuleInGridActionGroup.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="AssertCatalogRuleInGridActionGroup"> + <arguments> + <argument name="catalogRuleName" type="string"/> + <argument name="status" type="string" defaultValue=""/> + <argument name="websites" type="string"/> + <argument name="catalogRuleId" type="string"/> + </arguments> + <see selector="{{AdminCatalogPriceRuleGrid.firstRow}}" userInput="{{catalogRuleId}}" stepKey="seeCatalogRuleId"/> + <see selector="{{AdminCatalogPriceRuleGrid.firstRow}}" userInput="{{catalogRuleName}}" stepKey="seeCatalogRuleName"/> + <see selector="{{AdminCatalogPriceRuleGrid.firstRow}}" userInput="{{status}}" stepKey="seeCatalogRuleStatus"/> + <see selector="{{AdminCatalogPriceRuleGrid.firstRow}}" userInput="{{websites}}" stepKey="seeCatalogRuleWebsite"/> + </actionGroup> +</actionGroups> \ No newline at end of file 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/ActionGroup/CatalogPriceRuleActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml index b0c4f2d8a609f..90f4f22bcf631 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml @@ -27,8 +27,8 @@ <click stepKey="clickToCalender" selector="{{AdminNewCatalogPriceRule.toDateButton}}"/> <click stepKey="clickToToday" selector="{{AdminNewCatalogPriceRule.todayDate}}"/> <click stepKey="openActionDropdown" selector="{{AdminNewCatalogPriceRule.actionsTab}}"/> - <selectOption stepKey="discountType" selector="{{AdminNewCatalogPriceRuleActions.apply}}" userInput="{{catalogRule.simple_action}}"/> <fillField stepKey="fillDiscountValue" selector="{{AdminNewCatalogPriceRuleActions.discountAmount}}" userInput="{{catalogRule.discount_amount}}"/> + <selectOption stepKey="discountType" selector="{{AdminNewCatalogPriceRuleActions.apply}}" userInput="{{catalogRule.simple_action}}"/> <selectOption stepKey="discardSubsequentRules" selector="{{AdminNewCatalogPriceRuleActions.disregardRules}}" userInput="Yes"/> <!-- Scroll to top and either save or save and apply after the action group --> @@ -70,7 +70,6 @@ <click selector="{{AdminNewCatalogPriceRule.fromDateButton}}" stepKey="clickFromCalender"/> <click selector="{{AdminNewCatalogPriceRule.todayDate}}" stepKey="clickFromToday"/> </actionGroup> - <!-- Apply all of the saved catalog price rules --> <actionGroup name="applyCatalogPriceRules"> <amOnPage stepKey="goToPriceRulePage" url="{{CatalogRulePage.url}}"/> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogSelectCustomerGroupActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogSelectCustomerGroupActionGroup.xml new file mode 100644 index 0000000000000..da961abac304b --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogSelectCustomerGroupActionGroup.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="CatalogSelectCustomerGroupActionGroup"> + <arguments> + <argument name="customerGroupName" defaultValue="NOT LOGGED IN" type="string"/> + </arguments> + <selectOption selector="{{AdminNewCatalogPriceRule.customerGroups}}" userInput="{{customerGroupName}}" stepKey="selectCustomerGroup"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup.xml new file mode 100644 index 0000000000000..41323b8f3b4dc --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup.xml @@ -0,0 +1,26 @@ +<?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="CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup"> + <arguments> + <argument name="attributeName" type="string"/> + <argument name="targetSelectValue" type="string"/> + <argument name="indexA" type="string"/> + <argument name="indexB" type="string"/> + </arguments> + <click selector="{{AdminNewCatalogPriceRule.conditionsTab}}" stepKey="openConditionsTab"/> + <waitForPageLoad stepKey="waitForConditionTabOpened"/> + <click selector="{{AdminNewCatalogPriceRuleConditions.newCondition}}" stepKey="addNewCondition"/> + <selectOption selector="{{AdminNewCatalogPriceRuleConditions.conditionSelect(indexA)}}" userInput="{{attributeName}}" stepKey="selectTypeCondition"/> + <waitForElement selector="{{AdminNewCatalogPriceRuleConditions.ellipsisValue(indexA)}}" stepKey="waitForTarget"/> + <click selector="{{AdminNewCatalogPriceRuleConditions.ellipsisValue(indexA)}}" stepKey="clickOnEllipsis"/> + <selectOption selector="{{AdminNewCatalogPriceRuleConditions.targetEllipsisSelect(indexA, indexB)}}" userInput="{{targetSelectValue}}" stepKey="selectOption"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/SaveAndApplyCatalogPriceRuleActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/SaveAndApplyCatalogPriceRuleActionGroup.xml new file mode 100644 index 0000000000000..bc75414e0de21 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/SaveAndApplyCatalogPriceRuleActionGroup.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="SaveAndApplyCatalogPriceRuleActionGroup"> + <waitForElementVisible selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="waitForSaveAndApplyButton"/> + <click selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="saveAndApply"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule." stepKey="assertSuccess"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml b/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml index 5b75708d1ae0a..2bf3f064a9c51 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml @@ -94,4 +94,31 @@ <data key="simple_action">by_percent</data> <data key="discount_amount">10</data> </entity> + <entity name="InactiveCatalogRule" type="catalogRule"> + <data key="name" unique="suffix">InactiveCatalogRule</data> + <data key="description">Inactive Catalog Price Rule Description</data> + <data key="is_active">0</data> + <array key="customer_group_ids"> + <item>1</item> + </array> + <array key="website_ids"> + <item>1</item> + </array> + <data key="simple_action">by_fixed</data> + <data key="discount_amount">10</data> + </entity> + + <entity name="CatalogRuleWithoutDiscount" type="catalogRule"> + <data key="name" unique="suffix">CatalogPriceRuleWithoutDiscount</data> + <data key="description">Catalog Price Rule Description</data> + <data key="is_active">1</data> + <array key="customer_group_ids"> + <item>0</item> + </array> + <array key="website_ids"> + <item>1</item> + </array> + <data key="simple_action">by_percent</data> + <data key="discount_amount">0</data> + </entity> </entities> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Page/AdminNewCatalogPriceRulePage.xml b/app/code/Magento/CatalogRule/Test/Mftf/Page/AdminNewCatalogPriceRulePage.xml new file mode 100644 index 0000000000000..fded4f5e5f322 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Page/AdminNewCatalogPriceRulePage.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="AdminNewCatalogPriceRulePage" 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/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/Section/AdminNewCatalogPriceRuleSection.xml b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml index 635260888e7fb..5cb69cb6d7f45 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml @@ -13,6 +13,8 @@ <element name="save" type="button" selector="#save" timeout="30"/> <element name="saveAndContinue" type="button" selector="#save_and_continue" timeout="30"/> <element name="delete" type="button" selector="#delete" timeout="30"/> + <element name="okButton" type="button" selector="//button[@class='action-primary action-accept']"/> + <element name="successMessage" type="text" selector="#messages"/> <element name="ruleName" type="input" selector="[name='name']"/> <element name="description" type="textarea" selector="[name='description']"/> @@ -42,6 +44,8 @@ <element name="conditionSelect" type="select" selector="select#conditions__{{var}}__new_child" parameterized="true"/> <element name="targetEllipsis" type="button" selector="//li[{{var}}]//a[@class='label'][text() = '...']" parameterized="true"/> <element name="targetEllipsisValue" type="button" selector="//ul[@id='conditions__{{var}}__children']//a[contains(text(), '{{var1}}')]" parameterized="true" timeout="30"/> + <element name="ellipsisValue" type="button" selector="//ul[@id='conditions__{{var}}__children']//a[contains(text(), '...')]" parameterized="true" timeout="30"/> + <element name="targetEllipsisSelect" type="select" selector="select#conditions__{{var1}}--{{var2}}__value" parameterized="true" timeout="30"/> <element name="targetSelect" type="select" selector="//ul[@id='conditions__{{var}}__children']//select" parameterized="true" timeout="30"/> <element name="targetInput" type="input" selector="input#conditions__{{var1}}--{{var2}}__value" parameterized="true"/> <element name="applyButton" type="button" selector="#conditions__{{var1}}__children li:nth-of-type({{var2}}) a.rule-param-apply" parameterized="true"/> @@ -49,5 +53,11 @@ <section name="AdminCatalogPriceRuleGrid"> <element name="applyRules" type="button" selector="#apply_rules" timeout="30"/> + <element name="updateMessage" type="text" selector="//div[@class='message message-notice notice']//div[contains(.,'We found updated rules that are not applied. Please click')]"/> + <element name="ruleFilter" type="input" selector="//td[@data-column='name']/input[@name='name']"/> + <element name="resetFilter" type="button" selector="//button[@title='Reset Filter']" timeout="30"/> + <element name="search" type="button" selector="//div[@id='promo_catalog_grid']//button[@title='Search']" timeout="30"/> + <element name="selectRowByRuleName" type="text" selector="//tr[@data-role='row']//td[contains(.,'{{ruleName}}')]" parameterized="true"/> + <element name="firstRow" type="text" selector="//tr[@data-role='row']"/> </section> </sections> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateInactiveCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateInactiveCatalogPriceRuleTest.xml new file mode 100644 index 0000000000000..5223b18df4e4a --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateInactiveCatalogPriceRuleTest.xml @@ -0,0 +1,75 @@ +<?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="AdminCreateInactiveCatalogPriceRuleTest"> + <annotations> + <stories value="Create Catalog Price Rule"/> + <title value="Create Inactive Catalog Price Rule"/> + <description value="Login as admin and create inactive catalog price Rule"/> + <testCaseId value="MC-13977"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + </before> + <after> + <actionGroup ref="AdminSearchCatalogRuleInGridActionGroup" stepKey="searchCreatedCatalogRule"> + <argument name="catalogRuleName" value="{{InactiveCatalogRule.name}}"/> + </actionGroup> + <actionGroup ref="AdminSelectCatalogRuleFromGridActionGroup" stepKey="selectCreatedCatalogRule"> + <argument name="catalogRuleName" value="{{InactiveCatalogRule.name}}"/> + </actionGroup> + <actionGroup ref="AdminDeleteCatalogRuleActionGroup" stepKey="deleteTheCatalogRule"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create Inactive Catalog Price Rule --> + <actionGroup ref="AdminCreateNewCatalogPriceRuleActionGroup" stepKey="createCatalogPriceRule"> + <argument name="catalogRule" value="InactiveCatalogRule"/> + <argument name="customerGroup" value="General"/> + </actionGroup> + + <!-- Save and Apply Rules --> + <actionGroup ref="AdminSaveAndApplyRulesActionGroup" stepKey="saveAndApplyRules"/> + + <!-- Search Catalog Rule in Grid --> + <actionGroup ref="AdminSearchCatalogRuleInGridActionGroup" stepKey="searchAndSelectCreatedCatalogRule"> + <argument name="catalogRuleName" value="{{InactiveCatalogRule.name}}"/> + </actionGroup> + + <!--Select Catalog Rule in Grid --> + <actionGroup ref="AdminSelectCatalogRuleFromGridActionGroup" stepKey="selectCreatedCatalogRule"> + <argument name="catalogRuleName" value="{{InactiveCatalogRule.name}}"/> + </actionGroup> + <grabFromCurrentUrl stepKey="catalogRuleId" regex="#\/([0-9]*)?\/$#"/> + + <!-- Assert catalog Price Rule Form --> + <actionGroup ref="AssertCatalogPriceRuleFormActionGroup" stepKey="assertCatalogRuleForm"> + <argument name="catalogRule" value="InactiveCatalogRule"/> + <argument name="status" value="Inactive"/> + <argument name="websites" value="Main Website"/> + <argument name="customerGroup" value="General"/> + </actionGroup> + + <!-- Search Catalog Rule in Grid --> + <actionGroup ref="AdminSearchCatalogRuleInGridActionGroup" stepKey="searchCreatedCatalogRule"> + <argument name="catalogRuleName" value="{{InactiveCatalogRule.name}}"/> + </actionGroup> + + <!-- Assert Catalog Rule In Grid --> + <actionGroup ref="AssertCatalogRuleInGridActionGroup" stepKey="assertCatalogRuleInGrid"> + <argument name="catalogRuleName" value="{{InactiveCatalogRule.name}}"/> + <argument name="status" value="Inactive"/> + <argument name="websites" value="Main Website"/> + <argument name="catalogRuleId" value="$catalogRuleId"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml index 053a8c33e640c..0ff7e0cd85217 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml @@ -11,6 +11,7 @@ <test name="AdminEnableAttributeIsUndefinedCatalogPriceRuleTest"> <annotations> <features value="CatalogRule"/> + <stories value="Catalog price rule"/> <title value="Enable 'is undefined' condition to Scope Catalog Price rules by custom product attribute"/> <description value="Enable 'is undefined' condition to Scope Catalog Price rules by custom product attribute"/> <severity value="CRITICAL"/> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml new file mode 100644 index 0000000000000..be2e31d0766bd --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml @@ -0,0 +1,179 @@ +<?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="ApplyCatalogPriceRuleForSimpleProductAndConfigurableProductTest"> + <annotations> + <features value="CatalogRule"/> + <stories value="Apply catalog price rule"/> + <title value="Admin should be able to apply the catalog price rule for simple product and configurable product"/> + <description value="Admin should be able to apply the catalog price rule for simple product and configurable product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14770"/> + <group value="CatalogRule"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Create Simple Product --> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">56.78</field> + </createData> + + <!-- Create the configurable product based on the data in the /data folder --> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Make the configurable product have two options, that are children of the default attribute set --> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Create the 2 children that will be a part of the configurable product --> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + + <!-- Assign the two products to the configurable product --> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + </before> + <after> + <!-- Delete the catalog price rule --> + <amOnPage stepKey="goToPriceRulePage" url="{{CatalogRulePage.url}}"/> + <actionGroup stepKey="deletePriceRule" ref="deleteEntitySecondaryGrid"> + <argument name="name" value="{{_defaultCatalogRule.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + + <!-- Logout --> + <actionGroup ref="logout" stepKey="logout"/> + + <!-- Delete products and category --> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + <deleteData createDataKey="createCategory" stepKey="deleteApiCategory"/> + </after> + <!-- Begin creating a new catalog price rule --> + <actionGroup ref="newCatalogPriceRuleByUIWithConditionIsCategory" stepKey="newCatalogPriceRuleByUIWithConditionIsCategory"> + <argument name ="categoryId" value="$createCategory.id$"/> + </actionGroup> + + <!-- Select not logged in customer group --> + <actionGroup ref="selectNotLoggedInCustomerGroupActionGroup" stepKey="selectNotLoggedInCustomerGroup"/> + + <!-- Save and apply the new catalog price rule --> + <actionGroup ref="SaveAndApplyCatalogPriceRuleActionGroup" stepKey="saveAndApplyCatalogPriceRule"/> + + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + + <!-- Navigate to category on store front --> + <amOnPage url="{{StorefrontProductPage.url($createCategory.name$)}}" stepKey="goToCategoryPage"/> + + <!-- Check simple product name on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1Name"> + <argument name="productInfo" value="$createSimpleProduct.name$"/> + <argument name="productNumber" value="2"/> + </actionGroup> + + <!-- Check simple product price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1Price"> + <argument name="productInfo" value="$51.10"/> + <argument name="productNumber" value="2"/> + </actionGroup> + + <!-- Check simple product regular price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1RegularPrice"> + <argument name="productInfo" value="$56.78"/> + <argument name="productNumber" value="2"/> + </actionGroup> + + <!-- Check configurable product name on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct2Name"> + <argument name="productInfo" value="$createConfigProduct.name$"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Check configurable product price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct2Price"> + <argument name="productInfo" value="$110.70"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Navigate to simple product on store front --> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct.name$)}}" stepKey="goToProductPage1"/> + + <!-- Assert regular and special price after selecting ProductOptionValueDropdown1 --> + <actionGroup ref="AssertStorefrontProductPricesActionGroup" stepKey="assertStorefrontProductPrices"> + <argument name="productPrice" value="$56.78"/> + <argument name="productFinalPrice" value="$51.10"/> + </actionGroup> + + <!-- Add simple product to cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="cartAddSimpleProduct1ToCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Open configurable product 1 select option 1 attribute --> + <amOnPage url="{{StorefrontProductPage.url($$createConfigProduct.custom_attributes[url_key]$$)}}" stepKey="amOnConfigurableProductPage"/> + <selectOption selector="{{StorefrontProductInfoMainSection.productOptionSelect('$$createConfigProductAttribute.attribute[frontend_labels][0][label]$$')}}" userInput="$$createConfigProductAttributeOption1.option[store_labels][0][label]$$" stepKey="selectOption"/> + <actionGroup ref="AssertStorefrontProductPricesActionGroup" stepKey="assertStorefrontProductPrices2"> + <argument name="productPrice" value="$123.00"/> + <argument name="productFinalPrice" value="$110.70"/> + </actionGroup> + + <!-- Assert regular and special price after selecting ProductOptionValueDropdown1 --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="cartAddConfigProduct1ToCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Assert sub total on mini shopping cart --> + <actionGroup ref="AssertSubTotalOnStorefrontMiniCartActionGroup" stepKey="assertSubTotalOnStorefrontMiniCart"> + <argument name="subTotal" value="$161.80"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductAndFixedMethodTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductAndFixedMethodTest.xml new file mode 100644 index 0000000000000..7b7953c1d9b06 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductAndFixedMethodTest.xml @@ -0,0 +1,108 @@ +<?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="ApplyCatalogRuleForSimpleProductAndFixedMethodTest"> + <annotations> + <features value="CatalogRule"/> + <stories value="Apply catalog price rule"/> + <title value="Admin should be able to apply the catalog price rule for simple product with custom options"/> + <description value="Admin should be able to apply the catalog price rule for simple product with custom options"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14771"/> + <group value="CatalogRule"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Create category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + + <!-- Create Simple Product --> + <createData entity="_defaultProduct" stepKey="createProduct1"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">56.78</field> + </createData> + + <!-- Update all products to have custom options --> + <updateData createDataKey="createProduct1" entity="productWithFixedOptions" stepKey="updateProductWithOptions1"/> + </before> + <after> + <!-- Delete products and category --> + <deleteData createDataKey="createProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete the catalog price rule --> + <amOnPage stepKey="goToPriceRulePage" url="{{CatalogRulePage.url}}"/> + <actionGroup stepKey="deletePriceRule" ref="deleteEntitySecondaryGrid"> + <argument name="name" value="{{CatalogRuleByFixed.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + + <!-- Logout --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- 1. Begin creating a new catalog price rule --> + <actionGroup ref="newCatalogPriceRuleByUIWithConditionIsCategory" stepKey="newCatalogPriceRuleByUIWithConditionIsCategory"> + <argument name ="categoryId" value="$createCategory.id$"/> + <argument name ="catalogRule" value="CatalogRuleByFixed"/> + </actionGroup> + + <!-- Select not logged in customer group --> + <actionGroup ref="selectNotLoggedInCustomerGroupActionGroup" stepKey="selectNotLoggedInCustomerGroup"/> + + <!-- Save and apply the new catalog price rule --> + <actionGroup ref="SaveAndApplyCatalogPriceRuleActionGroup" stepKey="saveAndApplyCatalogPriceRule"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + + <!-- Navigate to category on store front --> + <amOnPage url="{{StorefrontProductPage.url($createCategory.name$)}}" stepKey="goToCategoryPage"/> + + <!-- Check product 1 name on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1Name"> + <argument name="productInfo" value="$createProduct1.name$"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Check product 1 price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1Price"> + <argument name="productInfo" value="$44.48"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Check product 1 regular price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1RegularPrice"> + <argument name="productInfo" value="$56.78"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Navigate to product 1 on store front --> + <amOnPage url="{{StorefrontProductPage.url($createProduct1.name$)}}" stepKey="goToProductPage1"/> + + <!-- Assert regular and special price after selecting ProductOptionValueDropdown1 --> + <actionGroup ref="StorefrontSelectCustomOptionRadioAndAssertPricesActionGroup" stepKey="storefrontSelectCustomOptionAndAssertPrices1"> + <argument name="customOption" value="ProductOptionRadioButton2"/> + <argument name="customOptionValue" value="ProductOptionValueRadioButtons1"/> + <argument name="productPrice" value="$156.77"/> + <argument name="productFinalPrice" value="$144.47"/> + </actionGroup> + + <!-- Add product 1 to cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="cartAddSimpleProduct1ToCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Assert sub total on mini shopping cart --> + <actionGroup ref="AssertSubTotalOnStorefrontMiniCartActionGroup" stepKey="assertSubTotalOnStorefrontMiniCart"> + <argument name="subTotal" value="$144.47"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest.xml new file mode 100644 index 0000000000000..e4af21cac6723 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest.xml @@ -0,0 +1,159 @@ +<?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="ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest"> + <annotations> + <features value="CatalogRule"/> + <stories value="Apply catalog price rule"/> + <title value="Admin should be able to apply the catalog price rule for simple product for new customer group"/> + <description value="Admin should be able to apply the catalog price rule for simple product for new customer group"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14772"/> + <group value="CatalogRule"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Create new customer group --> + <createData entity="CustomCustomerGroup" stepKey="customerGroup" /> + + <!--Creating customer--> + <createData entity="Simple_US_Customer" stepKey="createCustomer" > + <field key="group_id">$customerGroup.id$</field> + </createData> + + <!-- Create category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + + <!-- Create Simple Product --> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">56.78</field> + </createData> + </before> + <after> + <!-- Delete products and category --> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete customer --> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + + <!-- Delete customer group --> + <deleteData createDataKey="customerGroup" stepKey="deleteCustomerGroup"/> + + <!-- Delete the catalog price rule --> + <amOnPage stepKey="goToPriceRulePage" url="{{CatalogRulePage.url}}"/> + <actionGroup stepKey="deletePriceRule" ref="deleteEntitySecondaryGrid"> + <argument name="name" value="{{CatalogRuleByFixed.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + + <!-- Logout --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- 1. Begin creating a new catalog price rule --> + <actionGroup ref="newCatalogPriceRuleByUIWithConditionIsCategory" stepKey="newCatalogPriceRuleByUIWithConditionIsCategory"> + <argument name ="categoryId" value="$createCategory.id$"/> + <argument name="catalogRule" value="CatalogRuleByFixed"/> + </actionGroup> + + <!-- Select custom customer group --> + <actionGroup ref="CatalogSelectCustomerGroupActionGroup" stepKey="selectCustomCustomerGroup"> + <argument name="customerGroupName" value="$customerGroup.code$"/> + </actionGroup> + + <!-- Save and apply the new catalog price rule --> + <actionGroup ref="SaveAndApplyCatalogPriceRuleActionGroup" stepKey="saveAndApplyCatalogPriceRule"/> + + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + + <!-- Navigate to category on store front --> + <amOnPage url="{{StorefrontProductPage.url($createCategory.name$)}}" stepKey="goToCategoryPage"/> + + <!-- Check product 1 name on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProductName"> + <argument name="productInfo" value="$createSimpleProduct.name$"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Check product 1 has no discounts applied on store front category page --> + <actionGroup ref="AssertDontSeeProductDetailsOnStorefrontActionGroup" stepKey="storefrontProductRegularPrice"> + <argument name="productInfo" value="$44.48"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Check product 1 price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProductPrice"> + <argument name="productInfo" value="$56.78"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Navigate to product 1 on store front --> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct.name$)}}" stepKey="goToProductPage"/> + + <!-- Add product 1 to cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="cartAddSimpleProductToCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Assert sub total on mini shopping cart --> + <actionGroup ref="AssertSubTotalOnStorefrontMiniCartActionGroup" stepKey="assertSubTotalOnStorefrontMiniCart"> + <argument name="subTotal" value="$56.78"/> + </actionGroup> + + <!--Login to storefront as previously created customer--> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <!-- Navigate to category on store front as customer--> + <amOnPage url="{{StorefrontProductPage.url($createCategory.name$)}}" stepKey="goToCategoryPageAsCustomer"/> + + <!-- Check simple product name on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProductNameAsCustomer"> + <argument name="productInfo" value="$createSimpleProduct.name$"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Check simple product price on store front category page as customer --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProductPriceAsCustomer"> + <argument name="productInfo" value="$44.48"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Check simple product regular price on store front category page as customer --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProductRegularPriceAsCustomer"> + <argument name="productInfo" value="$56.78"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Navigate to simple product on store front as customer --> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct.name$)}}" stepKey="goToProductPage1AsCustomer"/> + + <!-- Assert regular and special price as customer--> + <actionGroup ref="AssertStorefrontProductPricesActionGroup" stepKey="assertStorefrontProductPrices"> + <argument name="productPrice" value="$56.78"/> + <argument name="productFinalPrice" value="$44.48"/> + </actionGroup> + + <!-- Add simple product to cart as customer --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="cartAddSimpleProductToCartAsCustomer"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Assert sub total on mini shopping cart as customer --> + <actionGroup ref="AssertSubTotalOnStorefrontMiniCartActionGroup" stepKey="assertSubTotalOnStorefrontMiniCartAsCustomer"> + <argument name="subTotal" value="$88.96"/> + </actionGroup> + + </test> +</tests> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductWithCustomOptionsTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductWithCustomOptionsTest.xml new file mode 100644 index 0000000000000..5b7e722c92a02 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductWithCustomOptionsTest.xml @@ -0,0 +1,198 @@ +<?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="ApplyCatalogRuleForSimpleProductWithCustomOptionsTest"> + <annotations> + <features value="CatalogRule"/> + <stories value="Apply catalog price rule"/> + <title value="Admin should be able to apply the catalog price rule for simple product with custom options"/> + <description value="Admin should be able to apply the catalog price rule for simple product with custom options"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14769"/> + <group value="CatalogRule"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Create category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + + <!-- Create Simple Product 1 --> + <createData entity="_defaultProduct" stepKey="createProduct1"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">56.78</field> + </createData> + + <!-- Create Simple Product 2 --> + <createData entity="_defaultProduct" stepKey="createProduct2"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">56.78</field> + </createData> + + <!-- Create Simple Product 3 --> + <createData entity="_defaultProduct" stepKey="createProduct3"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">56.78</field> + </createData> + + <!-- Update all products to have custom options --> + <updateData createDataKey="createProduct1" entity="productWithCustomOptions" stepKey="updateProductWithOptions1"/> + <updateData createDataKey="createProduct2" entity="productWithCustomOptions" stepKey="updateProductWithOptions2"/> + <updateData createDataKey="createProduct3" entity="productWithCustomOptions" stepKey="updateProductWithOptions3"/> + </before> + <after> + <!-- Delete products and category --> + <deleteData createDataKey="createProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="createProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="createProduct3" stepKey="deleteProduct3"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete the catalog price rule --> + <amOnPage stepKey="goToPriceRulePage" url="{{CatalogRulePage.url}}"/> + <actionGroup stepKey="deletePriceRule" ref="deleteEntitySecondaryGrid"> + <argument name="name" value="{{_defaultCatalogRule.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + + <!-- Logout --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- 1. Begin creating a new catalog price rule --> + <actionGroup ref="newCatalogPriceRuleByUIWithConditionIsCategory" stepKey="newCatalogPriceRuleByUIWithConditionIsCategory"> + <argument name ="categoryId" value="$createCategory.id$"/> + </actionGroup> + + <!-- Select not logged in customer group --> + <actionGroup ref="selectNotLoggedInCustomerGroupActionGroup" stepKey="selectNotLoggedInCustomerGroup"/> + + <!-- Save and apply the new catalog price rule --> + <actionGroup ref="SaveAndApplyCatalogPriceRuleActionGroup" stepKey="saveAndApplyCatalogPriceRule"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + + <!-- Navigate to category on store front --> + <amOnPage url="{{StorefrontProductPage.url($createCategory.name$)}}" stepKey="goToCategoryPage"/> + + <!-- Check product 1 name on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1Name"> + <argument name="productInfo" value="$createProduct1.name$"/> + <argument name="productNumber" value="3"/> + </actionGroup> + + <!-- Check product 1 price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1Price"> + <argument name="productInfo" value="$51.10"/> + <argument name="productNumber" value="3"/> + </actionGroup> + + <!-- Check product 1 regular price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1RegularPrice"> + <argument name="productInfo" value="$56.78"/> + <argument name="productNumber" value="3"/> + </actionGroup> + + <!-- Check product 2 name on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct2Name"> + <argument name="productInfo" value="$createProduct2.name$"/> + <argument name="productNumber" value="2"/> + </actionGroup> + + <!-- Check product 2 price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct2Price"> + <argument name="productInfo" value="$51.10"/> + <argument name="productNumber" value="2"/> + </actionGroup> + + <!-- Check product 2 price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct2RegularPrice"> + <argument name="productInfo" value="$56.78"/> + <argument name="productNumber" value="2"/> + </actionGroup> + + <!-- Check product 3 name on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct3Name"> + <argument name="productInfo" value="$createProduct3.name$"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Check product 3 price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct3Price"> + <argument name="productInfo" value="$51.10"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Check product 3 regular price on store front category page --> + <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct3RegularPrice"> + <argument name="productInfo" value="$56.78"/> + <argument name="productNumber" value="1"/> + </actionGroup> + + <!-- Navigate to product 1 on store front --> + <amOnPage url="{{StorefrontProductPage.url($createProduct1.name$)}}" stepKey="goToProductPage1"/> + + <!-- Assert regular and special price after selecting ProductOptionValueDropdown1 --> + <actionGroup ref="StorefrontSelectCustomOptionDropDownAndAssertPricesActionGroup" stepKey="storefrontSelectCustomOptionAndAssertPrices1"> + <argument name="customOption" value="{{ProductOptionValueDropdown1.title}} +$0.01"/> + <argument name="productPrice" value="$56.79"/> + <argument name="productFinalPrice" value="$51.11"/> + </actionGroup> + + <!-- Add product 1 to cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="cartAddSimpleProduct1ToCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Navigate to product 2 on store front --> + <amOnPage url="{{StorefrontProductPage.url($createProduct1.name$)}}" stepKey="goToProductPage2"/> + + <!-- Assert regular and special price after selecting ProductOptionValueDropdown3 --> + <actionGroup ref="StorefrontSelectCustomOptionDropDownAndAssertPricesActionGroup" stepKey="storefrontSelectCustomOptionAndAssertPrices2"> + <argument name="customOption" value="{{ProductOptionValueDropdown3.title}} +$5.11"/> + <argument name="productPrice" value="$62.46"/> + <argument name="productFinalPrice" value="$56.21"/> + </actionGroup> + + <!-- Add product 2 to cart --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="cartAddSimpleProduct2ToCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Navigate to product 3 on store front --> + <amOnPage url="{{StorefrontProductPage.url($createProduct3.name$)}}" stepKey="goToProductPage3"/> + + <!-- Add product 3 to cart with no custom option --> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="cartAddSimpleProduct3ToCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <!-- Assert sub total on mini shopping cart --> + <actionGroup ref="AssertSubTotalOnStorefrontMiniCartActionGroup" stepKey="assertSubTotalOnStorefrontMiniCart"> + <argument name="subTotal" value="$158.42"/> + </actionGroup> + + <!-- Navigate to checkout shipping page --> + <amOnPage stepKey="navigateToShippingPage" url="{{CheckoutShippingPage.url}}"/> + <waitForPageLoad stepKey="waitFoCheckoutShippingPageLoad"/> + + <!-- Fill Shipping information --> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="fillOrderShippingInfo"> + <argument name="customerVar" value="Simple_US_Customer"/> + <argument name="customerAddressVar" value="US_Address_TX"/> + </actionGroup> + + <!-- Verify order summary on payment page --> + <actionGroup ref="VerifyCheckoutPaymentOrderSummaryActionGroup" stepKey="verifyCheckoutPaymentOrderSummaryActionGroup"> + <argument name="orderSummarySubTotal" value="$158.42"/> + <argument name="orderSummaryShippingTotal" value="$15.00"/> + <argument name="orderSummaryTotal" value="$173.42"/> + </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/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml b/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml index fb34a3ac4bb3b..8174434a06c49 100644 --- a/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml +++ b/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml @@ -136,7 +136,7 @@ </validation> <dataType>number</dataType> <tooltip> - <link>http://docs.magento.com/m2/ce/user_guide/configuration/scope.html</link> + <link>https://docs.magento.com/m2/ce/user_guide/configuration/scope.html</link> <description>What is this?</description> </tooltip> <label translate="true">Websites</label> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup.xml new file mode 100644 index 0000000000000..4345d575a3320 --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup.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="StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup"> + <arguments> + <argument name="option" type="string"/> + <argument name="expectedPrice" type="string"/> + </arguments> + <selectOption userInput="{{option}}" selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="selectOption"/> + <see userInput="{{expectedPrice}}" selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="seeProductPrice"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup.xml new file mode 100644 index 0000000000000..56480583af7b4 --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/ActionGroup/StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup.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="StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup"> + <arguments> + <argument name="productName" type="string"/> + <argument name="expectedPrice" type="string"/> + </arguments> + <seeInTitle userInput="{{productName}}" stepKey="assertProductNameTitle"/> + <see userInput="{{productName}}" selector="{{StorefrontProductInfoMainSection.productName}}" stepKey="assertProductName"/> + <see userInput="{{expectedPrice}}" selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="assertProductPrice"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProductsTest.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProductsTest.xml new file mode 100644 index 0000000000000..5bb45f58bcd27 --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProductsTest.xml @@ -0,0 +1,265 @@ +<?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="AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProductsTest"> + <annotations> + <features value="CatalogRuleConfigurable"/> + <stories value="Apply catalog price rule"/> + <title value="Apply catalog rule for configurable product with assigned simple products"/> + <description value="Admin should be able to apply catalog rule for configurable product with assigned simple products"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14063"/> + <group value="catalogRuleConfigurable"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create category for first configurable product --> + <createData entity="SimpleSubCategory" stepKey="firstSimpleCategory"/> + + <!-- Create first configurable product with two options --> + <createData entity="ApiConfigurableProduct" stepKey="createFirstConfigProduct"> + <requiredEntity createDataKey="firstSimpleCategory"/> + </createData> + + <createData entity="productAttributeWithTwoOptions" stepKey="createFirstConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createFirstConfigProductAttributeFirstOption"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createFirstConfigProductAttributeSecondOption"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + </createData> + + <createData entity="AddToDefaultSet" stepKey="addFirstProductToAttributeSet"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + </createData> + + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getFirstConfigAttributeFirstOption"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getFirstConfigAttributeSecondOption"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + </getData> + + <!-- Create two child products for first configurable product --> + <createData entity="ApiSimpleOne" stepKey="createFirstConfigFirstChildProduct"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + <requiredEntity createDataKey="getFirstConfigAttributeFirstOption"/> + </createData> + + <createData entity="ApiSimpleOne" stepKey="createFirstConfigSecondChildProduct"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + <requiredEntity createDataKey="getFirstConfigAttributeSecondOption"/> + </createData> + + <createData entity="ConfigurableProductTwoOptions" stepKey="createFirstConfigProductOption"> + <requiredEntity createDataKey="createFirstConfigProduct"/> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + <requiredEntity createDataKey="getFirstConfigAttributeFirstOption"/> + <requiredEntity createDataKey="getFirstConfigAttributeSecondOption"/> + </createData> + + <createData entity="ConfigurableProductAddChild" stepKey="createFirstConfigProductAddFirstChild"> + <requiredEntity createDataKey="createFirstConfigProduct"/> + <requiredEntity createDataKey="createFirstConfigFirstChildProduct"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createFirstConfigProductAddSecondChild"> + <requiredEntity createDataKey="createFirstConfigProduct"/> + <requiredEntity createDataKey="createFirstConfigSecondChildProduct"/> + </createData> + + <!-- Add customizable options to first product --> + <updateData createDataKey="createFirstConfigProduct" entity="productWithOptionRadiobutton" stepKey="updateFirstProductWithOption"/> + + <!-- Create category for second configurable product --> + <createData entity="SimpleSubCategory" stepKey="secondSimpleCategory"/> + + <!-- Create second configurable product with two options --> + <createData entity="ApiConfigurableProduct" stepKey="createSecondConfigProduct"> + <requiredEntity createDataKey="secondSimpleCategory"/> + </createData> + + <createData entity="productAttributeWithTwoOptions" stepKey="createSecondConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createSecondConfigProductAttributeFirstOption"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createSecondConfigProductAttributeSecondOption"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + </createData> + + <createData entity="AddToDefaultSet" stepKey="addSecondProductToAttributeSet"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + </createData> + + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getSecondConfigAttributeFirstOption"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getSecondConfigAttributeSecondOption"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + </getData> + + <!-- Create two child products for second configurable product --> + <createData entity="ApiSimpleOne" stepKey="createSecondConfigFirstChildProduct"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + <requiredEntity createDataKey="getSecondConfigAttributeFirstOption"/> + </createData> + + <createData entity="ApiSimpleOne" stepKey="createSecondConfigSecondChildProduct"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + <requiredEntity createDataKey="getSecondConfigAttributeSecondOption"/> + </createData> + + <createData entity="ConfigurableProductTwoOptions" stepKey="createSecondConfigProductOption"> + <requiredEntity createDataKey="createSecondConfigProduct"/> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + <requiredEntity createDataKey="getSecondConfigAttributeFirstOption"/> + <requiredEntity createDataKey="getSecondConfigAttributeSecondOption"/> + </createData> + + <createData entity="ConfigurableProductAddChild" stepKey="createSecondConfigProductAddFirstChild"> + <requiredEntity createDataKey="createSecondConfigProduct"/> + <requiredEntity createDataKey="createSecondConfigFirstChildProduct"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createSecondConfigProductAddSecondChild"> + <requiredEntity createDataKey="createSecondConfigProduct"/> + <requiredEntity createDataKey="createSecondConfigSecondChildProduct"/> + </createData> + + <!-- Add customizable options to second product --> + <updateData createDataKey="createSecondConfigProduct" entity="productWithOptionRadiobutton" stepKey="updateSecondProductWithOption"/> + + <!--Create customer group --> + <createData entity="CustomCustomerGroup" stepKey="customerGroup"/> + + <!-- Create Customer --> + <createData entity="SimpleUsCustomerWithNewCustomerGroup" stepKey="createCustomer"> + <requiredEntity createDataKey="customerGroup" /> + </createData> + + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + </before> + <after> + <!-- Customer log out --> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> + + <!-- Delete created data --> + <deleteData createDataKey="createFirstConfigProduct" stepKey="deleteFirstConfigProduct"/> + <deleteData createDataKey="createFirstConfigFirstChildProduct" stepKey="deleteFirstConfigFirstChildProduct"/> + <deleteData createDataKey="createFirstConfigSecondChildProduct" stepKey="deleteFirstConfigSecondChildProduct"/> + <deleteData createDataKey="createFirstConfigProductAttribute" stepKey="deleteFirstConfigProductAttribute"/> + <deleteData createDataKey="firstSimpleCategory" stepKey="deleteFirstSimpleCategory"/> + + <deleteData createDataKey="createSecondConfigProduct" stepKey="deleteSecondConfigProduct"/> + <deleteData createDataKey="createSecondConfigFirstChildProduct" stepKey="deleteSecondConfigFirstChildProduct"/> + <deleteData createDataKey="createSecondConfigSecondChildProduct" stepKey="deleteSecondConfigSecondChildProduct"/> + <deleteData createDataKey="createSecondConfigProductAttribute" stepKey="deleteSecondConfigProductAttribute"/> + <deleteData createDataKey="secondSimpleCategory" stepKey="deleteSimpleCategory"/> + + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="customerGroup" stepKey="deleteCustomerGroup"/> + + <!-- Delete created price rules --> + <amOnPage url="{{CatalogRulePage.url}}" stepKey="goToPriceRulePage"/> + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deletePriceRuleForFirstOption"> + <argument name="name" value="{{CatalogRuleToFixed.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + + <!-- Admin log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Create price rule --> + <actionGroup ref="newCatalogPriceRuleByUI" stepKey="createPriceRule"> + <argument name="catalogRule" value="CatalogRuleToFixed"/> + </actionGroup> + <actionGroup ref="CatalogSelectCustomerGroupActionGroup" stepKey="addCustomerGroup"> + <argument name="customerGroupName" value="$$customerGroup.code$$"/> + </actionGroup> + + <!-- Save price rule --> + <click selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="clickSaveAndApplyRule"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule." stepKey="assertSuccessMessage"/> + + <!-- Run full reindex and clear caches --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + + <!-- Login to storefront from customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="logInFromCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <!-- Assert first product in category --> + <amOnPage url="$$firstSimpleCategory.name$$.html" stepKey="goToFirstCategoryPageStorefront"/> + <waitForPageLoad stepKey="waitForFirstCategoryPageLoad"/> + <actionGroup ref="StorefrontCheckCategoryConfigurableProductWithUpdatedPriceActionGroup" stepKey="checkFirstProductPriceInCategory"> + <argument name="productName" value="$$createFirstConfigProduct.name$$"/> + <argument name="expectedPrice" value="{{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + + <!-- Assert second product in category --> + <amOnPage url="$$secondSimpleCategory.name$$.html" stepKey="goToSecondCategoryPageStorefront"/> + <waitForPageLoad stepKey="waitForSecondCategoryPageLoad"/> + <actionGroup ref="StorefrontCheckCategoryConfigurableProductWithUpdatedPriceActionGroup" stepKey="checkSecondProductPriceInCategory"> + <argument name="productName" value="$$createSecondConfigProduct.name$$"/> + <argument name="expectedPrice" value="{{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + + <!-- Assert first product in storefront product page --> + <amOnPage url="$$createFirstConfigProduct.custom_attributes[url_key]$$.html" stepKey="amOnFirstProductPage"/> + <waitForPageLoad stepKey="waitForFirstProductPageLoad"/> + <actionGroup ref="StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup" stepKey="checkFirstProductPriceInStorefrontProductPage"> + <argument name="productName" value="$$createFirstConfigProduct.name$$"/> + <argument name="expectedPrice" value="{{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + + <!-- Add first product with selected options to the cart --> + <actionGroup ref="StorefrontAddProductWithSelectedConfigurableAndCustomOptionsToCartActionGroup" stepKey="addFirstProductWithSelectedOptionToCart1"> + <argument name="product" value="$$createFirstConfigProduct$$"/> + <argument name="option" value="$$createFirstConfigProductAttributeFirstOption.option[store_labels][1][label]$$"/> + <argument name="customizableOption" value="{{ProductOptionValueRadioButtons1.title}}"/> + </actionGroup> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.updatedPrice}}" stepKey="grabForthProductUpdatedPrice1"/> + + <actionGroup ref="StorefrontAddProductWithSelectedConfigurableAndCustomOptionsToCartActionGroup" stepKey="addFirstProductWithSelectedOptionToCart2"> + <argument name="product" value="$$createFirstConfigProduct$$"/> + <argument name="option" value="$$createFirstConfigProductAttributeSecondOption.option[store_labels][1][label]$$"/> + <argument name="customizableOption" value="{{ProductOptionValueRadioButtons3.title}}"/> + </actionGroup> + + <!-- Assert second product in storefront product page --> + <amOnPage url="$$createSecondConfigProduct.custom_attributes[url_key]$$.html" stepKey="amOnSecondProductPage"/> + <waitForPageLoad stepKey="waitForSecondProductPageLoad"/> + <actionGroup ref="StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup" stepKey="checkSecondProductPriceInStorefrontProductPage"> + <argument name="productName" value="$$createSecondConfigProduct.name$$"/> + <argument name="expectedPrice" value="{{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + + <!-- Add second product with selected options to the cart --> + <actionGroup ref="StorefrontAddProductWithSelectedConfigurableAndCustomOptionsToCartActionGroup" stepKey="addSecondProductWithSelectedOptionToCart1"> + <argument name="product" value="$$createSecondConfigProduct$$"/> + <argument name="option" value="$$createSecondConfigProductAttributeFirstOption.option[store_labels][1][label]$$"/> + <argument name="customizableOption" value="{{ProductOptionValueRadioButtons1.title}}"/> + </actionGroup> + + <actionGroup ref="StorefrontAddProductWithSelectedConfigurableAndCustomOptionsToCartActionGroup" stepKey="addSecondProductWithSelectedOptionToCart2"> + <argument name="product" value="$$createSecondConfigProduct$$"/> + <argument name="option" value="$$createSecondConfigProductAttributeSecondOption.option[store_labels][1][label]$$"/> + <argument name="customizableOption" value="{{ProductOptionValueRadioButtons3.title}}"/> + </actionGroup> + + <!--Assert products prices in the cart --> + <amOnPage url="/checkout/cart/" stepKey="amOnShoppingCartPage"/> + <waitForPageLoad stepKey="waitForShoppingCartPageLoad"/> + <see userInput="$210.69" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createFirstConfigProductAttributeFirstOption.option[store_labels][1][label]$$)}}" stepKey="assertFirstProductPriceForFirstProductOption"/> + <see userInput="$120.70" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createFirstConfigProductAttributeSecondOption.option[store_labels][1][label]$$)}}" stepKey="assertFirstProductPriceForSecondProductOption"/> + <see userInput="$210.69" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createSecondConfigProductAttributeFirstOption.option[store_labels][1][label]$$)}}" stepKey="assertSecondProductPriceForFirstProductOption"/> + <see userInput="$120.70" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createSecondConfigProductAttributeSecondOption.option[store_labels][1][label]$$)}}" stepKey="assertSecondProductPriceForSecondProductOption"/> + </test> +</tests> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptionsTest.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptionsTest.xml new file mode 100644 index 0000000000000..408e181aa1c87 --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptionsTest.xml @@ -0,0 +1,218 @@ +<?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="AdminApplyCatalogRuleForConfigurableProductWithOptionsTest"> + <annotations> + <features value="CatalogRuleConfigurable"/> + <stories value="Apply catalog price rule"/> + <title value="Apply catalog price rule for configurable product with options"/> + <description value="Admin should be able to apply the catalog rule for configurable product with options"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14062"/> + <group value="catalogRuleConfigurable"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="SimpleSubCategory" stepKey="simpleCategory"/> + + <!-- Create configurable product with three options --> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="simpleCategory"/> + </createData> + + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeFirstOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeSecondOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption3" stepKey="createConfigProductAttributeThirdOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeFirstOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeSecondOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="3" stepKey="getConfigAttributeThirdOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Create three child products --> + <createData entity="ApiSimpleOne" stepKey="createConfigFirstChildProduct"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeFirstOption"/> + </createData> + + <createData entity="ApiSimpleOne" stepKey="createConfigSecondChildProduct"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeSecondOption"/> + </createData> + + <createData entity="ApiSimpleOne" stepKey="createConfigThirdChildProduct"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeThirdOption"/> + </createData> + + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeFirstOption"/> + <requiredEntity createDataKey="getConfigAttributeSecondOption"/> + <requiredEntity createDataKey="getConfigAttributeThirdOption"/> + </createData> + + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddFirstChild"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigFirstChildProduct"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddSecondChild"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigSecondChildProduct"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddThirdChild"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigThirdChildProduct"/> + </createData> + + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + </before> + <after> + <!-- Delete created data --> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigFirstChildProduct" stepKey="deleteFirstSimpleProduct"/> + <deleteData createDataKey="createConfigSecondChildProduct" stepKey="deleteSecondSimpleProduct"/> + <deleteData createDataKey="createConfigThirdChildProduct" stepKey="deleteThirdSimpleProduct"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + <deleteData createDataKey="simpleCategory" stepKey="deleteCategory"/> + + <!-- Delete created price rules --> + <amOnPage url="{{CatalogRulePage.url}}" stepKey="goToPriceRulePage"/> + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deletePriceRuleForFirstOption"> + <argument name="name" value="{{CatalogRuleToFixed.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deletePriceRuleForSecondOption"> + <argument name="name" value="{{_defaultCatalogRule.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deletePriceRuleForThirdOption"> + <argument name="name" value="{{CatalogRuleWithoutDiscount.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + + <!-- Log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create price rule for first configurable product option --> + <actionGroup ref="newCatalogPriceRuleByUI" stepKey="createFirstPriceRule"> + <argument name="catalogRule" value="CatalogRuleToFixed"/> + </actionGroup> + <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="selectNotLoggedInCustomerGroupForFirstPriceRule"/> + <actionGroup ref="CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup" stepKey="createFirstCatalogPriceRuleCondition"> + <argument name="attributeName" value="$$createConfigProductAttribute.attribute[frontend_labels][0][label]$$"/> + <argument name="targetSelectValue" value="$$createConfigProductAttributeFirstOption.option[store_labels][1][label]$$"/> + <argument name="indexA" value="1"/> + <argument name="indexB" value="1"/> + </actionGroup> + <click selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="saveAndApplyFirstPriceRule"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule." stepKey="assertSuccessMessageForFirstPriceRule"/> + + <!-- Create price rule for second configurable product option --> + <actionGroup ref="newCatalogPriceRuleByUI" stepKey="createSecondPriceRule"> + <argument name="catalogRule" value="_defaultCatalogRule"/> + </actionGroup> + <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="selectNotLoggedInCustomerGroupForSecondPriceRule"/> + <actionGroup ref="CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup" stepKey="createSecondCatalogPriceRuleCondition"> + <argument name="attributeName" value="$$createConfigProductAttribute.attribute[frontend_labels][0][label]$$"/> + <argument name="targetSelectValue" value="$$createConfigProductAttributeSecondOption.option[store_labels][1][label]$$"/> + <argument name="indexA" value="1"/> + <argument name="indexB" value="1"/> + </actionGroup> + <click selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="saveAndApplySecondPriceRule"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule." stepKey="assertSuccessMessageForSecondPriceRule"/> + + <!-- Create price rule for third configurable product option --> + <actionGroup ref="newCatalogPriceRuleByUI" stepKey="createThirdPriceRule"> + <argument name="catalogRule" value="CatalogRuleWithoutDiscount"/> + </actionGroup> + <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="selectNotLoggedInCustomerGroupForThirdPriceRule"/> + <actionGroup ref="CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup" stepKey="createThirdCatalogPriceRuleCondition"> + <argument name="attributeName" value="$$createConfigProductAttribute.attribute[frontend_labels][0][label]$$"/> + <argument name="targetSelectValue" value="$$createConfigProductAttributeThirdOption.option[store_labels][1][label]$$"/> + <argument name="indexA" value="1"/> + <argument name="indexB" value="1"/> + </actionGroup> + <click selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="saveAndApplyThirdPriceRule"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule." stepKey="assertSuccessMessageForThirdPriceRule"/> + + <!-- Run full reindex and clear caches --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + + <!-- Assert product in storefront product page --> + <amOnPage url="$$createConfigProduct.custom_attributes[url_key]$$.html" stepKey="amOnProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <actionGroup ref="StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup" stepKey="assertUpdatedProductPriceInStorefrontProductPage"> + <argument name="productName" value="$$createConfigProduct.name$$"/> + <argument name="expectedPrice" value="{{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + + <!-- Assert product options price in storefront product page --> + <actionGroup ref="StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup" stepKey="assertCatalogPriceRuleAppliedToFirstProductOption"> + <argument name="option" value="$$createConfigProductAttributeFirstOption.option[store_labels][1][label]$$"/> + <argument name="expectedPrice" value="{{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + + <actionGroup ref="StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup" stepKey="assertCatalogPriceRuleAppliedToSecondProductOption"> + <argument name="option" value="$$createConfigProductAttributeSecondOption.option[store_labels][1][label]$$"/> + <argument name="expectedPrice" value="$110.70"/> + </actionGroup> + + <actionGroup ref="StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup" stepKey="assertCatalogPriceRuleAppliedToThirdProductOption"> + <argument name="option" value="$$createConfigProductAttributeThirdOption.option[store_labels][1][label]$$"/> + <argument name="expectedPrice" value="{{ApiConfigurableProduct.price}}"/> + </actionGroup> + + <!-- Add product with selected option to the cart --> + <actionGroup ref="StorefrontAddProductWithSelectedConfigurableOptionToCartActionGroup" stepKey="addProductWithSelectedFirstOptionToCart"> + <argument name="product" value="$$createConfigProduct$$"/> + <argument name="option" value="$$createConfigProductAttributeFirstOption.option[store_labels][1][label]$$"/> + </actionGroup> + + <actionGroup ref="StorefrontAddProductWithSelectedConfigurableOptionToCartActionGroup" stepKey="addProductWithSelectedSecondOptionToCart"> + <argument name="product" value="$$createConfigProduct$$"/> + <argument name="option" value="$$createConfigProductAttributeSecondOption.option[store_labels][1][label]$$"/> + </actionGroup> + + <actionGroup ref="StorefrontAddProductWithSelectedConfigurableOptionToCartActionGroup" stepKey="addProductWithSelectedThirdOptionToCart"> + <argument name="product" value="$$createConfigProduct$$"/> + <argument name="option" value="$$createConfigProductAttributeThirdOption.option[store_labels][1][label]$$"/> + </actionGroup> + + <!--Assert product price in the cart --> + <amOnPage url="/checkout/cart/" stepKey="amOnShoppingCartPage"/> + <waitForPageLoad stepKey="waitForShoppingCartPageLoad"/> + <see userInput="{{CatalogRuleToFixed.discount_amount}}" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createConfigProductAttributeFirstOption.option[store_labels][1][label]$$)}}" stepKey="assertProductPriceForFirstProductOption"/> + <see userInput="$110.70" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createConfigProductAttributeSecondOption.option[store_labels][1][label]$$)}}" stepKey="assertProductPriceForSecondProductOption"/> + <see userInput="{{ApiConfigurableProduct.price}}" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createConfigProductAttributeThirdOption.option[store_labels][1][label]$$)}}" stepKey="assertProductPriceForThirdProductOption"/> + </test> +</tests> 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/Section/AdminCatalogSearchTermIndexSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml index ac316d060f6e9..aa0145b9f96cd 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml @@ -19,5 +19,7 @@ <element name="searchTermRowCheckboxBySearchQuery" type="checkbox" selector="//*[normalize-space()='{{var1}}']/preceding-sibling::td//input[@name='search']" parameterized="true" timeout="30"/> <element name="okButton" type="button" selector="//button[@class='action-primary action-accept']/span" timeout="30"/> <element name="emptyRecords" type="text" selector="//tr[@class='data-grid-tr-no-data even']/td[@class='empty-text']"/> + <element name="gridRow" type="text" selector="//tr[@data-role='row']"/> + <element name="numberOfSearchTermResults" type="text" selector="//tr[@data-role='row']/td[@data-column='num_results']"/> </section> </sections> \ No newline at end of file diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermNewSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermNewSection.xml index a7d577a7508c0..a07e2a9b8a547 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermNewSection.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermNewSection.xml @@ -9,8 +9,10 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCatalogSearchTermNewSection"> - <element name="searchQuery" type="text" selector="//div[@class='admin__field-control control']/input[@id='query_text']"/> - <element name="store" type="text" selector="//select[@id='store_id']"/> + <element name="searchQuery" type="text" selector="#query_text"/> + <element name="store" type="text" selector="#store_id"/> + <element name="numberOfResults" type="button" selector="#num_results"/> + <element name="numberOfUses" type="button" selector="#popularity"/> <element name="redirectUrl" type="text" selector="//div[@class='admin__field-control control']/input[@id='redirect']"/> <element name="displayInSuggestedTerm" type="select" selector="//select[@name='display_in_terms']"/> <element name="saveSearchButton" type="button" selector="//button[@id='save']/span[@class='ui-button-text']" timeout="30"/> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/MinimalQueryLengthForCatalogSearchTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/MinimalQueryLengthForCatalogSearchTest.xml index 2fea5c988bf9d..b6417e12a6db7 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/MinimalQueryLengthForCatalogSearchTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/MinimalQueryLengthForCatalogSearchTest.xml @@ -11,6 +11,7 @@ <test name="MinimalQueryLengthForCatalogSearchTest"> <annotations> <features value="CatalogSearch"/> + <stories value="Catalog search"/> <title value="Minimal query length for catalog search"/> <description value="Minimal query length for catalog search"/> <severity value="AVERAGE"/> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontUpdateSearchTermEntityTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontUpdateSearchTermEntityTest.xml new file mode 100644 index 0000000000000..3ebb09f3c9c26 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontUpdateSearchTermEntityTest.xml @@ -0,0 +1,72 @@ +<?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="StorefrontUpdateSearchTermEntityTest"> + <annotations> + <stories value="Storefront Search"/> + <title value="Update Storefront Search Results"/> + <description value="You should see the updated Search Term on the Storefront via the Admin."/> + <testCaseId value="MC-13987"/> + <severity value="CRITICAL"/> + <group value="search"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="_defaultCategory" stepKey="createCategory1"/> + <createData entity="SimpleProduct" stepKey="createProduct1"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnStorefrontPage1"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logoutOfAdmin1"/> + + <deleteData createDataKey="createProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="createCategory1" stepKey="deleteCategory1"/> + </after> + + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByProductName1"> + <argument name="phrase" value="$$createProduct1.name$$"/> + </actionGroup> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> + + <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage1"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + + <actionGroup ref="searchTermFilterBySearchQuery" stepKey="filterByFirstSearchQuery1"> + <argument name="searchQuery" value="$$createProduct1.name$$"/> + </actionGroup> + + <click selector="{{AdminGridRow.editByValue($$createProduct1.name$$)}}" stepKey="clickOnSearchResult1"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + + <actionGroup ref="AdminFillAllSearchTermFieldsActionGroup" stepKey="searchForSearchTerm1"> + <argument name="searchTerm" value="UpdatedSearchTermData1"/> + </actionGroup> + + <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage2"/> + <waitForPageLoad stepKey="waitForPageLoad3"/> + + <actionGroup ref="searchTermFilterBySearchQuery" stepKey="filterByFirstSearchQuery2"> + <argument name="searchQuery" value="{{UpdatedSearchTermData1.query_text}}"/> + </actionGroup> + + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnStorefrontPage2"/> + <waitForPageLoad stepKey="waitForPageLoad4"/> + + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByProductName2"> + <argument name="phrase" value="{{UpdatedSearchTermData1.query_text}}"/> + </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/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml index 30a4290d882fb..5423267676507 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml @@ -10,6 +10,7 @@ <test name="AdminUrlForProductRewrittenCorrectlyTest"> <annotations> <features value="CatalogUrlRewrite"/> + <stories value="Url rewrites"/> <title value="Check that URL for product rewritten correctly"/> <description value="Check that URL for product rewritten correctly"/> <severity value="MAJOR"/> 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/AssertStorefronElementVisibleActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefronElementVisibleActionGroup.xml new file mode 100644 index 0000000000000..b99c4b7f5bf16 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefronElementVisibleActionGroup.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="AssertStorefrontElementVisibleActionGroup"> + <arguments> + <argument name="selector" type="string"/> + <argument name="userInput" type="string"/> + </arguments> + <see selector="{{selector}}" userInput="{{userInput}}" stepKey="assertElement"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontCheckoutCartItemsActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontCheckoutCartItemsActionGroup.xml new file mode 100644 index 0000000000000..77a8bef9baf1c --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontCheckoutCartItemsActionGroup.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="AssertStorefrontCheckoutCartItemsActionGroup"> + <arguments> + <argument name="productName" type="string"/> + <argument name="productSku" type="string"/> + <argument name="productPrice" type="string"/> + <argument name="subtotal" type="string"/> + <argument name="qty" type="string"/> + </arguments> + <see selector="{{CheckoutCartProductSection.productName}}" userInput="{{productName}}" stepKey="seeProductNameInCheckoutSummary"/> + <see selector="{{CheckoutCartProductSection.checkoutCartProductPrice}}" userInput="{{productPrice}}" stepKey="seeProductPriceInCart"/> + <see selector="{{CheckoutCartProductSection.checkoutCartSubtotal}}" userInput="{{subtotal}}" stepKey="seeSubtotalPrice"/> + <seeInField selector="{{CheckoutCartProductSection.qtyByContains(productSku)}}" userInput="{{qty}}" stepKey="seeProductQuantity"/> + </actionGroup> +</actionGroups> 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/AssertStorefrontMiniCartItemsActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontMiniCartItemsActionGroup.xml new file mode 100644 index 0000000000000..258d8ed9021c0 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontMiniCartItemsActionGroup.xml @@ -0,0 +1,24 @@ +<?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="AssertStorefrontMiniCartItemsActionGroup"> + <arguments> + <argument name="productName" type="string"/> + <argument name="productPrice" type="string"/> + <argument name="cartSubtotal" type="string"/> + <argument name="qty" type="string"/> + </arguments> + <see selector="{{StorefrontMinicartSection.miniCartItemsText}}" userInput="{{productName}}" stepKey="seeProductNameInMiniCart"/> + <see selector="{{StorefrontMinicartSection.miniCartItemsText}}" userInput="{{productPrice}}" stepKey="seeProductPriceInMiniCart"/> + <seeElement selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="seeCheckOutButtonInMiniCart"/> + <seeElement selector="{{StorefrontMinicartSection.productQuantity(productName, qty)}}" stepKey="seeProductQuantity1"/> + <seeElement selector="{{StorefrontMinicartSection.productImage}}" stepKey="seeProductImage"/> + <see selector="{{StorefrontMinicartSection.productSubTotal}}" userInput="{{cartSubtotal}}" stepKey="seeSubTotal"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontSeeElementActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontSeeElementActionGroup.xml new file mode 100644 index 0000000000000..02f00a8cd31a9 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontSeeElementActionGroup.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="AssertStorefrontSeeElementActionGroup"> + <arguments> + <argument name="selector" type="string"/> + </arguments> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <scrollTo selector="{{selector}}" stepKey="scrollToElement"/> + <seeElement selector="{{selector}}" stepKey="assertElement"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryItemsActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryItemsActionGroup.xml new file mode 100644 index 0000000000000..54fde1be70bce --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryItemsActionGroup.xml @@ -0,0 +1,22 @@ +<?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="AssertStorefrontShoppingCartSummaryItemsActionGroup"> + <arguments> + <argument name="subtotal" type="string"/> + <argument name="total" type="string"/> + </arguments> + <seeInCurrentUrl url="{{CheckoutCartPage.url}}" stepKey="assertUrl"/> + <waitForPageLoad stepKey="waitForCartPage"/> + <see userInput="{{subtotal}}" selector="{{CheckoutCartSummarySection.subtotal}}" stepKey="assertSubtotal"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <see userInput="{{total}}" selector="{{CheckoutCartSummarySection.total}}" stepKey="assertTotal"/> + <seeElement selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="seeProceedToCheckoutButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryWithShippingActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryWithShippingActionGroup.xml new file mode 100644 index 0000000000000..5eb3de3a1af82 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryWithShippingActionGroup.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="AssertStorefrontShoppingCartSummaryWithShippingActionGroup" extends="AssertStorefrontShoppingCartSummaryItemsActionGroup"> + <arguments> + <argument name="shipping" type="string"/> + </arguments> + <waitForLoadingMaskToDisappear stepKey="waitForMaskToDisappear" after="assertSubtotal"/> + <waitForElementVisible selector="{{CheckoutCartSummarySection.shipping}}" time="60" stepKey="waitForElementToBeVisible" after="waitForMaskToDisappear"/> + <!-- Shipping can take a long time to change in builds, can't rely on an explicit wait--> + <wait time="30" stepKey="waitForShippingDetailsToLoad" after="waitForElementToBeVisible"/> + <see userInput="{{shipping}}" selector="{{CheckoutCartSummarySection.shipping}}" stepKey="assertShipping" after="waitForShippingDetailsToLoad" /> + </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/StorefrontAddBundleProductToTheCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddBundleProductToTheCartActionGroup.xml new file mode 100644 index 0000000000000..2c64eda38a420 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddBundleProductToTheCartActionGroup.xml @@ -0,0 +1,24 @@ +<?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="StorefrontAddBundleProductToTheCartActionGroup"> + <arguments> + <argument name="productName" type="string"/> + <argument name="quantity" type="string"/> + </arguments> + <click selector="{{StorefrontBundleProductActionSection.customizeAndAddToCartButton}}" stepKey="clickOnCustomizeAndAddToCartButton"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <click selector="{{StorefrontBundleProductActionSection.dropdownSelectOption}}" stepKey="clickOnSelectOption"/> + <click selector="{{StorefrontBundleProductActionSection.dropdownProductSelection(productName)}}" stepKey="selectProduct"/> + <clearField selector="{{StorefrontBundleProductActionSection.quantityField}}" stepKey="clearTheQuantityField"/> + <fillField selector="{{StorefrontBundleProductActionSection.quantityField}}" userInput="{{quantity}}" stepKey="fillTheProductQuantity"/> + <click selector="{{StorefrontBundleProductActionSection.addToCartButton}}" stepKey="clickOnAddToButton"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddProductWithSelectedConfigurableAndCustomOptionsToCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddProductWithSelectedConfigurableAndCustomOptionsToCartActionGroup.xml new file mode 100644 index 0000000000000..8e13748d64e0e --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddProductWithSelectedConfigurableAndCustomOptionsToCartActionGroup.xml @@ -0,0 +1,24 @@ +<?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"> + <!-- Add product with selected configurable option and customizable option to cart from the product page --> + <actionGroup name="StorefrontAddProductWithSelectedConfigurableAndCustomOptionsToCartActionGroup"> + <arguments> + <argument name="product" type="entity"/> + <argument name="option" type="string"/> + <argument name="customizableOption" type="string"/> + </arguments> + <selectOption userInput="{{option}}" selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="selectOption"/> + <click selector="{{StorefrontProductInfoMainSection.selectCustomOptionByName(customizableOption)}}" stepKey="selectCustomOption"/> + <click selector="{{StorefrontProductInfoMainSection.AddToCart}}" stepKey="clickAddToCart"/> + <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" stepKey="waitForSuccessMessage"/> + <see selector="{{StorefrontProductPageSection.messagesBlock}}" userInput="You added {{product.name}} to your shopping cart." stepKey="assertSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddProductWithSelectedOptionToCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddProductWithSelectedOptionToCartActionGroup.xml new file mode 100644 index 0000000000000..5d844523d4454 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddProductWithSelectedOptionToCartActionGroup.xml @@ -0,0 +1,22 @@ +<?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"> + <!-- Add product with selected option to Cart from the product page --> + <actionGroup name="StorefrontAddProductWithSelectedConfigurableOptionToCartActionGroup"> + <arguments> + <argument name="product" type="entity"/> + <argument name="option" type="string"/> + </arguments> + <selectOption userInput="{{option}}" selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="selectOption"/> + <click selector="{{StorefrontProductInfoMainSection.AddToCart}}" stepKey="clickAddToCart"/> + <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" stepKey="waitForSuccessMessage"/> + <see selector="{{StorefrontProductPageSection.messagesBlock}}" userInput="You added {{product.name}} to your shopping cart." stepKey="assertSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddToTheCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddToTheCartActionGroup.xml new file mode 100644 index 0000000000000..88e81199e3705 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddToTheCartActionGroup.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="StorefrontAddToTheCartActionGroup"> + <waitForPageLoad stepKey="waitForPageLoad"/> + <scrollTo selector="{{StorefrontProductActionSection.addToCart}}" stepKey="scrollToAddToCartButton"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addToCart"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + </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/StorefrontAssertMiniCartItemCountActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertMiniCartItemCountActionGroup.xml new file mode 100644 index 0000000000000..03caa558e4272 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertMiniCartItemCountActionGroup.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="StorefrontAssertMiniCartItemCountActionGroup"> + <arguments> + <argument name="productCount" type="string"/> + <argument name="productCountText" type="string"/> + </arguments> + <see selector="{{StorefrontMinicartSection.productCount}}" userInput="{{productCount}}" stepKey="seeProductCountInCart"/> + <see selector="{{StorefrontMinicartSection.visibleItemsCountText}}" userInput="{{productCountText}}" stepKey="seeNumberOfItemDisplayInMiniCart"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertProductDetailsInOrderSummaryActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertProductDetailsInOrderSummaryActionGroup.xml new file mode 100644 index 0000000000000..59159ea2d0e7f --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertProductDetailsInOrderSummaryActionGroup.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="StorefrontAssertProductDetailsInOrderSummaryActionGroup"> + <arguments> + <argument name="productName" type="string"/> + <argument name="qty" type="string"/> + <argument name="price" type="string"/> + </arguments> + <see selector="{{CheckoutOrderSummarySection.productItemName}}" userInput="{{productName}}" stepKey="seeProductName"/> + <see selector="{{CheckoutOrderSummarySection.productItemQty}}" userInput="{{qty}}" stepKey="seeProductQty"/> + <see selector="{{CheckoutOrderSummarySection.productItemPrice}}" userInput="{{price}}" stepKey="seeProductPrice"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertShippingAddressPageDisplayActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertShippingAddressPageDisplayActionGroup.xml new file mode 100644 index 0000000000000..cd3d18e2aa09e --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertShippingAddressPageDisplayActionGroup.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="StorefrontAssertShippingAddressPageDisplayActionGroup"> + <seeInCurrentUrl url="checkout/#shipping" stepKey="seeCurrentUrl"/> + <seeElement selector="{{CheckoutShippingSection.shippingTab}}" stepKey="seeShippingTab"/> + <seeElement selector="{{CheckoutShippingGuestInfoSection.shippingBlock}}" stepKey="seeShippingBlock"/> + </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/StorefrontAttachOptionFileActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAttachOptionFileActionGroup.xml new file mode 100644 index 0000000000000..364f05abd3882 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAttachOptionFileActionGroup.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="StorefrontAttachOptionFileActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionFile"/> + <argument name="file" defaultValue="MagentoLogo.file" /> + </arguments> + <attachFile selector="{{StorefrontProductInfoMainSection.addLinkFileUploadFile(optionTitle.title)}}" userInput="{{file}}" stepKey="attachFile"/> + </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/StorefrontCheckCartDiscountAndSummaryActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckCartDiscountAndSummaryActionGroup.xml new file mode 100644 index 0000000000000..6c541db6defcc --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckCartDiscountAndSummaryActionGroup.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"> + <!-- Open the Minicart and check Summary --> + <actionGroup name="StorefrontCheckCartDiscountAndSummaryActionGroup"> + <arguments> + <argument name="total" type="string"/> + <argument name="discount" type="string"/> + </arguments> + <waitForPageLoad stepKey="waitForPageLoad"/> + <waitForLoadingMaskToDisappear stepKey="waitForPrices"/> + <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-${{discount}}" stepKey="assertDiscount"/> + <wait time="10" stepKey="waitForTotalPrice"/> + <see selector="{{CheckoutCartSummarySection.total}}" userInput="${{total}}" stepKey="assertTotal"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckThatCartIsEmptyActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckThatCartIsEmptyActionGroup.xml new file mode 100644 index 0000000000000..e5fbb9065f976 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckThatCartIsEmptyActionGroup.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="StorefrontCheckThatCartIsEmptyActionGroup"> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <see selector="{{StorefrontMinicartSection.messageEmptyCart}}" userInput="You have no items in your shopping cart." stepKey="seeNoItemsInShoppingCart"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckoutAndAssertOrderSummaryDisplayActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckoutAndAssertOrderSummaryDisplayActionGroup.xml new file mode 100644 index 0000000000000..0a29cba11d4a9 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckoutAndAssertOrderSummaryDisplayActionGroup.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="StorefrontCheckoutAndAssertOrderSummaryDisplayActionGroup"> + <arguments> + <argument name="itemsText" type="string"/> + </arguments> + <click selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="clickOnCheckOutButtonInMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <see selector="{{CheckoutShippingGuestInfoSection.itemInCart}}" userInput="{{itemsText}}" stepKey="seeOrderSummaryText"/> + <seeElement selector="{{CheckoutOrderSummarySection.miniCartTab}}" stepKey="clickOnOrderSummaryTab"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckoutCheckOutOfStockProductActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckoutCheckOutOfStockProductActionGroup.xml new file mode 100644 index 0000000000000..92ce4f1f360a6 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckoutCheckOutOfStockProductActionGroup.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="StorefrontCheckoutCheckOutOfStockProductActionGroup"> + <arguments> + <argument name="product" type="entity"/> + </arguments> + <see selector="{{CheckoutCartMessageSection.errorMessage}}" userInput="1 product requires your attention." stepKey="seeErrorMessage"/> + <see selector="{{CheckoutCartProductSection.productName}}" userInput="{{product.name}}" stepKey="seeProductName"/> + <see selector="{{CheckoutCartProductSection.messageErrorItem}}" userInput="Availability: Out of stock" stepKey="seeProductOutOfStock"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckoutCheckProductActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckoutCheckProductActionGroup.xml new file mode 100644 index 0000000000000..a994611869274 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCheckoutCheckProductActionGroup.xml @@ -0,0 +1,21 @@ +<?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="StorefrontCheckoutCheckProductActionGroup"> + <arguments> + <argument name="product" type="entity"/> + <argument name="cartItem" type="entity"/> + </arguments> + + <see selector="{{CheckoutCartProductSection.productName}}" userInput="{{product.name}}" stepKey="seeProductName"/> + <grabValueFrom selector="{{CheckoutCartProductSection.ProductQuantityByName(product.name)}}" stepKey="grabProductQty"/> + <assertEquals expected="{{cartItem.qty}}" actual="$grabProductQty" stepKey="assertQtyShoppingCart"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontClickOnMiniCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontClickOnMiniCartActionGroup.xml new file mode 100644 index 0000000000000..09e991c21645d --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontClickOnMiniCartActionGroup.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="StorefrontClickOnMiniCartActionGroup"> + <scrollToTopOfPage stepKey="scrollToTheTopOfThePage"/> + <waitForElementVisible selector="{{StorefrontMinicartSection.showCart}}" stepKey="waitForElementToBeVisible"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontEnterProductQuantityAndAddToTheCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontEnterProductQuantityAndAddToTheCartActionGroup.xml new file mode 100644 index 0000000000000..c76cb8c0f1f88 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontEnterProductQuantityAndAddToTheCartActionGroup.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="StorefrontEnterProductQuantityAndAddToTheCartActionGroup"> + <arguments> + <argument name="quantity" type="string"/> + </arguments> + <clearField selector="{{StorefrontBundleProductActionSection.quantityField}}" stepKey="clearTheQuantityField"/> + <fillField selector="{{StorefrontBundleProductActionSection.quantityField}}" userInput="{{quantity}}" stepKey="fillTheProductQuantity"/> + <click selector="{{StorefrontBundleProductActionSection.addToCartButton}}" stepKey="clickOnAddToButton"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + </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/StorefrontFillOptionFieldInputActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontFillOptionFieldInputActionGroup.xml new file mode 100644 index 0000000000000..001cdae5a4793 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontFillOptionFieldInputActionGroup.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="StorefrontFillOptionFieldInputActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionField"/> + <argument name="fieldInput" type="string" /> + </arguments> + <fillField selector="{{StorefrontProductInfoMainSection.productOptionFieldInput(optionTitle.title)}}" userInput="{{fieldInput}}" stepKey="fillOptionField"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontFillOptionTextAreaActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontFillOptionTextAreaActionGroup.xml new file mode 100644 index 0000000000000..1e3162bac3bfa --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontFillOptionTextAreaActionGroup.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="StorefrontFillOptionTextAreaActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionArea"/> + <argument name="fieldInput" type="string"/> + </arguments> + <fillField selector="{{StorefrontProductInfoMainSection.productOptionAreaInput(optionTitle.title)}}" userInput="{{fieldInput}}" stepKey="fillOptionTextArea"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontMiniCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontMiniCartActionGroup.xml index e4a388d2c3a58..28e109e2387a1 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontMiniCartActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontMiniCartActionGroup.xml @@ -8,9 +8,11 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="clickViewAndEditCartFromMiniCart"> + <scrollTo selector="{{StorefrontMinicartSection.showCart}}" stepKey="scrollToMiniCart"/> <conditionalClick selector="{{StorefrontMinicartSection.showCart}}" dependentSelector="{{StorefrontMinicartSection.miniCartOpened}}" visible="false" stepKey="openMiniCart"/> <waitForElementVisible selector="{{StorefrontMinicartSection.viewAndEditCart}}" stepKey="waitForViewAndEditCartVisible"/> <click selector="{{StorefrontMinicartSection.viewAndEditCart}}" stepKey="viewAndEditCart"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> <seeInCurrentUrl url="checkout/cart" stepKey="seeInCurrentUrl"/> </actionGroup> <actionGroup name="assertOneProductNameInMiniCart"> 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/ActionGroup/StorefrontOpenMiniCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontOpenMiniCartActionGroup.xml new file mode 100644 index 0000000000000..7afcb070f3f06 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontOpenMiniCartActionGroup.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="StorefrontOpenMiniCartActionGroup"> + <waitForElementVisible selector="{{StorefrontMinicartSection.showCart}}" stepKey="waitForElementToBeVisible"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml index 24ed05583b6fb..fb3f295836161 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml @@ -53,7 +53,6 @@ <seeLink stepKey="assertLinkToShoppingCart" url="{{_ENV.MAGENTO_BASE_URL}}checkout/cart/" userInput="shopping cart" /> <waitForText userInput="{{productCount}}" selector="{{StorefrontMinicartSection.productCount}}" time="30" stepKey="assertProductCount"/> </actionGroup> - <!-- Open the Minicart and check Simple Product --> <actionGroup name="StorefrontOpenMinicartAndCheckSimpleProductActionGroup"> <arguments> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionCheckBoxActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionCheckBoxActionGroup.xml new file mode 100644 index 0000000000000..731ac03ca414f --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionCheckBoxActionGroup.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="StorefrontSelectOptionCheckBoxActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionValueCheckbox"/> + </arguments> + <checkOption selector="{{StorefrontProductInfoMainSection.customOptionCheckBox(optionTitle.title)}}" stepKey="SelectOptionRadioButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDateActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDateActionGroup.xml new file mode 100644 index 0000000000000..37f2afd2a00b0 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDateActionGroup.xml @@ -0,0 +1,21 @@ +<?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="StorefrontSelectOptionDateActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionDate"/> + <argument name="month" type="string"/> + <argument name="day" type="string"/> + <argument name="year" type="string"/> + </arguments> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionMonth(optionTitle.title)}}" userInput="{{month}}" stepKey="selectMonthForOptionDate"/> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionDay(optionTitle.title)}}" userInput="{{day}}" stepKey="selectDayForOptionDate"/> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionYear(optionTitle.title)}}" userInput="{{year}}" stepKey="selectYearForOptionDate"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDateTimeActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDateTimeActionGroup.xml new file mode 100644 index 0000000000000..f2bac08a381d6 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDateTimeActionGroup.xml @@ -0,0 +1,27 @@ +<?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="StorefrontSelectOptionDateTimeActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionDateTime"/> + <argument name="month" type="string"/> + <argument name="day" type="string"/> + <argument name="year" type="string"/> + <argument name="hour" type="string"/> + <argument name="minute" type="string"/> + <argument name="dayPart" type="string"/> + </arguments> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionMonth(optionTitle.title)}}" userInput="{{month}}" stepKey="selectMonthForOptionDate"/> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionDay(optionTitle.title)}}" userInput="{{day}}" stepKey="selectDayForOptionDate"/> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionYear(optionTitle.title)}}" userInput="{{year}}" stepKey="selectYearForOptionDate"/> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionHour(optionTitle.title)}}" userInput="{{hour}}" stepKey="selectHourrForOptionDateTime"/> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionMinute(optionTitle.title)}}" userInput="{{minute}}" stepKey="selectMinuteForOptionDateTime"/> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionDayPart(optionTitle.title)}}" userInput="{{dayPart}}" stepKey="selectDayPartForOptionDateTime"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDropDownActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDropDownActionGroup.xml new file mode 100644 index 0000000000000..1f8cedae4efb1 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDropDownActionGroup.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="StorefrontSelectOptionDropDownActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionDropDown"/> + <argument name="option" defaultValue="ProductOptionValueDropdown2.title" /> + </arguments> + <selectOption selector="{{StorefrontProductInfoMainSection.productOptionSelect(optionTitle.title)}}" userInput="{{option}}" stepKey="fillOptionDropDown"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionMultiSelectActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionMultiSelectActionGroup.xml new file mode 100644 index 0000000000000..ebf14b3f27d12 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionMultiSelectActionGroup.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="StorefrontSelectOptionMultiSelectActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionMultiSelect"/> + <argument name="option" defaultValue="ProductOptionValueMultiSelect2.title"/> + </arguments> + <selectOption selector="{{StorefrontProductInfoMainSection.productOptionSelect(optionTitle.title)}}" userInput="{{option}}" stepKey="selectOption"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionRadioButtonActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionRadioButtonActionGroup.xml new file mode 100644 index 0000000000000..844c9ede80232 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionRadioButtonActionGroup.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="StorefrontSelectOptionRadioButtonActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionRadiobutton"/> + <argument name="optionPrice" defaultValue="ProductOptionValueRadioButtons2"/> + </arguments> + <checkOption selector="{{StorefrontProductInfoMainSection.productOptionRadioButtonsCheckbox(optionTitle.title, optionPrice.price)}}" stepKey="SelectOptionCheckBox"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionTimeActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionTimeActionGroup.xml new file mode 100644 index 0000000000000..bfa75eae70251 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionTimeActionGroup.xml @@ -0,0 +1,21 @@ +<?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="StorefrontSelectOptionTimeActionGroup"> + <arguments> + <argument name="optionTitle" defaultValue="ProductOptionTime"/> + <argument name="hour" type="string"/> + <argument name="minute" type="string"/> + <argument name="dayPart" type="string"/> + </arguments> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionHour(optionTitle.title)}}" userInput="{{hour}}" stepKey="selectHourrForOptionDateTime"/> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionMinute(optionTitle.title)}}" userInput="{{minute}}" stepKey="selectMinuteForOptionDateTime"/> + <selectOption selector="{{StorefrontProductInfoMainSection.customOptionDayPart(optionTitle.title)}}" userInput="{{dayPart}}" stepKey="selectDayPartForOptionDateTime"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/VerifyCheckoutPaymentOrderSummaryActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/VerifyCheckoutPaymentOrderSummaryActionGroup.xml new file mode 100644 index 0000000000000..7937092965f29 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/VerifyCheckoutPaymentOrderSummaryActionGroup.xml @@ -0,0 +1,22 @@ +<?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 shipment form for free shipping--> + <actionGroup name="VerifyCheckoutPaymentOrderSummaryActionGroup"> + <arguments> + <argument name="orderSummarySubTotal" type="string"/> + <argument name="orderSummaryShippingTotal" type="string"/> + <argument name="orderSummaryTotal" type="string"/> + </arguments> + <see selector="{{CheckoutPaymentSection.orderSummarySubtotal}}" userInput="{{orderSummarySubTotal}}" stepKey="seeCorrectSubtotal"/> + <see selector="{{CheckoutPaymentSection.orderSummaryShippingTotal}}" userInput="{{orderSummaryShippingTotal}}" stepKey="seeCorrectShipping"/> + <see selector="{{CheckoutPaymentSection.orderSummaryTotal}}" userInput="{{orderSummaryTotal}}" stepKey="seeCorrectOrderTotal"/> + </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/Page/CheckoutCartPage.xml b/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml index bf17800f29ad1..a77b07a129dce 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml @@ -12,5 +12,6 @@ <section name="CheckoutCartProductSection"/> <section name="CheckoutCartSummarySection"/> <section name="CheckoutCartCrossSellSection"/> + <section name="CheckoutCartMessageSection"/> </page> </pages> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartMessageSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartMessageSection.xml new file mode 100644 index 0000000000000..cf15cdf15cf15 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartMessageSection.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="CheckoutCartMessageSection"> + <element name="successMessage" type="text" selector=".message.message-success.success>div" /> + <element name="errorMessage" type="text" selector=".message-error.error.message>div" /> + <element name="emptyCartMessage" type="text" selector=".cart-empty>p"/> + </section> +</sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml index dcfb12fd4e965..a5a1ce90e3312 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml @@ -24,6 +24,7 @@ <element name="ProductOptionByNameAndAttribute" type="input" selector="//main//table[@id='shopping-cart-table']//tbody//tr[.//strong[contains(@class, 'product-item-name')]//a[contains(text(), '{{var1}}')]]//dl[@class='item-options']//dt[.='{{var2}}']/following-sibling::dd[1]" parameterized="true"/> + <element name="ProductPriceByOption" type="text" selector="//*[contains(@class, 'item-options')]/dd[normalize-space(.)='{{var1}}']/ancestor::tr//td[contains(@class, 'price')]//span[@class='price']" parameterized="true"/> <element name="RemoveItem" type="button" selector="//table[@id='shopping-cart-table']//tbody//tr[contains(@class,'item-actions')]//a[contains(@class,'action-delete')]"/> <element name="productName" type="text" selector="//tbody[@class='cart item']//strong[@class='product-item-name']"/> @@ -33,5 +34,13 @@ <element name="productSubtotalByName" type="input" selector="//main//table[@id='shopping-cart-table']//tbody//tr[..//strong[contains(@class, 'product-item-name')]//a/text()='{{var1}}'][1]//td[contains(@class, 'subtotal')]//span[@class='price']" parameterized="true"/> <element name="updateShoppingCartButton" type="button" selector="#form-validate button[type='submit'].update" timeout="30"/> <element name="qty" type="input" selector="//input[@data-cart-item-id='{{var}}'][@title='Qty']" parameterized="true"/> + <element name="qtyByContains" type="text" selector="//input[contains(@data-cart-item-id, '{{sku}}')][@title='Qty']" parameterized="true"/> + <element name="messageErrorItem" type="text" selector="#sku-stock-failed-"/> + <element name="messageErrorNeedChooseOptions" type="text" selector="//*[contains(@class, 'error')]/div"/> + <element name="productOptionsLink" type="text" selector="a.action.configure"/> + <element name="productOptionLabel" type="text" selector="//dl[@class='item-options']"/> + <element name="checkoutCartProductPrice" type="text" selector="//td[@class='col price']//span[@class='price']"/> + <element name="checkoutCartSubtotal" type="text" selector="//td[@class='col subtotal']//span[@class='price']"/> + <element name="emptyCart" selector=".cart-empty" type="text"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml index 8d14a9a561900..0737186c92b20 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml @@ -14,15 +14,18 @@ <element name="shippingMethodForm" type="text" selector="#co-shipping-method-form"/> <element name="shippingMethod" type="text" selector="//*[@id='cart-totals']//tr[@class='totals shipping excl']//th//span[@class='value']"/> <element name="shipping" type="text" selector="//*[@id='cart-totals']//tr[@class='totals shipping excl']//td//span[@class='price']"/> - <element name="total" type="text" selector="//*[@id='cart-totals']//tr[@class='grand totals']//td//span[@class='price']"/> + <element name="total" type="text" selector="//*[@id='cart-totals']//tr[@class='grand totals']//td//span[@class='price']" timeout="10"/> <element name="proceedToCheckout" type="button" selector=".action.primary.checkout span" timeout="30"/> <element name="discountAmount" type="text" selector="td[data-th='Discount']"/> <element name="shippingHeading" type="button" selector="#block-shipping-heading"/> <element name="postcode" type="input" selector="input[name='postcode']" 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/CheckoutOrderSummarySection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutOrderSummarySection.xml index 6e00329901757..3a5b16c5a52ca 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutOrderSummarySection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutOrderSummarySection.xml @@ -17,5 +17,6 @@ <element name="shippingAddress" type="textarea" selector="//*[@class='box box-address-shipping']//address"/> <element name="billingAddress" type="textarea" selector="//*[@class='box box-address-billing']//address"/> <element name="additionalAddress" type="text" selector=".block.block-addresses-list"/> + <element name="miniCartTabClosed" type="button" selector=".title[aria-expanded='false']" timeout="30"/> </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..0989fccce9e9e 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml @@ -9,16 +9,21 @@ <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"/> + <element name="shippingBlock" type="text" selector="#checkout-step-shipping"/> <!--Order Summary--> <element name="itemInCart" type="button" selector="//div[@class='title']"/> 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/StorefrontMiniCartSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml index 38c88bf4f80bb..81ac53b318109 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml @@ -33,5 +33,10 @@ <element name="subtotal" type="text" selector="//tr[@class='totals sub']//td[@class='amount']/span"/> <element name="emptyCart" type="text" selector=".counter.qty.empty"/> <element name="minicartContent" type="block" selector="#minicart-content-wrapper"/> + <element name="messageEmptyCart" type="text" selector="//*[@id='minicart-content-wrapper']//*[contains(@class,'subtitle empty')]"/> + <element name="visibleItemsCountText" type="text" selector="//div[@class='items-total']"/> + <element name="productQuantity" type="input" selector="//*[@id='mini-cart']//a[contains(text(),'{{productName}}')]/../..//div[@class='details-qty qty']//input[@data-item-qty='{{qty}}']" parameterized="true"/> + <element name="productImage" type="text" selector="//ol[@id='mini-cart']//img[@class='product-image-photo']"/> + <element name="productSubTotal" type="text" selector="//div[@class='subtotal']//span/span[@class='price']"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartWithoutAnySelectedOptionTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartWithoutAnySelectedOptionTest.xml new file mode 100644 index 0000000000000..2ac84000fd1e8 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartWithoutAnySelectedOptionTest.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StoreFrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartWithoutAnySelectedOptionTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Add simple product with all types of custom options to cart without selecting any options"/> + <description value="Add simple product with all types of custom options to cart without selecting any options"/> + <testCaseId value="MC-14725"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <updateData createDataKey="createProduct" entity="productWithOptions" stepKey="updateProductWithCustomOptions"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + </after> + + <!-- Open Product page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct1PageAndVerifyProduct"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + + <!--Click on Add To Cart button--> + <actionGroup ref="StorefrontAddToTheCartActionGroup" stepKey="clickOnAddToCartButton"/> + + <!--Assert all types of product options field displayed Required message --> + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertRequiredProductOptionField"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.requiredCustomField(ProductOptionField.title)}}"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertRequiredProductOptionDropDown"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.requiredCustomField(ProductOptionDropDown.title)}}"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertRequiredProductOptionRadioButton"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.requiredCustomField(ProductOptionRadiobutton.title)}}"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertRequiredProductOptionCheckBox"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.requiredCustomField(ProductOptionCheckbox.title)}}"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertRequiredProductOptionMultiSelect"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.requiredCustomField(ProductOptionMultiSelect.title)}}"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertRequiredProductFileOption"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.requiredCustomFile(ProductOptionFile.title)}}"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertRequiredProductDateOption"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.requiredCustomDate(ProductOptionDate.title)}}"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertRequiredProductDateAndTimeOption"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.requiredCustomDate(ProductOptionDateTime.title)}}"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertRequiredProducTimeOption"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.requiredCustomDate(ProductOptionTime.title)}}"/> + </actionGroup> + + <!-- Verify mini cart is empty --> + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertEmptryCart"> + <argument name="selector" value="{{StorefrontMiniCartSection.emptyMiniCart}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddBundleDynamicProductToShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddBundleDynamicProductToShoppingCartTest.xml new file mode 100644 index 0000000000000..755c0cb9c93a0 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddBundleDynamicProductToShoppingCartTest.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="StorefrontAddBundleDynamicProductToShoppingCartTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Add bundle dynamic product to the cart"/> + <description value="Add bundle dynamic product to the cart"/> + <testCaseId value="MC-14715"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="FlatRateShippingMethodDefault" stepKey="setDefaultFlatRateShippingMethod"/> + <createData entity="SimpleSubCategory" stepKey="createSubCategory"/> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">50.00</field> + </createData> + <!--Create Bundle product--> + <createData entity="BundleProductPriceViewRange" stepKey="createBundleProduct"> + <requiredEntity createDataKey="createSubCategory"/> + </createData> + <createData entity="DropDownBundleOption" stepKey="createBundleOption1_1"> + <requiredEntity createDataKey="createBundleProduct"/> + <field key="required">True</field> + </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> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="createBundleProduct" stepKey="deleteBundleProduct"/> + <deleteData createDataKey="createSubCategory" stepKey="deleteCategory"/> + </after> + + <!--Open Product page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$createBundleProduct$$"/> + </actionGroup> + + <!--Assert Product Price Range --> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeIsVisible"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.priceFrom}}"/> + <argument name="userInput" value="From $10.00"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeIsVisible1"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.priceTo}}"/> + <argument name="userInput" value="To $50.00"/> + </actionGroup> + + <!-- Click on customize And Add To Cart Button --> + <actionGroup ref="StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup" stepKey="clickOnCustomizeAndAddtoCartButton"/> + + <!-- Select Product, Quantity and add to the cart --> + <click selector="{{StorefrontBundleProductActionSection.dropdownSelectOption}}" stepKey="clickOnSelectOption"/> + <click selector="{{StorefrontBundleProductActionSection.dropdownProductSelection($$simpleProduct2.name$$)}}" stepKey="selectProduct"/> + <actionGroup ref="StorefrontEnterProductQuantityAndAddToTheCartActionGroup" stepKey="enterProductQuantityAndAddToTheCart"> + <argument name="quantity" value="2"/> + </actionGroup> + + <!-- Select Mini Cart and select 'View And Edit Cart' --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="selectViewAndEditCart"/> + + <!--Assert Shopping Cart Summary--> + <actionGroup ref="AssertStorefrontShoppingCartSummaryWithShippingActionGroup" stepKey="AssertCartSummary" > + <argument name="subtotal" value="$100.00"/> + <argument name="shipping" value="$10.00"/> + <argument name="total" value="$110.00"/> + </actionGroup> + + <!--Assert Product items in cart --> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertSimpleProduct1ItemsInCheckOutCart"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + <argument name="productSku" value="$$createBundleProduct.sku$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="subtotal" value="$100.00" /> + <argument name="qty" value="2"/> + </actionGroup> + + <!-- Assert Product Option In CheckOut Cart --> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionTitleInCart"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="$$createBundleOption1_1.title$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionInCart"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="1 x $$simpleProduct2.name$$ $50.00"/> + </actionGroup> + + <!-- Assert Product in Mini Cart --> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="cartSubtotal" value="$100.00" /> + <argument name="qty" value="2"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddBundleDynamicProductToShoppingCartWithDisableMiniCartSidebarTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddBundleDynamicProductToShoppingCartWithDisableMiniCartSidebarTest.xml new file mode 100644 index 0000000000000..afbc6216ff9ca --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddBundleDynamicProductToShoppingCartWithDisableMiniCartSidebarTest.xml @@ -0,0 +1,127 @@ +<?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="StorefrontAddBundleDynamicProductToShoppingCartWithDisableMiniCartSidebarTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Create Grouped product and verify mini cart action with disabled and enable Mini Cart Sidebar"/> + <description value="Create Grouped product and verify mini cart action with disabled and enable Mini Cart Sidebar"/> + <testCaseId value="MC-14719"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <magentoCLI stepKey="disableShoppingCartSidebar" command="config:set checkout/sidebar/display 0"/> + <createData entity="FlatRateShippingMethodDefault" stepKey="setDefaultFlatRateShippingMethod"/> + <createData entity="SimpleSubCategory" stepKey="createSubCategory"/> + + <!--Create simple products--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">50.00</field> + </createData> + + <!--Create Bundle product--> + <createData entity="BundleProductPriceViewRange" stepKey="createBundleProduct"> + <requiredEntity createDataKey="createSubCategory"/> + </createData> + <createData entity="DropDownBundleOption" stepKey="createBundleOption1_1"> + <requiredEntity createDataKey="createBundleProduct"/> + <field key="required">True</field> + </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> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="createBundleProduct" stepKey="deleteBundleProduct"/> + <deleteData createDataKey="createSubCategory" stepKey="deleteCategory"/> + <magentoCLI stepKey="disableShoppingCartSidebar" command="config:set checkout/sidebar/display 1"/> + </after> + + <!--Open Product page in StoreFront and assert product and price range --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$createBundleProduct$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeIsVisible"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.priceFrom}}"/> + <argument name="userInput" value="From $10.00"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeIsVisible1"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.priceTo}}"/> + <argument name="userInput" value="To $50.00"/> + </actionGroup> + + <!-- Add Bundle dynamic product to the cart --> + <actionGroup ref="StorefrontAddBundleProductToTheCartActionGroup" stepKey="addBundleDynamicProductToTheCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + <argument name="quantity" value="2"/> + </actionGroup> + + <!-- Select Mini Cart, verify it doen't open --> + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart"/> + <dontSeeElement selector="{{StorefrontMinicartSection.viewAndEditCart}}" stepKey="dontSeeViewAndEditCart"/> + + <!--Assert Product items in cart --> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertSimpleProduct1ItemsInCheckOutCart"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + <argument name="productSku" value="$$createBundleProduct.sku$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="subtotal" value="$100.00" /> + <argument name="qty" value="2"/> + </actionGroup> + + <!-- Assert Grouped product with option is displayed in cart --> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionTitle"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="$$createBundleOption1_1.title$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionInCart"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="1 x $$simpleProduct2.name$$ $50.00"/> + </actionGroup> + + <!--Assert Shopping Cart Summary--> + <actionGroup ref="AssertStorefrontShoppingCartSummaryWithShippingActionGroup" stepKey="AssertCartSummary" > + <argument name="subtotal" value="$100.00"/> + <argument name="shipping" value="$10.00"/> + <argument name="total" value="$110.00"/> + </actionGroup> + + <!--Enabled Shopping Cart Sidebar --> + <magentoCLI stepKey="enableShoppingCartSidebar" command="config:set checkout/sidebar/display 1"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <reloadPage stepKey="reloadThePage"/> + + <!--Click on mini cart--> + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart1"/> + + <!--Assert mini cart can open and check mini cart items --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProductInMiniCart1"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="cartSubtotal" value="$100.00" /> + <argument name="qty" value="2"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddConfigurableProductToShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddConfigurableProductToShoppingCartTest.xml new file mode 100644 index 0000000000000..452f37e29b850 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddConfigurableProductToShoppingCartTest.xml @@ -0,0 +1,161 @@ +<?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="StorefrontAddConfigurableProductToShoppingCartTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Create configurable product with three options and add configurable product to the cart"/> + <description value="Create configurable product with three options and add configurable product to the cart"/> + <testCaseId value="MC-14716"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <!-- Create Default Category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + + <!-- Create an attribute with three options to be used in the first child product --> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption3" stepKey="createConfigProductAttributeOption3"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <!-- Add the attribute just created to default attribute set --> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <!-- Get the first option of the attribute created --> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Get the second option of the attribute created --> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Get the third option of the attribute created --> + <getData entity="ProductAttributeOptionGetter" index="3" stepKey="getConfigAttributeOption3"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Create Configurable product --> + <createData entity="BaseConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Create a simple product and give it the attribute with the first option --> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <field key="price">10.00</field> + </createData> + + <!--Create a simple product and give it the attribute with the second option --> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + <field key="price">20.00</field> + </createData> + + <!--Create a simple product and give it the attribute with the Third option --> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct3"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption3"/> + <field key="price">30.00</field> + </createData> + + <!-- Create the configurable product --> + <createData entity="ConfigurableProductThreeOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + <requiredEntity createDataKey="getConfigAttributeOption3"/> + </createData> + + <!-- Add the first simple product to the configurable product --> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + + <!-- Add the second simple product to the configurable product --> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + + <!-- Add the third simple product to the configurable product --> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild3"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct3"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="createConfigChildProduct3" stepKey="deleteSimpleProduct3"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteProductAttribute"/> + </after> + + <!-- Add Configurable Product to the cart --> + <actionGroup ref="StorefrontAddConfigurableProductToTheCartActionGroup" stepKey="addConfigurableProductToCart"> + <argument name="urlKey" value="$$createConfigProduct.custom_attributes[url_key]$$" /> + <argument name="productAttribute" value="$$createConfigProductAttribute.default_value$$"/> + <argument name="productOption" value="$$getConfigAttributeOption2.label$$"/> + <argument name="qty" value="2"/> + </actionGroup> + + <!-- Select Mini Cart and select 'View And Edit Cart' --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="selectViewAndEditCart"/> + + <!--Assert Shopping Cart Summary --> + <actionGroup ref="AssertStorefrontShoppingCartSummaryWithShippingActionGroup" stepKey="AssertCartSummary" > + <argument name="subtotal" value="$40.00"/> + <argument name="shipping" value="$10.00"/> + <argument name="total" value="$50.00"/> + </actionGroup> + + <!--Assert Product Details In Checkout cart --> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertProductItemInCheckOutCart"> + <argument name="productName" value="$$createConfigProduct.name$$"/> + <argument name="productSku" value="$$createConfigChildProduct2.sku$$"/> + <argument name="productPrice" value="$$createConfigChildProduct2.price$$"/> + <argument name="subtotal" value="$40.00" /> + <argument name="qty" value="2"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionInCart"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="$$getConfigAttributeOption2.label$$"/> + </actionGroup> + + <!-- Assert product details in Mini Cart --> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertMiniCart"> + <argument name="productName" value="$$createConfigProduct.name$$"/> + <argument name="productPrice" value="$$createConfigChildProduct2.price$$"/> + <argument name="cartSubtotal" value="$40.00" /> + <argument name="qty" value="2"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddDownloadableProductToShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddDownloadableProductToShoppingCartTest.xml new file mode 100644 index 0000000000000..8a501ca7bc28f --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddDownloadableProductToShoppingCartTest.xml @@ -0,0 +1,86 @@ +<?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="StorefrontAddDownloadableProductToShoppingCartTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Create Downloadable product with two links and add to the shopping cart"/> + <description value="Create Downloadable product with two links and add to the shopping cart"/> + <testCaseId value="MC-14717"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="FlatRateShippingMethodDefault" stepKey="setDefaultFlatRateShippingMethod"/> + <createData entity="ApiDownloadableProduct" stepKey="createDownloadableProduct"/> + <createData entity="downloadableLink1" stepKey="addDownloadableLink1"> + <requiredEntity createDataKey="createDownloadableProduct"/> + </createData> + <createData entity="downloadableLink2" stepKey="addDownloadableLink2"> + <requiredEntity createDataKey="createDownloadableProduct"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="createDownloadableProduct" stepKey="deleteProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Open Downloadable Product page --> + <amOnPage url="{{StorefrontProductPage.url($$createDownloadableProduct.custom_attributes[url_key]$$)}}" stepKey="OpenStoreFrontProductPage"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!-- Add Downloadable product to the cart --> + <actionGroup ref="StorefrontAddToCartCustomOptionsProductPageActionGroup" stepKey="addToTheCart"> + <argument name="productName" value="$$createDownloadableProduct.name$$" /> + </actionGroup> + + <!-- Select Mini Cart and select 'View And Edit Cart' --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="selectViewAndEditCart"/> + + <!--Assert Shopping Cart Summary--> + <actionGroup ref="AssertStorefrontShoppingCartSummaryItemsActionGroup" stepKey="AssertCartSummary" > + <argument name="subtotal" value="$123.00"/> + <argument name="total" value="$123.00"/> + </actionGroup> + + <!--Assert Product Details In Checkout cart --> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertProductItemInCheckOutCart"> + <argument name="productName" value="$$createDownloadableProduct.name$$"/> + <argument name="productSku" value="$$createDownloadableProduct.sku$$"/> + <argument name="productPrice" value="$123.00"/> + <argument name="subtotal" value="$123.00" /> + <argument name="qty" value="1"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeLinksInCart"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="Links"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionLink1"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="{{downloadableLink1.title}}"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionLink2"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="{{downloadableLink2.title}}"/> + </actionGroup> + + <!-- Assert product details in Mini Cart --> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertMiniCart"> + <argument name="productName" value="$$createDownloadableProduct.name$$"/> + <argument name="productPrice" value="$123.00"/> + <argument name="cartSubtotal" value="123.00" /> + <argument name="qty" value="1"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddGroupedProductToShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddGroupedProductToShoppingCartTest.xml new file mode 100644 index 0000000000000..a5fa13e15e64d --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddGroupedProductToShoppingCartTest.xml @@ -0,0 +1,127 @@ +<?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="StorefrontAddGroupedProductToShoppingCartTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Create grouped product with three simple product and Add grouped product to the cart"/> + <description value="Create grouped product with three simple product and Add grouped product to the cart"/> + <testCaseId value="MC-14718"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <!--Create Grouped product with three simple product --> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"> + <field key="price">100.00</field> + </createData> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="simple3"> + <field key="price">200.00</field> + </createData> + <createData entity="ApiProductWithDescription" stepKey="simple3" before="product"> + <field key="price">300.00</field> + </createData> + <createData entity="ApiGroupedProduct" stepKey="product"/> + <createData entity="OneSimpleProductLink" stepKey="addProductOne"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <updateData entity="OneMoreSimpleProductLink" createDataKey="addProductOne" stepKey="addProductTwo"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="simple2"/> + </updateData> + <updateData entity="OneMoreSimpleProductLink" createDataKey="addProductOne" stepKey="addProductThree"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="simple3"/> + </updateData> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simple2" stepKey="deleteProduct2"/> + <deleteData createDataKey="simple3" stepKey="deleteProduct3"/> + <deleteData createDataKey="product" stepKey="deleteGroupProduct"/> + </after> + + <!-- Fill Quantity and add Product to the cart --> + <actionGroup ref="StorefrontAddThreeGroupedProductToTheCartActionGroup" stepKey="addGropedProductsToTheCart"> + <argument name="urlKey" value="$$product.custom_attributes[url_key]$$"/> + <argument name="product1" value="$$simple1.name$$"/> + <argument name="product2" value="$$simple2.name$$"/> + <argument name="product3" value="$$simple3.name$$"/> + <argument name="qty1" value="1"/> + <argument name="qty2" value="2"/> + <argument name="qty3" value="3"/> + </actionGroup> + + <!-- Select Mini Cart and select 'View And Edit Cart' --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="selectViewAndEditCart"/> + + <!--Assert Product1 items in cart --> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertSimpleProduct1ItemsInCheckOutCart"> + <argument name="productName" value="$$simple1.name$$"/> + <argument name="productSku" value="$$simple1.sku$$"/> + <argument name="productPrice" value="$100.00"/> + <argument name="subtotal" value="$100.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!--Assert Product2 items in cart --> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertSimpleProduct2ItemsInCheckOutCart"> + <argument name="productName" value="$$simple2.name$$"/> + <argument name="productSku" value="$$simple2.sku$$"/> + <argument name="productPrice" value="$200.00"/> + <argument name="subtotal" value="$400.00" /> + <argument name="qty" value="2"/> + </actionGroup> + + <!--Assert Product3 items in cart --> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertSimpleProduct3ItemsInCheckOutCart"> + <argument name="productName" value="$$simple3.name$$"/> + <argument name="productSku" value="$$simple3.sku$$"/> + <argument name="productPrice" value="$300.00"/> + <argument name="subtotal" value="$900.00" /> + <argument name="qty" value="3"/> + </actionGroup> + + <!--Assert Shopping Cart Summary--> + <actionGroup ref="AssertStorefrontShoppingCartSummaryWithShippingActionGroup" stepKey="AssertCartSummary" > + <argument name="subtotal" value="$1,400.00"/> + <argument name="shipping" value="$30.00"/> + <argument name="total" value="$1,430.00"/> + </actionGroup> + + <!-- Assert product1 details in Mini Cart --> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$simple3.name$$"/> + <argument name="productPrice" value="$300.00"/> + <argument name="cartSubtotal" value="$1,400.00" /> + <argument name="qty" value="3"/> + </actionGroup> + + <!-- Assert product2 details in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct2MiniCart"> + <argument name="productName" value="$$simple2.name$$"/> + <argument name="productPrice" value="$200.00"/> + <argument name="cartSubtotal" value="$1,400.00" /> + <argument name="qty" value="2"/> + </actionGroup> + + <!-- Assert product3 details in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct1MiniCart"> + <argument name="productName" value="$$simple1.name$$"/> + <argument name="productPrice" value="$100.00"/> + <argument name="cartSubtotal" value="$1,400.00" /> + <argument name="qty" value="1"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddOneBundleMultiSelectOptionToTheShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddOneBundleMultiSelectOptionToTheShoppingCartTest.xml new file mode 100644 index 0000000000000..84c13eb13d48d --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddOneBundleMultiSelectOptionToTheShoppingCartTest.xml @@ -0,0 +1,120 @@ +<?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="StorefrontAddOneBundleMultiSelectOptionToTheShoppingCartTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Select one multi select option of a bundle product and add to the shopping cart"/> + <description value="Select one multi select option of a bundle product and add to the shopping cart "/> + <testCaseId value="MC-14727"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="FlatRateShippingMethodDefault" stepKey="setDefaultFlatRateShippingMethod"/> + <createData entity="SimpleSubCategory" stepKey="createSubCategory"/> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">50.00</field> + </createData> + <!--Create Bundle product with Multi Select option--> + <createData entity="BundleProductPriceViewRange" stepKey="createBundleProduct"> + <requiredEntity createDataKey="createSubCategory"/> + </createData> + <createData entity="MultipleSelectOption" stepKey="createBundleOption1_1"> + <requiredEntity createDataKey="createBundleProduct"/> + <field key="required">True</field> + </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> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="createBundleProduct" stepKey="deleteBundleProduct"/> + <deleteData createDataKey="createSubCategory" stepKey="deleteCategory"/> + </after> + + <!--Open Product page in StoreFront and assert product details --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$createBundleProduct$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeIsVisible"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.priceFrom}}"/> + <argument name="userInput" value="From $10.00"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeIsVisible1"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.priceTo}}"/> + <argument name="userInput" value="To $60.00"/> + </actionGroup> + + <!-- Click on customize And Add To Cart Button --> + <actionGroup ref="StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup" stepKey="clickOnCustomizeAndAddtoCartButton"/> + + <!-- Select One Product Option from MultiSelect option --> + <selectOption selector="{{StorefrontBundledSection.multiSelectOption}}" userInput="$$simpleProduct2.name$$ +$50.00" stepKey="selectOption2Product"/> + + <!--Enter product Quantity and add to the cart --> + <actionGroup ref="StorefrontEnterProductQuantityAndAddToTheCartActionGroup" stepKey="enterProductQuantityAndAddToTheCart"> + <argument name="quantity" value="1"/> + </actionGroup> + + <!-- Select Mini Cart and select 'View And Edit Cart' --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="selectViewAndEditCart"/> + + <!--Assert Shopping Cart Summary--> + <actionGroup ref="AssertStorefrontShoppingCartSummaryWithShippingActionGroup" stepKey="AssertCartSummary" > + <argument name="subtotal" value="$50.00"/> + <argument name="shipping" value="$5.00"/> + <argument name="total" value="$55.00"/> + </actionGroup> + + <!--Assert Product items in cart --> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertSimpleProduct1ItemsInCheckOutCart"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + <argument name="productSku" value="$$createBundleProduct.sku$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="subtotal" value="$50.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Grouped product options is displayed in cart --> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionTitle"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="$$createBundleOption1_1.title$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionInCart"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="1 x $$simpleProduct2.name$$ $50.00"/> + </actionGroup> + + <!-- Assert Product in Mini Cart --> + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart"/> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProductInMiniCart"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="cartSubtotal" value="$50.00" /> + <argument name="qty" value="1"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartTest.xml new file mode 100644 index 0000000000000..611c53dba0dec --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartTest.xml @@ -0,0 +1,114 @@ +<?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="StorefrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Create a simple products with all types of oprtions and add it to the cart"/> + <description value="Create a simple products with all types of oprtions and add it to the cart"/> + <testCaseId value="MC-14726"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <updateData createDataKey="createProduct" entity="productWithOptions" stepKey="updateProductWithCustomOptions"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + </after> + <!-- Open Product page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct1PageAndVerifyProduct"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + + <!-- Fill Option Input field --> + <actionGroup ref="StorefrontFillOptionFieldInputActionGroup" stepKey="fillTheOptionFieldInput"> + <argument name="fieldInput" value="1"/> + </actionGroup> + + <!-- Fill Option Text Area --> + <actionGroup ref="StorefrontFillOptionTextAreaActionGroup" stepKey="fillTheOptionTextAreaInput"> + <argument name="fieldInput" value="1"/> + </actionGroup> + + <!-- Attach file option--> + <actionGroup ref="StorefrontAttachOptionFileActionGroup" stepKey="selectAndAttachFile"/> + + <!--Select Option From DropDown option --> + <actionGroup ref="StorefrontSelectOptionDropDownActionGroup" stepKey="selectDropDownOption"/> + <scrollTo selector="{{StorefrontProductInfoMainSection.customOptionLabel(ProductOptionMultiSelect.title)}}" stepKey="scrollToMultiSelect"/> + + <!-- Select CheckBox From CheckBox option --> + <actionGroup ref="StorefrontSelectOptionCheckBoxActionGroup" stepKey="selectCheckBoxOption"/> + + <!-- Select RadioButton From Radio Button option --> + <actionGroup ref="StorefrontSelectOptionRadioButtonActionGroup" stepKey="selectRadioButtonOption"/> + + <!-- Select option From MultiSelect option --> + <actionGroup ref="StorefrontSelectOptionMultiSelectActionGroup" stepKey="selectOptionFromMultiSelect"/> + + <!-- Generate Date --> + <generateDate date="Now" format="Y" stepKey="year"/> + + <!-- Select Month,Day and Year From Date option --> + <actionGroup ref="StorefrontSelectOptionDateActionGroup" stepKey="fillOptionDate"> + <argument name="month" value="11"/> + <argument name="day" value="10"/> + <argument name="year" value="$year"/> + </actionGroup> + + <!-- Select Month, Day, Year, Hour,Minute and DayPart from DateTime option --> + <actionGroup ref="StorefrontSelectOptionDateTimeActionGroup" stepKey="fillOptionDateAndTime"> + <argument name="month" value="11"/> + <argument name="day" value="10"/> + <argument name="year" value="$year"/> + <argument name="hour" value="10"/> + <argument name="minute" value="20"/> + <argument name="dayPart" value="AM"/> + </actionGroup> + + <!-- Select Hour,Minute and DayPart from Time option --> + <actionGroup ref="StorefrontSelectOptionTimeActionGroup" stepKey="fillOptionTime"> + <argument name="hour" value="10"/> + <argument name="minute" value="20"/> + <argument name="dayPart" value="AM"/> + </actionGroup> + + <!-- Add product to the cart --> + <fillField selector="{{StorefrontProductInfoMainSection.qty}}" userInput="2" stepKey="fillQuantity"/> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="addProductToTheCart"> + <argument name="product" value="$$createProduct$$"/> + <argument name="productCount" value="2"/> + </actionGroup> + + <!-- Open Mini Cart --> + <actionGroup ref="StorefrontOpenMiniCartActionGroup" stepKey="openMiniCart"/> + + <!-- Assert Product Count in Mini Cart --> + <actionGroup ref="StorefrontAssertMiniCartItemCountActionGroup" stepKey="assertProductCountAndTextInMiniCart"> + <argument name="productCount" value="2"/> + <argument name="productCountText" value="1 Item in Cart"/> + </actionGroup> + + <!--Assert Product Items in Mini cart--> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$createProduct.name$$"/> + <argument name="productPrice" value="$1,642.58"/> + <argument name="cartSubtotal" value="$3,285.16" /> + <argument name="qty" value="2"/> + </actionGroup> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddTwoBundleMultiSelectOptionsToTheShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddTwoBundleMultiSelectOptionsToTheShoppingCartTest.xml new file mode 100644 index 0000000000000..83c25fb109d03 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddTwoBundleMultiSelectOptionsToTheShoppingCartTest.xml @@ -0,0 +1,123 @@ +<?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="StorefrontAddTwoBundleMultiSelectOptionsToTheShoppingCartTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Add two products to the cart from multi select options of a bundle product"/> + <description value="Add two products to the cart from multi select options of a bundle product."/> + <testCaseId value="MC-14728"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="FlatRateShippingMethodDefault" stepKey="setDefaultFlatRateShippingMethod"/> + <createData entity="SimpleSubCategory" stepKey="createSubCategory"/> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">50.00</field> + </createData> + <!--Create Bundle product with multi select option--> + <createData entity="BundleProductPriceViewRange" stepKey="createBundleProduct"> + <requiredEntity createDataKey="createSubCategory"/> + </createData> + <createData entity="MultipleSelectOption" stepKey="createBundleOption1_1"> + <requiredEntity createDataKey="createBundleProduct"/> + <field key="required">True</field> + </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> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="createBundleProduct" stepKey="deleteBundleProduct"/> + <deleteData createDataKey="createSubCategory" stepKey="deleteCategory"/> + </after> + + <!--Open Product page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$createBundleProduct$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeIsVisible"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.priceFrom}}"/> + <argument name="userInput" value="From $10.00"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeIsVisible1"> + <argument name="selector" value="{{StorefrontProductInfoMainSection.priceTo}}"/> + <argument name="userInput" value="To $60.00"/> + </actionGroup> + + <!-- Click on customize And Add To Cart Button --> + <actionGroup ref="StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup" stepKey="clickOnCustomizeAndAddtoCartButton"/> + + <!-- Select Two Products, enter the quantity and add product to the cart --> + <selectOption selector="{{StorefrontBundledSection.multiSelectOption}}" parameterArray="[$$simpleProduct1.name$$ +$10.00, $$simpleProduct2.name$$ +$50.00]" stepKey="selectOptions"/> + <actionGroup ref="StorefrontEnterProductQuantityAndAddToTheCartActionGroup" stepKey="enterProductQuantityAndAddToTheCart"> + <argument name="quantity" value="1"/> + </actionGroup> + + <!-- Select Mini Cart and select 'View And Edit Cart' --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="selectViewAndEditCart"/> + + <!--Assert Shopping Cart Summary--> + <actionGroup ref="AssertStorefrontShoppingCartSummaryWithShippingActionGroup" stepKey="AssertCartSummary" > + <argument name="subtotal" value="$60.00"/> + <argument name="shipping" value="$5.00"/> + <argument name="total" value="$65.00"/> + </actionGroup> + + <!--Assert Product items in cart --> + <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertSimpleProduct1ItemsInCheckOutCart"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + <argument name="productSku" value="$$createBundleProduct.sku$$"/> + <argument name="productPrice" value="$60.00"/> + <argument name="subtotal" value="$60.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Grouped product options is displayed in cart --> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOptionTitle"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="$$createBundleOption1_1.title$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOption1InCart"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="1 x $$simpleProduct1.name$$ $10.00"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductOption2InCart"> + <argument name="selector" value="{{CheckoutCartProductSection.productOptionLabel}}"/> + <argument name="userInput" value="1 x $$simpleProduct2.name$$ $50.00"/> + </actionGroup> + + <!-- Assert Product in Mini Cart --> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$createBundleProduct.name$$"/> + <argument name="productPrice" value="$60.00"/> + <argument name="cartSubtotal" value="$60.00" /> + <argument name="qty" value="1"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartAndSummaryBlockItemDisplayWithDefaultDisplayLimitationTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartAndSummaryBlockItemDisplayWithDefaultDisplayLimitationTest.xml new file mode 100644 index 0000000000000..92c612b75f07a --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartAndSummaryBlockItemDisplayWithDefaultDisplayLimitationTest.xml @@ -0,0 +1,342 @@ +<?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="StorefrontCheckCartAndSummaryBlockItemDisplayWithDefaultDisplayLimitationTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Add 10 items to the cart and check cart and summary block display with default display limit"/> + <description value="Add 10 items to the cart and check cart and summary block display with default display limit"/> + <testCaseId value="MC-14722"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="FlatRateShippingMethodDefault" stepKey="setDefaultFlatRateShippingMethod"/> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">20.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct3"> + <field key="price">30.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct4"> + <field key="price">40.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct5"> + <field key="price">50.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct6"> + <field key="price">60.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct7"> + <field key="price">70.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct8"> + <field key="price">80.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct9"> + <field key="price">90.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct10"> + <field key="price">100.00</field> + </createData> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="simpleProduct3" stepKey="deleteProduct3"/> + <deleteData createDataKey="simpleProduct4" stepKey="deleteProduct4"/> + <deleteData createDataKey="simpleProduct5" stepKey="deleteProduct5"/> + <deleteData createDataKey="simpleProduct6" stepKey="deleteProduct6"/> + <deleteData createDataKey="simpleProduct7" stepKey="deleteProduct7"/> + <deleteData createDataKey="simpleProduct8" stepKey="deleteProduct8"/> + <deleteData createDataKey="simpleProduct9" stepKey="deleteProduct9"/> + <deleteData createDataKey="simpleProduct10" stepKey="deleteProduct10"/> + </after> + + <!-- Open Product1 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct1PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + + <!-- Add Product1 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct1ToTheCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + </actionGroup> + + <!-- Open Product2 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct2PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + + <!-- Add Product2 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct2ToTheCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + </actionGroup> + + <!-- Open Product3 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct3PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct3$$"/> + </actionGroup> + + <!-- Add Product3 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct3ToTheCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + </actionGroup> + + <!-- Open Product4 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct4PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct4$$"/> + </actionGroup> + + <!-- Add Product4 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct4ToTheCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + </actionGroup> + + <!-- Open Product5 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct5PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct5$$"/> + </actionGroup> + + <!-- Add Product5 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct5ToTheCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + </actionGroup> + + <!-- Open Product6 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct6PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct6$$"/> + </actionGroup> + + <!-- Add Product6 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct6ToTheCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + </actionGroup> + + <!-- Open Product7 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct7PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct7$$"/> + </actionGroup> + + <!-- Add Product7 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct7ToTheCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + </actionGroup> + + <!-- Open Product8 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct8PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct8$$"/> + </actionGroup> + + <!-- Add Product8 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct8ToTheCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + </actionGroup> + + <!-- Open Product9 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct9PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct9$$"/> + </actionGroup> + + <!-- Add Product9 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct9ToTheCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + </actionGroup> + + <!-- Open Product10 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPage10AndVerifyProduct"> + <argument name="product" value="$$simpleProduct10$$"/> + </actionGroup> + + <!-- Add Product10 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct10ToTheCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + </actionGroup> + + <!-- Open Mini Cart --> + <actionGroup ref="StorefrontOpenMiniCartActionGroup" stepKey="openMiniCart"/> + + <!-- Assert Product Count in Mini Cart --> + <actionGroup ref="StorefrontAssertMiniCartItemCountActionGroup" stepKey="assertProductCountAndTextInMiniCart"> + <argument name="productCount" value="10"/> + <argument name="productCountText" value="10 Items in Cart"/> + </actionGroup> + + <!-- Assert Product1 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct11MiniCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + <argument name="productPrice" value="$10.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product2 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct2MiniCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + <argument name="productPrice" value="$20.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product3 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + <argument name="productPrice" value="$30.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product4 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct4MiniCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + <argument name="productPrice" value="$40.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product5 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct5MiniCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product6 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct6MiniCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + <argument name="productPrice" value="$60.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product7 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct7MiniCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + <argument name="productPrice" value="$70.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product8 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct8MiniCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + <argument name="productPrice" value="$80.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product9 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct9MiniCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + <argument name="productPrice" value="$90.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product10 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct10MiniCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + <argument name="productPrice" value="$100.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Order Summary --> + <actionGroup ref="StorefrontCheckoutAndAssertOrderSummaryDisplayActionGroup" stepKey="AssertOrderSummary"> + <argument name="itemsText" value="10 Items in Cart"/> + </actionGroup> + + <!-- Assert Shipping Page --> + <actionGroup ref="StorefrontAssertShippingAddressPageDisplayActionGroup" stepKey="assertShippingPageDisplay"/> + + <!--Click on order summary tab --> + <conditionalClick selector="{{CheckoutOrderSummarySection.miniCartTab}}" dependentSelector="{{CheckoutOrderSummarySection.miniCartTabClosed}}" visible="true" stepKey="clickOnOrderSummaryTab"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!-- Assert Product1 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct1InOrderSummary"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$10.00"/> + </actionGroup> + + <!-- Assert Product2 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct2InOrderSummary"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$20.00"/> + </actionGroup> + + <!-- Assert Product3 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct3InOrderSummary"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$30.00"/> + </actionGroup> + + <!-- Assert Product4 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct4InOrderSummary"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$40.00"/> + </actionGroup> + + <!-- Assert Product5 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct5InOrderSummary"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$50.00"/> + </actionGroup> + + <!-- Assert Product6 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct6InOrderSummary"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$60.00"/> + </actionGroup> + + <!-- Assert Product7 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct7InOrderSummary"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$70.00"/> + </actionGroup> + + <!-- Assert Product8 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct8InOrderSummary"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$80.00"/> + </actionGroup> + + <!-- Assert Product9 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct9InOrderSummary"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$90.00"/> + </actionGroup> + + <!-- Assert Product10 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct10InOrderSummary"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$100.00"/> + </actionGroup> + + <!-- Verify View and Edit Cart is not displayed in Order Summary --> + <dontSeeElement selector="{{StorefrontMinicartSection.viewAndEditCart}}" stepKey="dontSeeViewAndEditLink"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartItemDisplayWhenMoreItemsAddedToTheCartThanDefaultDisplayLimitTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartItemDisplayWhenMoreItemsAddedToTheCartThanDefaultDisplayLimitTest.xml new file mode 100644 index 0000000000000..fec5bb9fadb6e --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartItemDisplayWhenMoreItemsAddedToTheCartThanDefaultDisplayLimitTest.xml @@ -0,0 +1,282 @@ +<?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="StorefrontCheckCartItemDisplayWhenMoreItemsAddedToTheCartThanDefaultDisplayLimitTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Add 11 Simple product to the cart and check cart display with default display limit"/> + <description value="Add 11 Simple product to the cart and check cart display with default display limit"/> + <testCaseId value="MC-14720"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="FlatRateShippingMethodDefault" stepKey="setDefaultFlatRateShippingMethod"/> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">20.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct3"> + <field key="price">30.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct4"> + <field key="price">40.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct5"> + <field key="price">50.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct6"> + <field key="price">60.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct7"> + <field key="price">70.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct8"> + <field key="price">80.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct9"> + <field key="price">90.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct10"> + <field key="price">100.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct11"> + <field key="price">110.00</field> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="simpleProduct3" stepKey="deleteProduct3"/> + <deleteData createDataKey="simpleProduct4" stepKey="deleteProduct4"/> + <deleteData createDataKey="simpleProduct5" stepKey="deleteProduct5"/> + <deleteData createDataKey="simpleProduct6" stepKey="deleteProduct6"/> + <deleteData createDataKey="simpleProduct7" stepKey="deleteProduct7"/> + <deleteData createDataKey="simpleProduct8" stepKey="deleteProduct8"/> + <deleteData createDataKey="simpleProduct9" stepKey="deleteProduct9"/> + <deleteData createDataKey="simpleProduct10" stepKey="deleteProduct10"/> + <deleteData createDataKey="simpleProduct11" stepKey="deleteProduct11"/> + </after> + + <!-- Open Product1 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct1PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + + <!-- Add Product1 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct1ToTheCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + </actionGroup> + + <!-- Open Product2 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct2PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + + <!-- Add Product2 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct2ToTheCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + </actionGroup> + + <!-- Open Product3 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct3PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct3$$"/> + </actionGroup> + + <!-- Add Product3 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct3ToTheCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + </actionGroup> + + <!-- Open Product4 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct4PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct4$$"/> + </actionGroup> + + <!-- Add Product4 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct4ToTheCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + </actionGroup> + + <!-- Open Product5 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct5PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct5$$"/> + </actionGroup> + + <!-- Add Product5 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct5ToTheCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + </actionGroup> + + <!-- Open Product6 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct6PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct6$$"/> + </actionGroup> + + <!-- Add Product6 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct6ToTheCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + </actionGroup> + + <!-- Open Product7 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct7PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct7$$"/> + </actionGroup> + + <!-- Add Product7 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct7ToTheCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + </actionGroup> + + <!-- Open Product8 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct8PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct8$$"/> + </actionGroup> + + <!-- Add Product8 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct8ToTheCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + </actionGroup> + + <!-- Open Product9 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct9PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct9$$"/> + </actionGroup> + + <!-- Add Product9 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct9ToTheCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + </actionGroup> + + <!-- Open Product10 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPage10AndVerifyProduct"> + <argument name="product" value="$$simpleProduct10$$"/> + </actionGroup> + + <!-- Add Product10 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct10ToTheCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + </actionGroup> + + <!-- Open Product11 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPage11AndVerifyProduct"> + <argument name="product" value="$$simpleProduct11$$"/> + </actionGroup> + + <!-- Add Product11 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct11ToTheCart"> + <argument name="productName" value="$$simpleProduct11.name$$"/> + </actionGroup> + + + <!-- Assert Product details in Mini Cart --> + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart"/> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductCountInMiniCart"> + <argument name="selector" value="{{StorefrontMinicartSection.productCount}}"/> + <argument name="userInput" value="11"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeTotalNumberOfItemDisplayed"> + <argument name="selector" value="{{StorefrontMinicartSection.visibleItemsCountText}}"/> + <argument name="userInput" value="10 of 11 Items in Cart"/> + </actionGroup> + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="seeViewAndEditLink"> + <argument name="selector" value="{{StorefrontMinicartSection.viewAndEditCart}}"/> + </actionGroup> + + <!-- Assert Product1 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct1InMiniCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + <argument name="productPrice" value="$10.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product2 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct2InMiniCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + <argument name="productPrice" value="$20.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product3 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + <argument name="productPrice" value="$30.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product4 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct4InMiniCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + <argument name="productPrice" value="$40.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product5 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct5InMiniCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product6 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct6InMiniCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + <argument name="productPrice" value="$60.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product7 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct7InMiniCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + <argument name="productPrice" value="$70.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product8 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct8InMiniCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + <argument name="productPrice" value="$80.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product9 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct9InMiniCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + <argument name="productPrice" value="$90.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product10 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct10InMiniCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + <argument name="productPrice" value="$100.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Verify Product11 is not displayed in Mini Cart --> + <dontSee selector="{{StorefrontMinicartSection.miniCartItemsText}}" userInput="$$simpleProduct11.name$$" stepKey="dontSeeProduct11NameInMiniCart"/> + <dontSee selector="{{StorefrontMinicartSection.miniCartItemsText}}" userInput="110" stepKey="dontSeeProduct11PriceInMiniCart"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartItemDisplayWithDefaultDisplayLimitAndDefaultTotalQuantityTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartItemDisplayWithDefaultDisplayLimitAndDefaultTotalQuantityTest.xml new file mode 100644 index 0000000000000..2339789bd85d1 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartItemDisplayWithDefaultDisplayLimitAndDefaultTotalQuantityTest.xml @@ -0,0 +1,256 @@ +<?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="StorefrontCheckCartItemDisplayWithDefaultDisplayLimitAndDefaultTotalQuantityTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Add 10 items to the cart and check cart display with default display limit"/> + <description value="Add 10 items to the cart and check cart display with default display limit"/> + <testCaseId value="MC-14721"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">20.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct3"> + <field key="price">30.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct4"> + <field key="price">40.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct5"> + <field key="price">50.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct6"> + <field key="price">60.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct7"> + <field key="price">70.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct8"> + <field key="price">80.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct9"> + <field key="price">90.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct10"> + <field key="price">100.00</field> + </createData> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="simpleProduct3" stepKey="deleteProduct3"/> + <deleteData createDataKey="simpleProduct4" stepKey="deleteProduct4"/> + <deleteData createDataKey="simpleProduct5" stepKey="deleteProduct5"/> + <deleteData createDataKey="simpleProduct6" stepKey="deleteProduct6"/> + <deleteData createDataKey="simpleProduct7" stepKey="deleteProduct7"/> + <deleteData createDataKey="simpleProduct8" stepKey="deleteProduct8"/> + <deleteData createDataKey="simpleProduct9" stepKey="deleteProduct9"/> + <deleteData createDataKey="simpleProduct10" stepKey="deleteProduct10"/> + </after> + + <!-- Open Product1 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct1PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + + <!-- Add Product1 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct1ToTheCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + </actionGroup> + + <!-- Open Product2 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct2PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + + <!-- Add Product2 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct2ToTheCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + </actionGroup> + + <!-- Open Product3 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct3PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct3$$"/> + </actionGroup> + + <!-- Add Product3 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct3ToTheCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + </actionGroup> + + <!-- Open Product4 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct4PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct4$$"/> + </actionGroup> + + <!-- Add Product4 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct4ToTheCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + </actionGroup> + + <!-- Open Product5 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct5PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct5$$"/> + </actionGroup> + + <!-- Add Product5 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct5ToTheCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + </actionGroup> + + <!-- Open Product6 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct6PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct6$$"/> + </actionGroup> + + <!-- Add Product6 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct6ToTheCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + </actionGroup> + + <!-- Open Product7 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct7PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct7$$"/> + </actionGroup> + + <!-- Add Product7 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct7ToTheCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + </actionGroup> + + <!-- Open Product8 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct8PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct8$$"/> + </actionGroup> + + <!-- Add Product8 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct8ToTheCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + </actionGroup> + + <!-- Open Product9 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct9PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct9$$"/> + </actionGroup> + + <!-- Add Product9 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct9ToTheCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + </actionGroup> + + <!-- Open Product10 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPage10AndVerifyProduct"> + <argument name="product" value="$$simpleProduct10$$"/> + </actionGroup> + + <!-- Add Product10 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct10ToTheCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + </actionGroup> + + <!-- Open Mini Cart --> + <actionGroup ref="StorefrontOpenMiniCartActionGroup" stepKey="openMiniCart"/> + + <!-- Assert Product Count in Mini Cart --> + <actionGroup ref="StorefrontAssertMiniCartItemCountActionGroup" stepKey="assertProductCountAndTextInMiniCart"> + <argument name="productCount" value="10"/> + <argument name="productCountText" value="10 Items in Cart"/> + </actionGroup> + + <!-- Assert Product1 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct11MiniCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + <argument name="productPrice" value="$10.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product2 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct2MiniCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + <argument name="productPrice" value="$20.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product3 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + <argument name="productPrice" value="$30.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product4 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct4MiniCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + <argument name="productPrice" value="$40.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product5 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct5MiniCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product6 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct6MiniCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + <argument name="productPrice" value="$60.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product7 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct7MiniCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + <argument name="productPrice" value="$70.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product8 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct8MiniCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + <argument name="productPrice" value="$80.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product9 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct9MiniCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + <argument name="productPrice" value="$90.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product10 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct10MiniCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + <argument name="productPrice" value="$100.00"/> + <argument name="cartSubtotal" value="$550.00" /> + <argument name="qty" value="1"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckSimpleProductCartItemDisplayWithDefaultLimitationTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckSimpleProductCartItemDisplayWithDefaultLimitationTest.xml new file mode 100644 index 0000000000000..bca14c8379ca3 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckSimpleProductCartItemDisplayWithDefaultLimitationTest.xml @@ -0,0 +1,356 @@ +<?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="StorefrontCheckSimpleProductCartItemDisplayWithDefaultLimitationTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Add 10 SimpleProducts to the cart and check cart display with default display limit"/> + <description value="Add 10 SimpleProducts to the cart and check cart display with default display limit"/> + <testCaseId value="MC-14723"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <createData entity="FlatRateShippingMethodDefault" stepKey="setDefaultFlatRateShippingMethod"/> + <!--Create simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">20.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct3"> + <field key="price">30.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct4"> + <field key="price">40.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct5"> + <field key="price">50.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct6"> + <field key="price">60.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct7"> + <field key="price">70.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct8"> + <field key="price">80.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct9"> + <field key="price">90.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct10"> + <field key="price">100.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct11"> + <field key="price">110.00</field> + </createData> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="simpleProduct3" stepKey="deleteProduct3"/> + <deleteData createDataKey="simpleProduct4" stepKey="deleteProduct4"/> + <deleteData createDataKey="simpleProduct5" stepKey="deleteProduct5"/> + <deleteData createDataKey="simpleProduct6" stepKey="deleteProduct6"/> + <deleteData createDataKey="simpleProduct7" stepKey="deleteProduct7"/> + <deleteData createDataKey="simpleProduct8" stepKey="deleteProduct8"/> + <deleteData createDataKey="simpleProduct9" stepKey="deleteProduct9"/> + <deleteData createDataKey="simpleProduct10" stepKey="deleteProduct10"/> + <deleteData createDataKey="simpleProduct11" stepKey="deleteProduct11"/> + </after> + + <!-- Open Product1 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct1PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + + <!-- Add Product1 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct1ToTheCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + </actionGroup> + + <!-- Open Product2 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct2PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + + <!-- Add Product2 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct2ToTheCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + </actionGroup> + + <!-- Open Product3 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct3PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct3$$"/> + </actionGroup> + + <!-- Add Product3 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct3ToTheCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + </actionGroup> + + <!-- Open Product4 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct4PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct4$$"/> + </actionGroup> + + <!-- Add Product4 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct4ToTheCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + </actionGroup> + + <!-- Open Product5 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct5PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct5$$"/> + </actionGroup> + + <!-- Add Product5 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct5ToTheCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + </actionGroup> + + <!-- Open Product6 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct6PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct6$$"/> + </actionGroup> + + <!-- Add Product6 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct6ToTheCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + </actionGroup> + + <!-- Open Product7 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct7PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct7$$"/> + </actionGroup> + + <!-- Add Product7 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct7ToTheCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + </actionGroup> + + <!-- Open Product8 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct8PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct8$$"/> + </actionGroup> + + <!-- Add Product8 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct8ToTheCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + </actionGroup> + + <!-- Open Product9 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct9PageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct9$$"/> + </actionGroup> + + <!-- Add Product9 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct9ToTheCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + </actionGroup> + + <!-- Open Product10 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPage10AndVerifyProduct"> + <argument name="product" value="$$simpleProduct10$$"/> + </actionGroup> + + <!-- Add Product10 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct10ToTheCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + </actionGroup> + + <!-- Open Product11 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPage11AndVerifyProduct"> + <argument name="product" value="$$simpleProduct11$$"/> + </actionGroup> + + <!-- Add Product11 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct11ToTheCart"> + <argument name="productName" value="$$simpleProduct11.name$$"/> + </actionGroup> + + <!-- Open Mini Cart --> + <actionGroup ref="StorefrontOpenMiniCartActionGroup" stepKey="openMiniCart"/> + + <!-- Assert Product Count in Mini Cart --> + <actionGroup ref="StorefrontAssertMiniCartItemCountActionGroup" stepKey="assertProductCountAndTextInMiniCart"> + <argument name="productCount" value="11"/> + <argument name="productCountText" value="10 of 11 Items in Cart"/> + </actionGroup> + + <!-- Assert Product1 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct11MiniCart"> + <argument name="productName" value="$$simpleProduct1.name$$"/> + <argument name="productPrice" value="$10.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product2 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct2MiniCart"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + <argument name="productPrice" value="$20.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product3 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + <argument name="productPrice" value="$30.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product4 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct4MiniCart"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + <argument name="productPrice" value="$40.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product5 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct5MiniCart"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + <argument name="productPrice" value="$50.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product6 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct6MiniCart"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + <argument name="productPrice" value="$60.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product7 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct7MiniCart"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + <argument name="productPrice" value="$70.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product8 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct8MiniCart"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + <argument name="productPrice" value="$80.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product9 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct9MiniCart"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + <argument name="productPrice" value="$90.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product10 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct10MiniCart"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + <argument name="productPrice" value="$100.00"/> + <argument name="cartSubtotal" value="$660.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Order Summary --> + <actionGroup ref="StorefrontCheckoutAndAssertOrderSummaryDisplayActionGroup" stepKey="AssertItemsCountInOrderSummary"> + <argument name="itemsText" value="10 of 11 Items in Cart"/> + </actionGroup> + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="seeViewAndEditLinkInOrderSummary"> + <argument name="selector" value="{{StorefrontMinicartSection.viewAndEditCart}}"/> + </actionGroup> + + <!-- Click and open order summary tab--> + <conditionalClick selector="{{CheckoutOrderSummarySection.miniCartTab}}" dependentSelector="{{CheckoutOrderSummarySection.miniCartTabClosed}}" visible="true" stepKey="clickOnOrderSummaryTab"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!-- Assert Shipping Page --> + <actionGroup ref="StorefrontAssertShippingAddressPageDisplayActionGroup" stepKey="assertShippingPageDisplay"/> + + <!-- Assert Product2 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct2InOrderSummary"> + <argument name="productName" value="$$simpleProduct2.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$20.00"/> + </actionGroup> + + <!-- Assert Product3 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct3InOrderSummary"> + <argument name="productName" value="$$simpleProduct3.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$30.00"/> + </actionGroup> + + <!-- Assert Product4 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct4InOrderSummary"> + <argument name="productName" value="$$simpleProduct4.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$40.00"/> + </actionGroup> + + <!-- Assert Product5 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct5InOrderSummary"> + <argument name="productName" value="$$simpleProduct5.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$50.00"/> + </actionGroup> + + <!-- Assert Product6 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct6InOrderSummary"> + <argument name="productName" value="$$simpleProduct6.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$60.00"/> + </actionGroup> + + <!-- Assert Product7 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct7InOrderSummary"> + <argument name="productName" value="$$simpleProduct7.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$70.00"/> + </actionGroup> + + <!-- Assert Product8 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct8InOrderSummary"> + <argument name="productName" value="$$simpleProduct8.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$80.00"/> + </actionGroup> + + <!-- Assert Product9 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct9InOrderSummary"> + <argument name="productName" value="$$simpleProduct9.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$90.00"/> + </actionGroup> + + <!-- Assert Product10 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct10InOrderSummary"> + <argument name="productName" value="$$simpleProduct10.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$100.00"/> + </actionGroup> + + <!-- Assert Product11 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct11InOrderSummary"> + <argument name="productName" value="$$simpleProduct11.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$110.00"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckVirtualProductCountDisplayWithCustomDisplayConfigurationTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckVirtualProductCountDisplayWithCustomDisplayConfigurationTest.xml new file mode 100644 index 0000000000000..1f63565899f88 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckVirtualProductCountDisplayWithCustomDisplayConfigurationTest.xml @@ -0,0 +1,147 @@ +<?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="StorefrontCheckVirtualProductCountDisplayWithCustomDisplayConfigurationTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Verify virtual products count in mini cart and summary block with custom display configuration"/> + <description value="Verify virtual products count in mini cart and summary block with custom display configuration"/> + <testCaseId value="MC-14724"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <!--Set Mini Cart and Summary Block Display --> + <magentoCLI stepKey="setMaxDisplayCountForMiniCart" command="config:set checkout/options/max_items_display_count 2"/> + <magentoCLI stepKey="setMaxDisplayCountForOrderSummary" command="config:set checkout/sidebar/max_items_display_count 3"/> + <!--Create simple product--> + <createData entity="VirtualProduct" stepKey="virtualProduct1"> + <field key="price">10.00</field> + </createData> + <createData entity="VirtualProduct" stepKey="virtualProduct2"> + <field key="price">20.00</field> + </createData> + <createData entity="VirtualProduct" stepKey="virtualProduct3"> + <field key="price">30.00</field> + </createData> + <createData entity="VirtualProduct" stepKey="virtualProduct4"> + <field key="price">40.00</field> + </createData> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="virtualProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="virtualProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="virtualProduct3" stepKey="deleteProduct3"/> + <deleteData createDataKey="virtualProduct4" stepKey="deleteProduct4"/> + <magentoCLI stepKey="setMaxDisplayCountForMiniCart" command="config:set checkout/options/max_items_display_count 10"/> + <magentoCLI stepKey="setMaxDisplayCountForOrderSummary" command="config:set checkout/sidebar/max_items_display_count 10"/> + </after> + + <!-- Open Product1 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct1PageAndVerifyProduct"> + <argument name="product" value="$$virtualProduct1$$"/> + </actionGroup> + + <!-- Add Product1 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct1ToTheCart"> + <argument name="productName" value="$$virtualProduct1.name$$"/> + </actionGroup> + + <!-- Open Product2 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct2PageAndVerifyProduct"> + <argument name="product" value="$$virtualProduct2$$"/> + </actionGroup> + + <!-- Add Product2 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct2ToTheCart"> + <argument name="productName" value="$$virtualProduct2.name$$"/> + </actionGroup> + + <!-- Open Product3 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct3PageAndVerifyProduct"> + <argument name="product" value="$$virtualProduct3$$"/> + </actionGroup> + + <!-- Add Product3 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct3ToTheCart"> + <argument name="productName" value="$$virtualProduct3.name$$"/> + </actionGroup> + + <!-- Open Product4 page in StoreFront --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProduct4PageAndVerifyProduct"> + <argument name="product" value="$$virtualProduct4$$"/> + </actionGroup> + + <!-- Add Product4 to the cart --> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addProduct4ToTheCart"> + <argument name="productName" value="$$virtualProduct4.name$$"/> + </actionGroup> + + <!-- Open Mini Cart --> + <actionGroup ref="StorefrontOpenMiniCartActionGroup" stepKey="openMiniCart"/> + + <!-- Assert Product Count in Mini Cart --> + <actionGroup ref="StorefrontAssertMiniCartItemCountActionGroup" stepKey="assertProductCountAndTextInMiniCart"> + <argument name="productCount" value="4"/> + <argument name="productCountText" value="3 of 4 Items in Cart"/> + </actionGroup> + + <!-- Assert Product1 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct11MiniCart"> + <argument name="productName" value="$$virtualProduct1.name$$"/> + <argument name="productPrice" value="$10.00"/> + <argument name="cartSubtotal" value="$100.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product2 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct2MiniCart"> + <argument name="productName" value="$$virtualProduct2.name$$"/> + <argument name="productPrice" value="$20.00"/> + <argument name="cartSubtotal" value="$100.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Product3 in Mini Cart --> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="assertSimpleProduct3MiniCart"> + <argument name="productName" value="$$virtualProduct3.name$$"/> + <argument name="productPrice" value="$30.00"/> + <argument name="cartSubtotal" value="$100.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!-- Assert Order Summary --> + <actionGroup ref="StorefrontCheckoutAndAssertOrderSummaryDisplayActionGroup" stepKey="AssertItemCountInOrderSummary"> + <argument name="itemsText" value="2 of 4 Items in Cart"/> + </actionGroup> + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="seeViewAndEditLinkInOrderSummary"> + <argument name="selector" value="{{StorefrontMinicartSection.viewAndEditCart}}"/> + </actionGroup> + + <!-- Click and open order summary tab--> + <conditionalClick selector="{{CheckoutOrderSummarySection.miniCartTab}}" dependentSelector="{{CheckoutOrderSummarySection.miniCartTabClosed}}" visible="true" stepKey="clickOnOrderSummaryTab"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!-- Assert Product3 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct3InOrderSummary"> + <argument name="productName" value="$$virtualProduct3.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$30.00"/> + </actionGroup> + + <!-- Assert Product4 displayed in Order Summary --> + <actionGroup ref="StorefrontAssertProductDetailsInOrderSummaryActionGroup" stepKey="assertProduct4InOrderSummary"> + <argument name="productName" value="$$virtualProduct4.name$$"/> + <argument name="qty" value="1"/> + <argument name="price" value="$40.00"/> + </actionGroup> + </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/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml index 3401369a8c749..5e7e76ae4f02a 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml @@ -11,6 +11,7 @@ <test name="StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest"> <annotations> <features value="Checkout"/> + <stories value="Minicart"/> <title value="Checking Product name in Minicart and on Checkout page with different store views"/> <description value="Checking Product name in Minicart and on Checkout page with different store views"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml index 423f4049f6722..72f6cf95a6fbe 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml @@ -11,9 +11,11 @@ <test name="StorefrontUpdateShoppingCartSimpleProductQtyTest"> <annotations> <features value="Checkout"/> + <stories value="Shopping cart"/> <title value="Check updating shopping cart while updating items qty"/> <description value="Check updating shopping cart while updating items qty"/> <testCaseId value="MC-14731" /> + <severity value="AVERAGE"/> <group value="shoppingCart"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml index 84080b04c80ee..7a653f13c4ee5 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml @@ -11,9 +11,11 @@ <test name="StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest"> <annotations> <features value="Checkout"/> + <stories value="Shopping cart"/> <title value="Check updating shopping cart while updating qty of items with custom options"/> <description value="Check updating shopping cart while updating qty of items with custom options"/> <testCaseId value="MC-14732" /> + <severity value="AVERAGE"/> <group value="shoppingCart"/> <group value="mtf_migrated"/> </annotations> 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 e54f464f24d02..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); } }); 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/CheckoutAgreementsGraphQl/Model/Resolver/CheckoutAgreements.php b/app/code/Magento/CheckoutAgreementsGraphQl/Model/Resolver/CheckoutAgreements.php index 7009e1e2d85e6..02345f71681f8 100644 --- a/app/code/Magento/CheckoutAgreementsGraphQl/Model/Resolver/CheckoutAgreements.php +++ b/app/code/Magento/CheckoutAgreementsGraphQl/Model/Resolver/CheckoutAgreements.php @@ -7,10 +7,14 @@ namespace Magento\CheckoutAgreementsGraphQl\Model\Resolver; -use Magento\CheckoutAgreementsGraphQl\Model\Resolver\DataProvider\CheckoutAgreements as CheckoutAgreementsDataProvider; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\CheckoutAgreements\Api\Data\AgreementInterface; +use Magento\CheckoutAgreements\Model\ResourceModel\Agreement\CollectionFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; /** * Checkout Agreements resolver, used for GraphQL request processing @@ -18,17 +22,33 @@ class CheckoutAgreements implements ResolverInterface { /** - * @var CheckoutAgreementsDataProvider + * @var CollectionFactory */ - private $checkoutAgreementsDataProvider; + private $agreementCollectionFactory; /** - * @param CheckoutAgreementsDataProvider $checkoutAgreementsDataProvider + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @param CollectionFactory $agreementCollectionFactory + * @param StoreManagerInterface $storeManager + * @param ScopeConfigInterface $scopeConfig */ public function __construct( - CheckoutAgreementsDataProvider $checkoutAgreementsDataProvider + CollectionFactory $agreementCollectionFactory, + StoreManagerInterface $storeManager, + ScopeConfigInterface $scopeConfig ) { - $this->checkoutAgreementsDataProvider = $checkoutAgreementsDataProvider; + $this->agreementCollectionFactory = $agreementCollectionFactory; + $this->storeManager = $storeManager; + $this->scopeConfig = $scopeConfig; } /** @@ -41,8 +61,27 @@ public function resolve( array $value = null, array $args = null ) { - $checkoutAgreementsData = $this->checkoutAgreementsDataProvider->getData(); + if (!$this->scopeConfig->isSetFlag('checkout/options/enable_agreements', ScopeInterface::SCOPE_STORE)) { + return []; + } + + $agreementsCollection = $this->agreementCollectionFactory->create(); + $agreementsCollection->addStoreFilter($this->storeManager->getStore()->getId()); + $agreementsCollection->addFieldToFilter('is_active', 1); - return $checkoutAgreementsData; + $checkoutAgreementData = []; + /** @var AgreementInterface $checkoutAgreement */ + foreach ($agreementsCollection->getItems() as $checkoutAgreement) { + $checkoutAgreementData[] = [ + AgreementInterface::AGREEMENT_ID => $checkoutAgreement->getAgreementId(), + AgreementInterface::CONTENT => $checkoutAgreement->getContent(), + AgreementInterface::NAME => $checkoutAgreement->getName(), + AgreementInterface::CONTENT_HEIGHT => $checkoutAgreement->getContentHeight(), + AgreementInterface::CHECKBOX_TEXT => $checkoutAgreement->getCheckboxText(), + AgreementInterface::IS_HTML => $checkoutAgreement->getIsHtml(), + AgreementInterface::MODE => $checkoutAgreement->getMode(), + ]; + } + return $checkoutAgreementData; } } diff --git a/app/code/Magento/CheckoutAgreementsGraphQl/Model/Resolver/DataProvider/CheckoutAgreements.php b/app/code/Magento/CheckoutAgreementsGraphQl/Model/Resolver/DataProvider/CheckoutAgreements.php deleted file mode 100644 index 2fa3fe9d1284d..0000000000000 --- a/app/code/Magento/CheckoutAgreementsGraphQl/Model/Resolver/DataProvider/CheckoutAgreements.php +++ /dev/null @@ -1,82 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\CheckoutAgreementsGraphQl\Model\Resolver\DataProvider; - -use Magento\CheckoutAgreements\Api\Data\AgreementInterface; -use Magento\CheckoutAgreements\Model\Agreement; -use Magento\CheckoutAgreements\Model\ResourceModel\Agreement\CollectionFactory; -use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Store\Model\ScopeInterface; -use Magento\Store\Model\StoreManagerInterface; - -/** - * Checkout Agreements data provider - */ -class CheckoutAgreements -{ - /** - * @var CollectionFactory - */ - private $agreementCollectionFactory; - - /** - * @var StoreManagerInterface - */ - private $storeManager; - - /** - * @var ScopeConfigInterface - */ - private $scopeConfig; - - /** - * @param CollectionFactory $agreementCollectionFactory - * @param StoreManagerInterface $storeManager - * @param ScopeConfigInterface $scopeConfig - */ - public function __construct( - CollectionFactory $agreementCollectionFactory, - StoreManagerInterface $storeManager, - ScopeConfigInterface $scopeConfig - ) { - $this->agreementCollectionFactory = $agreementCollectionFactory; - $this->storeManager = $storeManager; - $this->scopeConfig = $scopeConfig; - } - - /** - * Get All Active Checkout Agreements Data - * - * @return array - */ - public function getData(): array - { - if (!$this->scopeConfig->isSetFlag('checkout/options/enable_agreements', ScopeInterface::SCOPE_STORE)) { - return []; - } - $agreementsCollection = $this->agreementCollectionFactory->create(); - $agreementsCollection->addStoreFilter($this->storeManager->getStore()->getId()); // TODO: store should be get from query context - $agreementsCollection->addFieldToFilter('is_active', 1); - - $checkoutAgreementData = []; - /** @var Agreement $checkoutAgreement */ - foreach ($agreementsCollection->getItems() as $checkoutAgreement) { - $checkoutAgreementData[] = [ - AgreementInterface::AGREEMENT_ID => $checkoutAgreement->getAgreementId(), - AgreementInterface::CONTENT => $checkoutAgreement->getContent(), - AgreementInterface::NAME => $checkoutAgreement->getName(), - AgreementInterface::CONTENT_HEIGHT => $checkoutAgreement->getContentHeight(), - AgreementInterface::CHECKBOX_TEXT => $checkoutAgreement->getCheckboxText(), - AgreementInterface::IS_HTML => $checkoutAgreement->getIsHtml(), - AgreementInterface::MODE => $checkoutAgreement->getMode(), - ]; - } - - return $checkoutAgreementData; - } -} diff --git a/app/code/Magento/CheckoutAgreementsGraphQl/composer.json b/app/code/Magento/CheckoutAgreementsGraphQl/composer.json index 064e8f7c2ec94..5972d48b35ea9 100644 --- a/app/code/Magento/CheckoutAgreementsGraphQl/composer.json +++ b/app/code/Magento/CheckoutAgreementsGraphQl/composer.json @@ -9,8 +9,7 @@ "magento/module-checkout-agreements": "*" }, "suggest": { - "magento/module-graph-ql": "*", - "magento/module-store-graph-ql": "*" + "magento/module-graph-ql": "*" }, "license": [ "OSL-3.0", 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/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/StoreViewLanguageCorrectSwitchTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml index 2c351a12af72e..d4796b2ef7d43 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml @@ -11,6 +11,7 @@ <test name="StoreViewLanguageCorrectSwitchTest"> <annotations> <features value="Cms"/> + <stories value="Store view language"/> <title value="Check that Store View(language) switches correct"/> <description value="Check that Store View(language) switches correct"/> <severity value="MAJOR"/> 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 22009824452be..8e1e770b01e9d 100644 --- a/app/code/Magento/CmsGraphQl/Model/Resolver/DataProvider/Page.php +++ b/app/code/Magento/CmsGraphQl/Model/Resolver/DataProvider/Page.php @@ -40,6 +40,8 @@ public function __construct( } /** + * Get the page data + * * @param int $pageId * @return array * @throws NoSuchEntityException @@ -55,6 +57,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 6a2e3950f93d0..18a6f1aa95e37 100644 --- a/app/code/Magento/CmsGraphQl/composer.json +++ b/app/code/Magento/CmsGraphQl/composer.json @@ -10,6 +10,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 e8abd2201b886..04d2efa4c7cde 100644 --- a/app/code/Magento/CmsGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CmsGraphQl/etc/schema.graphqls @@ -13,10 +13,10 @@ type StoreConfig @doc(description: "The type contains information about a store type Query { cmsPage ( id: Int @doc(description: "Id 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/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/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/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/StorefrontAddConfigurableProductToTheCartActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontAddConfigurableProductToTheCartActionGroup.xml new file mode 100644 index 0000000000000..9e3935501adee --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontAddConfigurableProductToTheCartActionGroup.xml @@ -0,0 +1,26 @@ +<?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="StorefrontAddConfigurableProductToTheCartActionGroup"> + <arguments> + <argument name="urlKey" type="string"/> + <argument name="productAttribute" type="string"/> + <argument name="productOption" type="string" /> + <argument name="qty" type="string"/> + </arguments> + <amOnPage url="{{urlKey}}.html" stepKey="goToStorefrontPage"/> + <waitForPageLoad stepKey="waitForProductFrontPageToLoad"/> + <selectOption selector="{{StorefrontProductInfoMainSection.productOptionSelect(productAttribute)}}" userInput="{{productOption}}" stepKey="selectOption1"/> + <fillField selector="{{StorefrontProductPageSection.qtyInput}}" userInput="{{qty}}" stepKey="fillProductQuantity"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="clickOnAddToCartButton"/> + <waitForPageLoad stepKey="waitForProductToAddInCart"/> + <seeElement selector="{{StorefrontProductPageSection.successMsg}}" stepKey="seeSuccessSaveMessage"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontCheckCategoryConfigurableProductWithUpdatedPriceActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontCheckCategoryConfigurableProductWithUpdatedPriceActionGroup.xml new file mode 100644 index 0000000000000..5d5e37ecce4ef --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontCheckCategoryConfigurableProductWithUpdatedPriceActionGroup.xml @@ -0,0 +1,21 @@ +<?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="StorefrontCheckCategoryConfigurableProductWithUpdatedPriceActionGroup"> + <arguments> + <argument name="productName" type="string"/> + <argument name="expectedPrice" type="string"/> + </arguments> + <seeElement selector="{{StorefrontCategoryProductSection.ProductTitleByName(productName)}}" stepKey="assertProductName"/> + <see userInput="{{expectedPrice}}" selector="{{StorefrontCategoryProductSection.ProductPriceByName(productName)}}" stepKey="AssertProductPrice"/> + <moveMouseOver selector="{{StorefrontCategoryProductSection.ProductInfoByName(productName)}}" stepKey="moveMouseOverProduct"/> + <seeElement selector="{{StorefrontCategoryProductSection.ProductAddToCartByName(productName)}}" stepKey="AssertAddToCart"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml index a56c6e23b6d8f..2c3e5716d6add 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml @@ -79,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> @@ -91,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/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 24cd9262b6742..0853d22eda7b9 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -15,6 +15,7 @@ <element name="productAttributeOptions1" type="select" selector="#product-options-wrapper div[tabindex='0'] option"/> <element name="productAttributeOptionsSelectButton" type="select" selector="#product-options-wrapper .super-attribute-select"/> <element name="productAttributeOptionsError" type="text" selector="//div[@class='mage-error']"/> + <element name="selectCustomOptionByName" type="radio" selector="//*[@class='options-list nested']//span[contains(text(), '{{value}}')]/../../input" parameterized="true"/> <!-- 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" /> 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/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml index 456be43f80b8d..f6469e060747f 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml @@ -12,6 +12,7 @@ <annotations> <features value="ConfigurableProduct"/> + <stories value="Cancel order"/> <title value="Product qunatity return after order cancel"/> <description value="Check Product qunatity return after order cancel"/> <severity value="CRITICAL"/> 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/ConfigurableProductGraphQl/etc/schema.graphqls b/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls index d4780c5c0867a..45a4323e7f4bc 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls +++ b/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls @@ -41,7 +41,7 @@ type ConfigurableProductOptionsValues @doc(description: "ConfigurableProductOpti input AddConfigurableProductsToCartInput { cart_id: String! - cartItems: [ConfigurableProductCartItemInput!]! + cart_items: [ConfigurableProductCartItemInput!]! } type AddConfigurableProductsToCartOutput { 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/Model/ResourceModel/CustomerRepository.php b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php index ddc0576cb0dae..038526053b872 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php +++ b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php @@ -343,7 +343,7 @@ public function getById($customerId) * Retrieve customers which match a specified criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#CustomerRepositoryInterface to determine + * included. See https://devdocs.magento.com/codelinks/attributes.html#CustomerRepositoryInterface to determine * which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria 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/Data/CustomerData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml index 06c23a2864984..fd8cbcbf89f57 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml @@ -46,6 +46,32 @@ <data key="website_id">0</data> <requiredEntity type="address">US_Address_TX</requiredEntity> </entity> + <entity name="SimpleUsCustomerWithNewCustomerGroup" type="customer"> + <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> + <var entityType="customerGroup" entityKey="id" key="group_id" /> + </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 +229,31 @@ <data key="store_id">0</data> <data key="website_id">0</data> </entity> + <entity name="Retailer_Customer" type="customer"> + <data key="group_id">3</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_CA</requiredEntity> + </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/Page/StorefrontCustomerDashboardPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml index eaca1c820e49e..d4fe9106fbbad 100644 --- a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml @@ -11,5 +11,6 @@ <page name="StorefrontCustomerDashboardPage" url="/customer/account/" area="storefront" module="Magento_Customer"> <section name="StorefrontCustomerDashboardAccountInformationSection" /> <section name="StorefrontCustomerSidebarSection"/> + <section name="StorefrontMinicartSection"/> </page> </pages> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml index 71e3e673477d2..37c2f0381ee05 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml @@ -31,5 +31,6 @@ <element name="firstNameRequiredMessage" type="text" selector="//input[@name='customer[firstname]']/../label[contains(.,'This is a required field.')]"/> <element name="lastNameRequiredMessage" type="text" selector="//input[@name='customer[lastname]']/../label[contains(.,'This is a required field.')]"/> <element name="emailRequiredMessage" type="text" selector="//input[@name='customer[email]']/../label[contains(.,'This is a required field.')]"/> + <element name="disabledGroup" type="text" selector="//div[@class='admin__action-group-wrap admin__action-multiselect-wrap action-select-wrap _disabled']"/> </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/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml index f39394ef312e4..11a6fbff8a405 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml @@ -9,6 +9,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AllowedCountriesRestrictionApplyOnBackendTest"> <annotations> + <stories value="Country filter"/> <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"/> 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/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml index 3121bd0da9d2d..5d0eec935e192 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml @@ -11,6 +11,7 @@ <test name="StorefrontResetCustomerPasswordFailedTest"> <annotations> <features value="Customer"/> + <stories value="Reset password"/> <title value="Customer tries to reset password several times"/> <description value="Customer tries to reset password several times"/> <severity value="CRITICAL" /> 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/base/ui_component/customer_form.xml b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml index 7e6b7bbe9cd09..5fb8b17dbb8c5 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"> @@ -118,7 +129,7 @@ </validation> <dataType>number</dataType> <tooltip> - <link>http://docs.magento.com/m2/ce/user_guide/configuration/scope.html</link> + <link>https://docs.magento.com/m2/ce/user_guide/configuration/scope.html</link> <description translate="true">If your Magento installation has multiple websites, you can edit the scope to associate the customer with a specific site.</description> </tooltip> <imports> diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/GenerateCustomerToken.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/GenerateCustomerToken.php index 1bd77fe1cde8f..2a7cb8a734a50 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/GenerateCustomerToken.php +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/GenerateCustomerToken.php @@ -44,11 +44,11 @@ public function resolve( array $value = null, array $args = null ) { - if (!isset($args['email'])) { + if (!isset($args['email']) || empty($args['email'])) { throw new GraphQlInputException(__('Specify the "email" value.')); } - if (!isset($args['password'])) { + if (!isset($args['password']) || empty($args['password'])) { throw new GraphQlInputException(__('Specify the "password" value.')); } 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/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/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/ActionGroup/AdminAddDownloadableLinkInformationActionGroup.xml b/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/AdminAddDownloadableLinkInformationActionGroup.xml new file mode 100644 index 0000000000000..37c6936cfb28a --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/AdminAddDownloadableLinkInformationActionGroup.xml @@ -0,0 +1,21 @@ +<?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"> + <!-- Add information before create downloadable link --> + <actionGroup name="AdminAddDownloadableLinkInformationActionGroup"> + <arguments> + <argument name="downloadableData" type="entity" defaultValue="downloadableData"/> + </arguments> + <click selector="{{AdminProductDownloadableSection.sectionHeader}}" stepKey="openDownloadableSection"/> + <checkOption selector="{{AdminProductDownloadableSection.isDownloadableProduct}}" stepKey="checkOptionIsDownloadable" after="openDownloadableSection"/> + <fillField userInput="{{downloadableData.link_title}}" selector="{{AdminProductDownloadableSection.linksTitleInput}}" stepKey="fillLinkTitle" after="checkOptionIsDownloadable"/> + <fillField userInput="{{downloadableData.sample_title}}" selector="{{AdminProductDownloadableSection.samplesTitleInput}}" stepKey="fillSampleTitle" after="fillLinkTitle"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml index c836b0d90d13d..59651383febf2 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml @@ -19,6 +19,17 @@ <data key="status">1</data> <data key="urlKey" unique="suffix">downloadableproduct</data> </entity> + <entity name="DownloadableProductOutOfStock" type="product"> + <data key="sku" unique="suffix">downloadableoutofstockproduct</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">0</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> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Section/AdminProductDownloadableSection.xml b/app/code/Magento/Downloadable/Test/Mftf/Section/AdminProductDownloadableSection.xml index 274f26498958b..074ed55781103 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Section/AdminProductDownloadableSection.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Section/AdminProductDownloadableSection.xml @@ -10,6 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductDownloadableSection"> <element name="sectionHeader" type="button" selector="div[data-index='downloadable']" timeout="30" /> + <element name="sectionLinkGrid" type="text" selector="//div[@data-index='link']" timeout="30"/> <element name="isDownloadableProduct" type="input" selector="input[name='is_downloadable']" /> <element name="linksTitleInput" type="input" selector="input[name='product[links_title]']" /> <element name="isLinksPurchasedSeparately" type="input" selector="input[name='product[links_purchased_separately]']" /> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontDownloadableProductSection.xml b/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontDownloadableProductSection.xml new file mode 100644 index 0000000000000..a1db2d4d94941 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontDownloadableProductSection.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="StorefrontDownloadableProductSection"> + <element name="downloadableLinkBlock" type="text" selector="//div[contains(@class, 'field downloads required')]//span[text()='Downloadable Links']"/> + <element name="downloadableLinkLabel" type="text" selector="//label[contains(., '{{title}}')]" parameterized="true" timeout="30"/> + <element name="downloadableLinkByTitle" type="input" selector="//*[@id='downloadable-links-list']/*[contains(.,'{{title}}')]//input" parameterized="true" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductAndAssignItToCustomStoreTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductAndAssignItToCustomStoreTest.xml new file mode 100644 index 0000000000000..5d7e4518525f7 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductAndAssignItToCustomStoreTest.xml @@ -0,0 +1,93 @@ +<?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="AdminCreateDownloadableProductAndAssignItToCustomStoreTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create Downloadable Product"/> + <title value="Create downloadable product and assign it to custom store test"/> + <description value="Admin should be able to create downloadable and assign it to custom website"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14516"/> + <group value="Downloadable"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete created downloadable product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Delete store view --> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteCreatedStoreView"/> + + <!-- Log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create store view --> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView"/> + + <!-- Create Downloadable product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad stepKey="waitForProductGridPageLoad"/> + <actionGroup ref="GoToSpecifiedCreateProductPage" stepKey="createProduct"> + <argument name="productType" value="downloadable"/> + </actionGroup> + + <!-- Fill downloadable product values --> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillDownloadableProductForm"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Add downloadable product to category --> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createCategory.name$$]" stepKey="fillCategory"/> + + <!-- Fill downloadable link information before the creation link --> + <actionGroup ref="AdminAddDownloadableLinkInformationActionGroup" stepKey="addDownloadableLinkInformation"/> + + <!-- Links can be purchased separately --> + <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkOptionPurchaseSeparately"/> + + <!-- Add downloadable link --> + <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addDownloadableProductLink"> + <argument name="link" value="downloadableLinkWithMaxDownloads"/> + </actionGroup> + + <!-- Save product --> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!-- Switch default store view on store view created below --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnStorefrontPage"/> + <waitForPageLoad stepKey="waitForStorefrontPageLoad"/> + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="SwitchStoreView"> + <argument name="storeView" value="customStore"/> + </actionGroup> + + <!-- Assert product in custom store --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- See product link in storefront product page in custom store --> + <scrollTo selector="{{StorefrontDownloadableProductSection.downloadableLinkBlock}}" stepKey="scrollToLinksInStorefront"/> + <seeElement stepKey="seeDownloadableLink" selector="{{StorefrontDownloadableProductSection.downloadableLinkLabel(downloadableLinkWithMaxDownloads.title)}}"/> + </test> +</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithCustomOptionsTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithCustomOptionsTest.xml new file mode 100644 index 0000000000000..0ae2c1254be01 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithCustomOptionsTest.xml @@ -0,0 +1,112 @@ +<?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="AdminCreateDownloadableProductWithCustomOptionsTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create Downloadable Product"/> + <title value="Create downloadable product with custom options test"/> + <description value="Admin should be able to create downloadable product with custom options"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14508"/> + <group value="Downloadable"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete created downloadable product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create Downloadable product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad stepKey="waitForProductGridPageLoad"/> + <actionGroup ref="GoToSpecifiedCreateProductPage" stepKey="createProduct"> + <argument name="productType" value="downloadable"/> + </actionGroup> + + <!-- Fill downloadable product values --> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillDownloadableProductForm"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Add downloadable product to category --> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createCategory.name$$]" stepKey="fillCategory"/> + + <!-- Open custom option panel --> + <click selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" stepKey="openCustomizableOptions"/> + <waitForPageLoad stepKey="waitForCustomOptionsOpen"/> + + <!-- Create a first custom option with 2 values --> + <actionGroup ref="AdminCreateCustomDropDownOptionsActionGroup" stepKey="createFirstCustomOption"> + <argument name="customOptionName" value="{{ProductOptionDropDown.title}}"/> + <argument name="firstOption" value="ProductOptionValueDropdown1"/> + <argument name="secondOption" value="ProductOptionValueDropdown2"/> + </actionGroup> + + <!-- Create a second custom option with 2 values --> + <actionGroup ref="AdminCreateCustomDropDownOptionsActionGroup" stepKey="createSecondCustomOption"> + <argument name="customOptionName" value="{{ProductOptionDropDownWithLongValuesTitle.title}}"/> + <argument name="firstOption" value="ProductOptionValueDropdown1"/> + <argument name="secondOption" value="ProductOptionValueDropdown2"/> + </actionGroup> + + <!-- Fill downloadable link information before the creation link --> + <actionGroup ref="AdminAddDownloadableLinkInformationActionGroup" stepKey="addDownloadableLinkInformation"/> + + <!-- Links can be purchased separately --> + <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkOptionPurchaseSeparately"/> + + <!-- Add downloadable link --> + <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addDownloadableProductLink"> + <argument name="link" value="downloadableLinkWithMaxDownloads"/> + </actionGroup> + + <!-- Save product --> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!-- Go to storefront category page --> + <amOnPage url="$$createCategory.name$$.html" stepKey="amOnCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + + <!-- Assert product in storefront category page --> + <actionGroup ref="StorefrontCheckProductPriceInCategoryActionGroup" stepKey="StorefrontCheckCategorySimpleProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Assert product in storefront product page --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Assert product custom options on product page --> + <scrollTo selector="{{StorefrontDownloadableProductSection.downloadableLinkBlock}}" stepKey="scrollToLinksInStorefront"/> + <seeElement stepKey="seeFirstOptionName" selector="{{StorefrontProductInfoMainSection.customOptionLabel(ProductOptionDropDown.title)}}"/> + <seeElement stepKey="seeSecondOptionName" selector="{{StorefrontProductInfoMainSection.customOptionLabel(ProductOptionDropDownWithLongValuesTitle.title)}}"/> + + <!-- Assert downloadable links data --> + <scrollTo selector="{{StorefrontDownloadableProductSection.downloadableLinkBlock}}" stepKey="scrollToLinks"/> + <seeElement stepKey="seeDownloadableLink" selector="{{StorefrontDownloadableProductSection.downloadableLinkLabel(downloadableLinkWithMaxDownloads.title)}}"/> + </test> +</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithDefaultSetLinksTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithDefaultSetLinksTest.xml new file mode 100644 index 0000000000000..eadefabb8bbcb --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithDefaultSetLinksTest.xml @@ -0,0 +1,112 @@ +<?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="AdminCreateDownloadableProductWithLinkTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create Downloadable Product"/> + <title value="Create downloadable product with default set links"/> + <description value="Admin should be able to create downloadable product with default set links"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14507"/> + <group value="Downloadable"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete created downloadable product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create downloadable product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad stepKey="waitForProductGridPageLoad"/> + <actionGroup ref="GoToSpecifiedCreateProductPage" stepKey="createProduct"> + <argument name="productType" value="downloadable"/> + </actionGroup> + + <!-- Fill downloadable product values --> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillDownloadableProductForm"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Add downloadable product to category --> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createCategory.name$$]" stepKey="fillCategory"/> + + <!-- Fill downloadable link information before the creation link --> + <actionGroup ref="AdminAddDownloadableLinkInformationActionGroup" stepKey="addDownloadableLinkInformation"/> + + <!-- Links can be purchased separately --> + <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkOptionPurchaseSeparately"/> + + <!-- Add first downloadable link --> + <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addFirstDownloadableProductLink"> + <argument name="link" value="downloadableLinkWithMaxDownloads"/> + </actionGroup> + + <!-- Add second downloadable link --> + <actionGroup ref="addDownloadableProductLink" stepKey="addSecondDownloadableProductLink"> + <argument name="link" value="downloadableLink"/> + </actionGroup> + + <!-- Save product --> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!-- Find downloadable product in grid --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPage"/> + <waitForPageLoad stepKey="waitForAdminProductPageLoad"/> + <actionGroup ref="filterProductGridBySku" stepKey="findCreatedConfigurableProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + <click selector="{{AdminProductGridSection.firstRow}}" stepKey="clickOnProduct"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + + <!-- Assert downloadable links in product form --> + <scrollTo selector="{{AdminProductDownloadableSection.sectionLinkGrid}}" stepKey="scrollToLinks"/> + <seeElement selector="{{AdminProductDownloadableSection.addLinkTitleInput('0')}}" stepKey="seeFirstLinkTitle"/> + <seeElement selector="{{AdminProductDownloadableSection.addLinkTitleInput('1')}}" stepKey="seeSecondLinkTitle"/> + + <!-- Go to storefront category page --> + <amOnPage url="$$createCategory.name$$.html" stepKey="amOnCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + + <!-- Assert product in storefront category page --> + <actionGroup ref="StorefrontCheckProductPriceInCategoryActionGroup" stepKey="StorefrontCheckCategorySimpleProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Assert product in storefront product page --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- See product links in storefront product page --> + <scrollTo selector="{{StorefrontDownloadableProductSection.downloadableLinkBlock}}" stepKey="scrollToLinksInStorefront"/> + <seeElement stepKey="seeFirstDownloadableLink" selector="{{StorefrontDownloadableProductSection.downloadableLinkLabel(downloadableLinkWithMaxDownloads.title)}}"/> + <seeElement stepKey="seeSecondDownloadableLink" selector="{{StorefrontDownloadableProductSection.downloadableLinkLabel(downloadableLink.title)}}"/> + + <!-- Assert product in stock --> + <see selector="{{StorefrontProductInfoMainSection.productStockStatus}}" userInput="In Stock" stepKey="seeProductStatusInStoreFront"/> + </test> +</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithGroupPriceTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithGroupPriceTest.xml new file mode 100644 index 0000000000000..8bd4305e6b358 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithGroupPriceTest.xml @@ -0,0 +1,109 @@ +<?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="AdminCreateDownloadableProductWithGroupPriceTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create Downloadable Product"/> + <title value="Create downloadable product with group price test"/> + <description value="Admin should be able to create downloadable product with group price"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14514"/> + <group value="Downloadable"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete created downloadable product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="ApiDownloadableProduct"/> + </actionGroup> + + <!-- Log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create downloadable product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad stepKey="waitForProductGridPageLoad"/> + <actionGroup ref="GoToSpecifiedCreateProductPage" stepKey="createProduct"> + <argument name="productType" value="downloadable"/> + </actionGroup> + + <!-- Add group price to product --> + <actionGroup ref="AdminAddAdvancedPricingToTheProductActionGroup" stepKey="addCustomerGroupPrice"> + <argument name="index" value="0"/> + <argument name="groupPrice" value="simpleGroupPrice"/> + </actionGroup> + + <!-- Fill downloadable product values --> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillDownloadableProductForm"> + <argument name="product" value="ApiDownloadableProduct"/> + </actionGroup> + + <!-- Add downloadable product to category --> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createCategory.name$$]" stepKey="fillCategory"/> + + <!-- Fill downloadable link information before the creation link --> + <actionGroup ref="AdminAddDownloadableLinkInformationActionGroup" stepKey="addDownloadableLinkInformation"/> + + <!-- Links can be purchased separately --> + <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkOptionPurchaseSeparately"/> + + <!-- Add first downloadable link --> + <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addFirstDownloadableProductLink"> + <argument name="link" value="downloadableLinkWithMaxDownloads"/> + </actionGroup> + + <!-- Add second downloadable link --> + <actionGroup ref="addDownloadableProductLink" stepKey="addSecondDownloadableProductLink"> + <argument name="link" value="downloadableLink"/> + </actionGroup> + + <!-- Save product --> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!-- Find downloadable product in grid --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPage"/> + <waitForPageLoad stepKey="waitForAdminProductPageLoad"/> + <actionGroup ref="filterProductGridBySku" stepKey="findCreatedConfigurableProduct"> + <argument name="product" value="ApiDownloadableProduct"/> + </actionGroup> + <click selector="{{AdminProductGridSection.firstRow}}" stepKey="clickOnProduct"/> + <waitForPageLoad stepKey="waitForProductFormPageLoad"/> + + <!-- Assert downloadable links in product form --> + <scrollTo selector="{{AdminProductDownloadableSection.sectionLinkGrid}}" stepKey="scrollToLinks"/> + <seeElement selector="{{AdminProductDownloadableSection.addLinkTitleInput('0')}}" stepKey="seeFirstLinkTitle"/> + <seeElement selector="{{AdminProductDownloadableSection.addLinkTitleInput('1')}}" stepKey="seeSecondLinkTitle"/> + + <!-- Assert product in storefront product page --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> + <argument name="product" value="ApiDownloadableProduct"/> + </actionGroup> + + <!-- Assert group price in storefront product page --> + <see selector="{{StorefrontProductInfoMainSection.updatedPrice}}" userInput="$90.00" stepKey="assertGroupPrice"/> + + <!-- See product link in storefront product page --> + <scrollTo selector="{{StorefrontDownloadableProductSection.downloadableLinkBlock}}" stepKey="scrollToLinksInStorefront"/> + <seeElement stepKey="seeFirstDownloadableLink" selector="{{StorefrontDownloadableProductSection.downloadableLinkLabel(downloadableLinkWithMaxDownloads.title)}}"/> + <seeElement stepKey="seeSecondDownloadableLink" selector="{{StorefrontDownloadableProductSection.downloadableLinkLabel(downloadableLink.title)}}"/> + </test> +</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithLinkTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithLinkTest.xml new file mode 100644 index 0000000000000..d8bd641e84e55 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithLinkTest.xml @@ -0,0 +1,101 @@ +<?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="AdminCreateDownloadableProductWithDefaultSetLinksTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create Downloadable Product"/> + <title value="Create Downloadable Product with link"/> + <description value="Admin should be able to create downloadable product with one simple link"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14506"/> + <group value="Downloadable"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete created downloadable product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create downloadable product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad stepKey="waitForProductGridPageLoad"/> + <actionGroup ref="GoToSpecifiedCreateProductPage" stepKey="createProduct"> + <argument name="productType" value="downloadable"/> + </actionGroup> + + <!-- Fill downloadable product values --> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillDownloadableProductForm"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Add downloadable product to category --> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createCategory.name$$]" stepKey="fillCategory"/> + + <!-- Fill downloadable link information before the creation link --> + <actionGroup ref="AdminAddDownloadableLinkInformationActionGroup" stepKey="addDownloadableLinkInformation"/> + + <!-- Links can be purchased separately --> + <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkOptionPurchaseSeparately"/> + + <!-- Add downloadable link --> + <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addDownloadableProductLink"> + <argument name="link" value="downloadableLinkWithMaxDownloads"/> + </actionGroup> + + <!-- Save product --> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!-- Assert product in storefront category page --> + <amOnPage url="$$createCategory.name$$.html" stepKey="amOnCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + <actionGroup ref="StorefrontCheckProductPriceInCategoryActionGroup" stepKey="StorefrontCheckCategorySimpleProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Assert product in storefront product page --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Assert product price in storefront product page --> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="{{DownloadableProduct.price}}" stepKey="assertProductPrice"/> + + <!-- Select product link in storefront product page--> + <scrollTo selector="{{StorefrontDownloadableProductSection.downloadableLinkBlock}}" stepKey="scrollToLinks"/> + <click selector="{{StorefrontDownloadableProductSection.downloadableLinkByTitle(downloadableLinkWithMaxDownloads.title)}}" stepKey="selectProductLink"/> + + <!-- Add product with selected link to the cart --> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="addProductToCart"> + <argument name="product" value="DownloadableProduct"/> + <argument name="productCount" value="1"/> + </actionGroup> + + <!-- Assert product price in cart --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="openShoppingCartPage"/> + <waitForPageLoad stepKey="waitForShoppingCartPageLoad"/> + <see selector="{{CheckoutCartProductSection.ProductPriceByName(DownloadableProduct.name)}}" userInput="$51.99" stepKey="assertProductPriceInCart"/> + </test> +</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithManageStockTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithManageStockTest.xml new file mode 100644 index 0000000000000..3efd4b8ab276f --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithManageStockTest.xml @@ -0,0 +1,109 @@ +<?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="AdminCreateDownloadableProductWithManageStockTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create Downloadable Product"/> + <title value="Create downloadable product with manage stock test"/> + <description value="Admin should be able to create downloadable product with manage stock"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14510"/> + <group value="Downloadable"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete created downloadable product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create downloadable product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad stepKey="waitForProductGridPageLoad"/> + <actionGroup ref="GoToSpecifiedCreateProductPage" stepKey="createProduct"> + <argument name="productType" value="downloadable"/> + </actionGroup> + + <!-- Fill downloadable product values --> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillDownloadableProductForm"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Add advanced inventory data to downloadable product --> + <actionGroup ref="AdminClickOnAdvancedInventoryLinkActionGroup" stepKey="clickOnAdvancedInventoryLink"/> + + <actionGroup ref="AdminFillAdvancedInventoryQtyActionGroup" stepKey="fillAdvancedInventoryQty"> + <argument name="qty" value="{{DownloadableProduct.quantity}}"/> + </actionGroup> + + <actionGroup ref="AdminFillAdvancedInventoryOutOfStockThresholdActionGroup" stepKey="fillAdvancedInventoryOutOfStockThreshold"> + <argument name="qty" value="{{DownloadableProduct.quantity}}"/> + </actionGroup> + + <actionGroup ref="AdminSubmitAdvancedInventoryFormActionGroup" stepKey="clickDoneBtn"/> + + <!-- Add downloadable product to category --> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createCategory.name$$]" stepKey="fillCategory"/> + + <!-- Fill downloadable link information before the creation link --> + <actionGroup ref="AdminAddDownloadableLinkInformationActionGroup" stepKey="addDownloadableLinkInformation"/> + + <!-- Links can be purchased separately --> + <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkOptionPurchaseSeparately"/> + + <!-- Add first downloadable link --> + <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addFirstDownloadableProductLink"> + <argument name="link" value="downloadableLinkWithMaxDownloads"/> + </actionGroup> + + <!-- Add second downloadable link --> + <actionGroup ref="addDownloadableProductLink" stepKey="addSecondDownloadableProductLink"> + <argument name="link" value="downloadableLink"/> + </actionGroup> + + <!-- Save product --> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!-- Find downloadable product in grid --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPage"/> + <waitForPageLoad stepKey="waitForAdminProductPageLoad"/> + <actionGroup ref="filterProductGridBySku" stepKey="findCreatedConfigurableProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + <click selector="{{AdminProductGridSection.firstRow}}" stepKey="clickOnProduct"/> + <waitForPageLoad stepKey="waitForProductFormPageLoad"/> + + <!-- Assert downloadable links in product form --> + <scrollTo selector="{{AdminProductDownloadableSection.sectionLinkGrid}}" stepKey="scrollToLinks"/> + <seeInField selector="{{AdminProductDownloadableSection.addLinkTitleInput('0')}}" userInput="{{downloadableLinkWithMaxDownloads.title}}" stepKey="seeFirstLinkTitle"/> + <seeInField selector="{{AdminProductDownloadableSection.addLinkTitleInput('1')}}" userInput="{{downloadableLink.title}}" stepKey="seeSecondLinkTitle"/> + + <!-- Assert product is out of stock in store front --> + <amOnPage url="{{DownloadableProduct.urlKey}}.html" stepKey="navigateToProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{DownloadableProduct.name}}" stepKey="seeProductNameInStoreFront"/> + <see selector="{{StorefrontProductInfoMainSection.productStockStatus}}" userInput="OUT OF STOCK" stepKey="seeProductStatusInStoreFront"/> + </test> +</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithOutOfStockStatusTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithOutOfStockStatusTest.xml new file mode 100644 index 0000000000000..07f7c40bb3560 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithOutOfStockStatusTest.xml @@ -0,0 +1,95 @@ +<?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="AdminCreateDownloadableProductWithOutOfStockStatusTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create Downloadable Product"/> + <title value="Create downloadable product with out of stock status test"/> + <description value="Admin should be able to create downloadable product with out of stock status"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14509"/> + <group value="Downloadable"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete created downloadable product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="DownloadableProductOutOfStock"/> + </actionGroup> + + <!-- Log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create Downloadable product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad stepKey="waitForProductGridPageLoad"/> + <actionGroup ref="GoToSpecifiedCreateProductPage" stepKey="createProduct"> + <argument name="productType" value="downloadable"/> + </actionGroup> + + <!-- Fill downloadable product values --> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillDownloadableProductForm"> + <argument name="product" value="DownloadableProductOutOfStock"/> + </actionGroup> + + <!-- Add downloadable product to category --> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createCategory.name$$]" stepKey="fillCategory"/> + + <!-- Fill downloadable link information before the creation link --> + <actionGroup ref="AdminAddDownloadableLinkInformationActionGroup" stepKey="addDownloadableLinkInformation"/> + + <!-- Links can be purchased separately --> + <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkOptionPurchaseSeparately"/> + + <!-- Add first downloadable link --> + <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addFirstDownloadableProductLink"> + <argument name="link" value="downloadableLinkWithMaxDownloads"/> + </actionGroup> + + <!-- Add second downloadable link --> + <actionGroup ref="addDownloadableProductLink" stepKey="addSecondDownloadableProductLink"> + <argument name="link" value="downloadableLink"/> + </actionGroup> + + <!-- Save product --> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!-- Assert product is out of stock --> + <amOnPage url="{{DownloadableProductOutOfStock.urlKey}}.html" stepKey="navigateToProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productStockStatus}}" userInput="OUT OF STOCK" stepKey="seeProductStatusInStoreFront"/> + + <!-- Find downloadable product in grid --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPage"/> + <waitForPageLoad stepKey="waitForAdminProductPageLoad"/> + <actionGroup ref="filterProductGridBySku" stepKey="findCreatedConfigurableProduct"> + <argument name="product" value="DownloadableProductOutOfStock"/> + </actionGroup> + <click selector="{{AdminProductGridSection.firstRow}}" stepKey="clickOnProduct"/> + <waitForPageLoad stepKey="waitForProductFormPageLoad"/> + + <!-- Assert downloadable links in product form --> + <scrollTo selector="{{AdminProductDownloadableSection.sectionLinkGrid}}" stepKey="scrollToLinks"/> + <seeInField selector="{{AdminProductDownloadableSection.addLinkTitleInput('0')}}" userInput="{{downloadableLinkWithMaxDownloads.title}}" stepKey="seeFirstLinkTitle"/> + <seeInField selector="{{AdminProductDownloadableSection.addLinkTitleInput('1')}}" userInput="{{downloadableLink.title}}" stepKey="seeSecondLinkTitle"/> + </test> +</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithSpecialPriceTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithSpecialPriceTest.xml new file mode 100644 index 0000000000000..275e72b2ec8cb --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithSpecialPriceTest.xml @@ -0,0 +1,108 @@ +<?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="AdminCreateDownloadableProductWithSpecialPriceTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create Downloadable Product"/> + <title value="Create downloadable product with special price test"/> + <description value="Admin should be able to create downloadable product with special price"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14513"/> + <group value="Downloadable"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete created downloadable product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="ApiDownloadableProduct"/> + </actionGroup> + + <!-- Log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create downloadable product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad stepKey="waitForProductGridPageLoad"/> + <actionGroup ref="GoToSpecifiedCreateProductPage" stepKey="createProduct"> + <argument name="productType" value="downloadable"/> + </actionGroup> + + <!-- Add special price to product --> + <actionGroup ref="AddSpecialPriceToProductActionGroup" stepKey="setSpecialPriceToCreatedProduct" after="createProduct"> + <argument name="price" value="{{specialProductPrice.price}}"/> + </actionGroup> + + <!-- Fill downloadable product values --> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillDownloadableProductForm"> + <argument name="product" value="ApiDownloadableProduct"/> + </actionGroup> + + <!-- Add downloadable product to category --> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createCategory.name$$]" stepKey="fillCategory"/> + + <!-- Fill downloadable link information before the creation link --> + <actionGroup ref="AdminAddDownloadableLinkInformationActionGroup" stepKey="addDownloadableLinkInformation"/> + + <!-- Links can be purchased separately --> + <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkOptionPurchaseSeparately"/> + + <!-- Add first downloadable link --> + <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addFirstDownloadableProductLink"> + <argument name="link" value="downloadableLinkWithMaxDownloads"/> + </actionGroup> + + <!-- Add second downloadable link --> + <actionGroup ref="addDownloadableProductLink" stepKey="addSecondDownloadableProductLink"> + <argument name="link" value="downloadableLink"/> + </actionGroup> + + <!-- Save product --> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!-- Find downloadable product in grid --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPage"/> + <waitForPageLoad stepKey="waitForAdminProductPageLoad"/> + <actionGroup ref="filterProductGridBySku" stepKey="findCreatedConfigurableProduct"> + <argument name="product" value="ApiDownloadableProduct"/> + </actionGroup> + <click selector="{{AdminProductGridSection.firstRow}}" stepKey="clickOnProduct"/> + <waitForPageLoad stepKey="waitForProductFormPageLoad"/> + + <!-- Assert downloadable links in product form --> + <scrollTo selector="{{AdminProductDownloadableSection.sectionLinkGrid}}" stepKey="scrollToLinks"/> + <seeElement selector="{{AdminProductDownloadableSection.addLinkTitleInput('0')}}" stepKey="seeFirstLinkTitle"/> + <seeElement selector="{{AdminProductDownloadableSection.addLinkTitleInput('1')}}" stepKey="seeSecondLinkTitle"/> + + <!-- Assert product in storefront product page --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> + <argument name="product" value="ApiDownloadableProduct"/> + </actionGroup> + + <!-- Assert special price in storefront product page --> + <see selector="{{StorefrontProductInfoMainSection.specialPriceValue}}" userInput="$99.99" stepKey="assertSpecialPrice"/> + + <!-- See product link in storefront product page --> + <scrollTo selector="{{StorefrontDownloadableProductSection.downloadableLinkBlock}}" stepKey="scrollToLinksInStorefront"/> + <seeElement stepKey="seeFirstDownloadableLink" selector="{{StorefrontDownloadableProductSection.downloadableLinkLabel(downloadableLinkWithMaxDownloads.title)}}"/> + <seeElement stepKey="seeSecondDownloadableLink" selector="{{StorefrontDownloadableProductSection.downloadableLinkLabel(downloadableLink.title)}}"/> + </test> +</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceText.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceText.xml new file mode 100644 index 0000000000000..ca4e560506ad0 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceText.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="AdminCreateDownloadableProductWithTierPriceText" extends="AdminCreateDownloadableProductWithGroupPriceTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create Downloadable Product"/> + <title value="Create downloadable product with tier price test"/> + <description value="Admin should be able to create downloadable with tier price"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14515"/> + <group value="Downloadable"/> + <group value="mtf_migrated"/> + </annotations> + <remove keyForRemoval="addCustomerGroupPrice"/> + + <!-- Add tier price to product --> + <actionGroup ref="AdminAddAdvancedPricingToTheProductActionGroup" stepKey="addCustomerTierPrice" after="createProduct"> + <argument name="index" value="0"/> + <argument name="groupPrice" value="tierProductPrice"/> + </actionGroup> + + <remove keyForRemoval="assertGroupPrice"/> + + <!-- Assert tier price in storefront product page --> + <see selector="{{StorefrontProductInfoMainSection.tierPriceText}}" userInput="Buy {{tierProductPrice.quantity}} for ${{tierProductPrice.price}} each and save 27%" stepKey="assertTierPriceText"/> + </test> +</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithoutFillingQuantityAndStockTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithoutFillingQuantityAndStockTest.xml new file mode 100644 index 0000000000000..f326a047c32b5 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithoutFillingQuantityAndStockTest.xml @@ -0,0 +1,101 @@ +<?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="AdminCreateDownloadableProductWithoutFillingQuantityAndStockTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create Downloadable Product"/> + <title value="Create downloadable product without filling quantity and stock test"/> + <description value="Admin should be able to create downloadable product without filling quantity and stock"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14512"/> + <group value="Downloadable"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete created downloadable product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create downloadable product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad stepKey="waitForProductGridPageLoad"/> + <actionGroup ref="GoToSpecifiedCreateProductPage" stepKey="createProduct"> + <argument name="productType" value="downloadable"/> + </actionGroup> + + <!-- Fill downloadable product values --> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillDownloadableProductForm"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <clearField selector="{{AdminProductFormSection.productQuantity}}" stepKey="clearProductQty"/> + + <!-- Add downloadable product to category --> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createCategory.name$$]" stepKey="fillCategory"/> + + <!-- Fill downloadable link information before the creation link --> + <actionGroup ref="AdminAddDownloadableLinkInformationActionGroup" stepKey="addDownloadableLinkInformation"/> + + <!-- Links can be purchased separately --> + <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkOptionPurchaseSeparately"/> + + <!-- Add first downloadable link --> + <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addFirstDownloadableProductLink"> + <argument name="link" value="downloadableLinkWithMaxDownloads"/> + </actionGroup> + + <!-- Add second downloadable link --> + <actionGroup ref="addDownloadableProductLink" stepKey="addSecondDownloadableProductLink"> + <argument name="link" value="downloadableLink"/> + </actionGroup> + + <!-- Save product --> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!-- Find downloadable product in grid --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPage"/> + <waitForPageLoad stepKey="waitForAdminProductPageLoad"/> + <actionGroup ref="filterProductGridBySku" stepKey="findCreatedConfigurableProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + <click selector="{{AdminProductGridSection.firstRow}}" stepKey="clickOnProduct"/> + <waitForPageLoad stepKey="waitForProductFormPageLoad"/> + + <!-- Assert downloadable links in product form --> + <scrollTo selector="{{AdminProductDownloadableSection.sectionLinkGrid}}" stepKey="scrollToLinks"/> + <seeInField selector="{{AdminProductDownloadableSection.addLinkTitleInput('0')}}" userInput="{{downloadableLinkWithMaxDownloads.title}}" stepKey="seeFirstLinkTitle"/> + <seeInField selector="{{AdminProductDownloadableSection.addLinkTitleInput('1')}}" userInput="{{downloadableLink.title}}" stepKey="seeSecondLinkTitle"/> + + + <!-- See stock status--> + <seeOptionIsSelected selector="{{AdminProductFormSection.productStockStatus}}" userInput="Out of Stock" stepKey="seeStockStatus"/> + + <!-- Assert product is out of stock --> + <amOnPage url="{{DownloadableProduct.urlKey}}.html" stepKey="navigateToProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productStockStatus}}" userInput="OUT OF STOCK" stepKey="seeProductStatusInStoreFront"/> + </test> +</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithoutTaxClassIdTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithoutTaxClassIdTest.xml new file mode 100644 index 0000000000000..8e33a082d0ba2 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithoutTaxClassIdTest.xml @@ -0,0 +1,103 @@ +<?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="AdminCreateDownloadableProductWithoutTaxClassIdTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create Downloadable Product"/> + <title value="Create downloadable product without tax class id test"/> + <description value="Admin should be able to create downloadable product without tax class id"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14511"/> + <group value="Downloadable"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete created downloadable product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create downloadable product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad stepKey="waitForProductGridPageLoad"/> + <actionGroup ref="GoToSpecifiedCreateProductPage" stepKey="createProduct"> + <argument name="productType" value="downloadable"/> + </actionGroup> + + <!-- Fill downloadable product values --> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillDownloadableProductForm"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Tax class id = "none" --> + <selectOption selector="{{AdminProductFormBundleSection.taxClassDropDown}}" userInput="None" stepKey="taxClassDropDown"/> + + <!-- Add downloadable product to category --> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createCategory.name$$]" stepKey="fillCategory"/> + + <!-- Fill downloadable link information before the creation link --> + <actionGroup ref="AdminAddDownloadableLinkInformationActionGroup" stepKey="addDownloadableLinkInformation"/> + + <!-- Links can be purchased separately --> + <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkOptionPurchaseSeparately"/> + + <!-- Add first downloadable link --> + <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addFirstDownloadableProductLink"> + <argument name="link" value="downloadableLinkWithMaxDownloads"/> + </actionGroup> + + <!-- Add second downloadable link --> + <actionGroup ref="addDownloadableProductLink" stepKey="addSecondDownloadableProductLink"> + <argument name="link" value="downloadableLink"/> + </actionGroup> + + <!-- Save product --> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!-- Find downloadable product in grid --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPage"/> + <waitForPageLoad stepKey="waitForAdminProductPageLoad"/> + <actionGroup ref="filterProductGridBySku" stepKey="findCreatedConfigurableProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + <click selector="{{AdminProductGridSection.firstRow}}" stepKey="clickOnProduct"/> + <waitForPageLoad stepKey="waitForProductFormPageLoad"/> + + <!-- Assert downloadable links in product form --> + <scrollTo selector="{{AdminProductDownloadableSection.sectionLinkGrid}}" stepKey="scrollToLinks"/> + <seeInField selector="{{AdminProductDownloadableSection.addLinkTitleInput('0')}}" userInput="{{downloadableLinkWithMaxDownloads.title}}" stepKey="seeFirstLinkTitle"/> + <seeInField selector="{{AdminProductDownloadableSection.addLinkTitleInput('1')}}" userInput="{{downloadableLink.title}}" stepKey="seeSecondLinkTitle"/> + + <!-- Assert product in storefront product page --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- See product link in storefront product page --> + <scrollTo selector="{{StorefrontDownloadableProductSection.downloadableLinkBlock}}" stepKey="scrollToLinksInStorefront"/> + <seeElement stepKey="seeFirstDownloadableLink" selector="{{StorefrontDownloadableProductSection.downloadableLinkLabel(downloadableLinkWithMaxDownloads.title)}}"/> + <seeElement stepKey="seeSecondDownloadableLink" selector="{{StorefrontDownloadableProductSection.downloadableLinkLabel(downloadableLink.title)}}"/> + </test> +</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminDeleteDownloadableProductTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminDeleteDownloadableProductTest.xml index d7e93d3429b96..d3c2d6e5d71a4 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminDeleteDownloadableProductTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminDeleteDownloadableProductTest.xml @@ -10,6 +10,7 @@ <test name="AdminDeleteDownloadableProductTest"> <annotations> <features value="Downloadable"/> + <stories value="Downloadable product"/> <title value="Delete Downloadable Product"/> <description value="Admin should be able to delete a downloadable product"/> <testCaseId value="MC-11018"/> 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/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 9e27ca5d608f0..75b3ad277c603 100644 --- a/app/code/Magento/GraphQl/Controller/GraphQl.php +++ b/app/code/Magento/GraphQl/Controller/GraphQl.php @@ -12,23 +12,27 @@ use Magento\Framework\App\RequestInterface; use Magento\Framework\App\ResponseInterface; use Magento\Framework\GraphQl\Exception\ExceptionFormatter; -use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Query\QueryProcessor; use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; 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; @@ -67,6 +71,16 @@ class GraphQl implements FrontControllerInterface */ private $queryFields; + /** + * @var JsonFactory + */ + private $jsonFactory; + + /** + * @var HttpResponse + */ + private $httpResponse; + /** * @param Response $response * @param SchemaGeneratorInterface $schemaGenerator @@ -76,6 +90,9 @@ class GraphQl implements FrontControllerInterface * @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, @@ -85,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; @@ -95,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); } /** @@ -106,10 +127,10 @@ public function __construct( public function dispatch(RequestInterface $request) : ResponseInterface { $statusCode = 200; + $jsonResult = $this->jsonFactory->create(); try { /** @var Http $request */ $this->requestProcessor->validateRequest($request); - $this->requestProcessor->processHeaders($request); $data = $this->getDataFromRequest($request); $query = $data['query'] ?? ''; @@ -131,11 +152,11 @@ public function dispatch(RequestInterface $request) : ResponseInterface $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; } /** @@ -153,6 +174,8 @@ private function getDataFromRequest(RequestInterface $request) : array $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 []; } 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 246ad15379f85..0000000000000 --- a/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/StoreProcessor.php +++ /dev/null @@ -1,56 +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\App\HttpRequestInterface; -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 - * - * @param string $headerValue - * @return void - * @throws GraphQlInputException - */ - 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/HttpRequestValidatorInterface.php b/app/code/Magento/GraphQl/Controller/HttpRequestValidatorInterface.php index c0873b0caff89..2d9d50569e344 100644 --- a/app/code/Magento/GraphQl/Controller/HttpRequestValidatorInterface.php +++ b/app/code/Magento/GraphQl/Controller/HttpRequestValidatorInterface.php @@ -8,6 +8,7 @@ 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 @@ -19,6 +20,7 @@ interface HttpRequestValidatorInterface * * @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 b4f0113f58776..03bae5c80e12c 100644 --- a/app/code/Magento/GraphQl/etc/graphql/di.xml +++ b/app/code/Magento/GraphQl/etc/graphql/di.xml @@ -27,9 +27,6 @@ </type> <type name="Magento\GraphQl\Controller\HttpRequestProcessor"> <arguments> - <argument name="graphQlHeaders" xsi:type="array"> - <item name="Store" xsi:type="object">Magento\GraphQl\Controller\HttpHeaderProcessor\StoreProcessor</item> - </argument> <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> 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/Mftf/ActionGroup/StorefrontAddThreeGroupedProductToTheCartActionGroup.xml b/app/code/Magento/GroupedProduct/Test/Mftf/ActionGroup/StorefrontAddThreeGroupedProductToTheCartActionGroup.xml new file mode 100644 index 0000000000000..ba198ea9992d7 --- /dev/null +++ b/app/code/Magento/GroupedProduct/Test/Mftf/ActionGroup/StorefrontAddThreeGroupedProductToTheCartActionGroup.xml @@ -0,0 +1,30 @@ +<?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 that required fields are actually required--> + <actionGroup name="StorefrontAddThreeGroupedProductToTheCartActionGroup"> + <arguments> + <argument name="urlKey" type="string"/> + <argument name="product1" type="string"/> + <argument name="product2" type="string"/> + <argument name="product3" type="string"/> + <argument name="qty1" type="string"/> + <argument name="qty2" type="string"/> + <argument name="qty3" type="string"/> + </arguments> + <amOnPage url="{{StorefrontProductPage.url(urlKey)}}" stepKey="OpenStoreFrontProductPage"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <fillField stepKey="fillQuantityForProduct1" selector="{{StorefrontProductPageSection.qtyInputWithProduct(product1)}}" userInput="{{qty1}}"/> + <fillField stepKey="fillQuantityForProduct2" selector="{{StorefrontProductPageSection.qtyInputWithProduct(product2)}}" userInput="{{qty2}}"/> + <fillField stepKey="fillQuantityForProduct3" selector="{{StorefrontProductPageSection.qtyInputWithProduct(product3)}}" userInput="{{qty3}}"/> + <click selector="{{StorefrontProductPageSection.addToCartBtn}}" stepKey="clickOnAddToCartButton"/> + </actionGroup> +</actionGroups> + diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminDeleteGroupedProductTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminDeleteGroupedProductTest.xml index 966e24851395c..6768dd5a1a249 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminDeleteGroupedProductTest.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminDeleteGroupedProductTest.xml @@ -10,6 +10,7 @@ <test name="AdminDeleteGroupedProductTest"> <annotations> <features value="GroupedProduct"/> + <stories value="Delete product"/> <title value="Delete Grouped Product"/> <description value="Admin should be able to delete a grouped product"/> <testCaseId value="MC-11019"/> 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/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/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/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/Msrp/Test/Mftf/Data/MsrpData.xml b/app/code/Magento/Msrp/Test/Mftf/Data/MsrpData.xml new file mode 100644 index 0000000000000..f311e3c06e9ff --- /dev/null +++ b/app/code/Magento/Msrp/Test/Mftf/Data/MsrpData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="MsrpBeforeOrderConfirmation" type="minimum_advertised_price"> + <data key="msrp">600</data> + <data key="msrp_display_actual_price_type">Before Order Confirmation</data> + </entity> +</entities> diff --git a/app/code/Magento/Msrp/Test/Mftf/Test/StorefrontProductWithMapAssignedConfigProductIsCorrectTest.xml b/app/code/Magento/Msrp/Test/Mftf/Test/StorefrontProductWithMapAssignedConfigProductIsCorrectTest.xml index a874de3b223a2..d1eb1339fe856 100644 --- a/app/code/Magento/Msrp/Test/Mftf/Test/StorefrontProductWithMapAssignedConfigProductIsCorrectTest.xml +++ b/app/code/Magento/Msrp/Test/Mftf/Test/StorefrontProductWithMapAssignedConfigProductIsCorrectTest.xml @@ -11,6 +11,7 @@ <test name="StorefrontProductWithMapAssignedConfigProductIsCorrectTest"> <annotations> <features value="Msrp"/> + <stories value="Minimum advertised price"/> <title value="Check that simple products with MAP assigned to configurable product displayed correctly"/> <description value="Check that simple products with MAP assigned to configurable product displayed correctly"/> <severity value="CRITICAL"/> 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/Section/StorefrontNewsletterManageSection.xml b/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontNewsletterManageSection.xml index 96a944a4952ac..9248970d9e623 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontNewsletterManageSection.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontNewsletterManageSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontNewsletterManageSection"> <element name="subscriptionCheckbox" type="checkbox" selector="#subscription" /> + <element name="saveButton" type="button" selector="div.primary>button"/> </section> </sections> 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/Model/SmartButtonConfig.php b/app/code/Magento/Paypal/Model/SmartButtonConfig.php index 80a0d477216b0..c491f2978e560 100644 --- a/app/code/Magento/Paypal/Model/SmartButtonConfig.php +++ b/app/code/Magento/Paypal/Model/SmartButtonConfig.php @@ -67,7 +67,8 @@ public function getConfig(string $page): array 'locale' => $this->localeResolver->getLocale(), 'allowedFunding' => $this->getAllowedFunding($page), 'disallowedFunding' => $this->getDisallowedFunding(), - 'styles' => $this->getButtonStyles($page) + 'styles' => $this->getButtonStyles($page), + 'isVisibleOnProductPage' => (int)$this->config->getValue('visible_on_product') ]; } 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/Test/Unit/Model/_files/expected_config.php b/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_config.php index 3a76d11e51374..1442642a324b9 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_config.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_config.php @@ -31,7 +31,8 @@ 'shape' => 'pillow', 'label' => 'installment', 'installmentperiod' => 0 - ] + ], + 'isVisibleOnProductPage' => 0 ] ], 'checkout' => [ @@ -59,7 +60,8 @@ 'shape' => 'pillow', 'label' => 'installment', 'installmentperiod' => 0 - ] + ], + 'isVisibleOnProductPage' => 0 ] ], 'mini_cart' => [ @@ -86,7 +88,8 @@ 'color' => 'gold', 'shape' => 'rect', 'label' => 'paypal' - ] + ], + 'isVisibleOnProductPage' => 0 ] ], 'mini_cart' => [ @@ -113,7 +116,8 @@ 'color' => 'gold', 'shape' => 'rect', 'label' => 'paypal' - ] + ], + 'isVisibleOnProductPage' => 0 ] ], 'product' => [ @@ -140,7 +144,8 @@ 'color' => 'gold', 'shape' => 'rect', 'label' => 'paypal', - ] + ], + 'isVisibleOnProductPage' => 0 ] ] ]; diff --git a/app/code/Magento/Paypal/etc/adminhtml/system.xml b/app/code/Magento/Paypal/etc/adminhtml/system.xml index ea48aa65132e8..ca886c7827ffd 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system.xml @@ -75,7 +75,7 @@ <label>Payments Pro</label> <attribute type="activity_path">payment/paypal_payment_pro/active</attribute> <group id="configuration_details"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-pro.html</comment> + <comment>https://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-pro.html</comment> </group> <group id="paypal_payflow_required" showInDefault="1" showInWebsite="1" sortOrder="10"> <field id="enable_paypal_payflow"> @@ -95,7 +95,7 @@ <comment>Accept credit card and PayPal payments securely.</comment> <attribute type="activity_path">payment/wps_express/active</attribute> <group id="configuration_details"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-standard.html</comment> + <comment>https://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-standard.html</comment> </group> <group id="express_checkout_required"> <group id="express_checkout_required_express_checkout"> @@ -174,7 +174,7 @@ <comment>Accept credit card and PayPal payments securely.</comment> <attribute type="activity_path">payment/wps_express/active</attribute> <group id="configuration_details"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-standard.html</comment> + <comment>https://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-standard.html</comment> </group> <group id="express_checkout_required"> <group id="express_checkout_required_express_checkout" translate="label"> @@ -245,7 +245,7 @@ <comment>Accept credit card and PayPal payments securely.</comment> <attribute type="activity_path">payment/wps_express/active</attribute> <group id="configuration_details"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-standard.html</comment> + <comment>https://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-standard.html</comment> </group> <group id="express_checkout_required"> <group id="express_checkout_required_express_checkout" translate="label"> @@ -290,7 +290,7 @@ <label>Website Payments Pro</label> <attribute type="activity_path">payment/paypal_payment_pro/active</attribute> <group id="configuration_details"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-pro.html</comment> + <comment>https://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-pro.html</comment> </group> <group id="paypal_payflow_required" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> <group id="paypal_payflow_api_settings"> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml index 7abefbe1a674e..d5287659ee7d2 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml @@ -13,7 +13,7 @@ <comment>Add PayPal as an additional payment method to your checkout page.</comment> <attribute type="activity_path">payment/paypal_express/active</attribute> <group id="configuration_details" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="4"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-express-checkout.html</comment> + <comment>https://docs.magento.com/m2/ce/user_guide/payment/paypal-express-checkout.html</comment> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Hint</frontend_model> </group> <group id="express_checkout_required" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml index e7de9c0d641a7..cba36916c3305 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml @@ -13,7 +13,7 @@ <comment><![CDATA[Accept payments with a PCI-compliant checkout that keeps customers on your site. (<u>Includes Express Checkout</u>)]]></comment> <attribute type="activity_path">payment/payflow_advanced/active</attribute> <group id="configuration_details" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="4"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-advanced.html</comment> + <comment>https://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-advanced.html</comment> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Hint</frontend_model> </group> <group id="required_settings" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml index 647bc7a60975a..ed11e8ba18d07 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml @@ -13,7 +13,7 @@ <comment><![CDATA[Connect your merchant account with a PCI-compliant gateway that lets customers pay without leaving your site. (<u>Includes Express Checkout</u>)]]></comment> <attribute type="activity_path">payment/payflow_link/active</attribute> <group id="configuration_details" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="4"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payflow-link.html</comment> + <comment>https://docs.magento.com/m2/ce/user_guide/payment/paypal-payflow-link.html</comment> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Hint</frontend_model> </group> <group id="payflow_link_required" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/payments_pro_hosted_solution.xml b/app/code/Magento/Paypal/etc/adminhtml/system/payments_pro_hosted_solution.xml index 35cd844204843..727bffdaf27e3 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/payments_pro_hosted_solution.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/payments_pro_hosted_solution.xml @@ -14,7 +14,7 @@ <comment><![CDATA[Accept payments with a PCI compliant checkout that keeps customers on your site. (<u>Includes Express Checkout</u>)]]></comment> <attribute type="paypal_ec_separate">1</attribute> <group id="configuration_details" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="4"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-pro.html</comment> + <comment>https://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-pro.html</comment> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Hint</frontend_model> </group> <group id="pphs_required_settings" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml index fcf3fb39d1b7d..e5a0319bdc1bf 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml @@ -14,7 +14,7 @@ <attribute type="activity_path">payment/payflowpro/active</attribute> <attribute type="paypal_ec_separate">1</attribute> <group id="configuration_details" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="4"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payflow-pro.html</comment> + <comment>https://docs.magento.com/m2/ce/user_guide/payment/paypal-payflow-pro.html</comment> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Hint</frontend_model> </group> <group id="paypal_payflow_required" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> 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/Paypal/view/frontend/web/js/in-context/product-express-checkout.js b/app/code/Magento/Paypal/view/frontend/web/js/in-context/product-express-checkout.js index 413820cc731ac..f4adaae06a112 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/in-context/product-express-checkout.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/in-context/product-express-checkout.js @@ -24,7 +24,11 @@ define([ customer = customerData.get('customer'); this._super(); - this.renderPayPalButtons(element); + + if (config.clientConfig.isVisibleOnProductPage) { + this.renderPayPalButtons(element); + } + this.declinePayment = !customer().firstname && !cart().isGuestCheckoutAllowed; return this; 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/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/Test/Mftf/Data/CartItemData.xml b/app/code/Magento/Quote/Test/Mftf/Data/CartItemData.xml index 4cc2c4f392419..a513b6747f612 100644 --- a/app/code/Magento/Quote/Test/Mftf/Data/CartItemData.xml +++ b/app/code/Magento/Quote/Test/Mftf/Data/CartItemData.xml @@ -13,4 +13,14 @@ <var key="quote_id" entityKey="return" entityType="GuestCart"/> <var key="sku" entityKey="sku" entityType="product"/> </entity> + <entity name="SimpleTwoCartItems" type="CartItem"> + <data key="qty">2</data> + <var key="quote_id" entityKey="return" entityType="GuestCart"/> + <var key="sku" entityKey="sku" entityType="product"/> + </entity> + <entity name="NotValidCartItems" type="CartItem"> + <data key="qty">abc</data> + <var key="quote_id" entityKey="return" entityType="GuestCart"/> + <var key="sku" entityKey="sku" entityType="product"/> + </entity> </entities> 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/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..b491b10730c10 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php @@ -13,21 +13,13 @@ use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; -use Magento\Framework\Stdlib\ArrayManager; use Magento\Quote\Model\Quote; /** * Add simple product to cart - * - * TODO: should be replaced for different types resolver */ class AddSimpleProductToCart { - /** - * @var ArrayManager - */ - private $arrayManager; - /** * @var DataObjectFactory */ @@ -39,16 +31,13 @@ class AddSimpleProductToCart private $productRepository; /** - * @param ArrayManager $arrayManager * @param DataObjectFactory $dataObjectFactory * @param ProductRepositoryInterface $productRepository */ public function __construct( - ArrayManager $arrayManager, DataObjectFactory $dataObjectFactory, ProductRepositoryInterface $productRepository ) { - $this->arrayManager = $arrayManager; $this->dataObjectFactory = $dataObjectFactory; $this->productRepository = $productRepository; } @@ -66,12 +55,7 @@ public function __construct( public function execute(Quote $cart, array $cartItemData): void { $sku = $this->extractSku($cartItemData); - $qty = $this->extractQty($cartItemData); - if ($qty <= 0) { - throw new GraphQlInputException( - __('Please enter a number greater than 0 in this field.') - ); - } + $quantity = $this->extractQuantity($cartItemData); $customizableOptions = $this->extractCustomizableOptions($cartItemData); try { @@ -81,7 +65,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( __( @@ -105,27 +89,32 @@ public function execute(Quote $cart, array $cartItemData): void */ private function extractSku(array $cartItemData): string { - $sku = $this->arrayManager->get('data/sku', $cartItemData); - if (!isset($sku)) { - throw new GraphQlInputException(__('Missing key "sku" in cart item data')); + if (!isset($cartItemData['data']['sku']) || empty($cartItemData['data']['sku'])) { + throw new GraphQlInputException(__('Missed "sku" in cart item data')); } - return (string)$sku; + return (string)$cartItemData['data']['sku']; } /** - * 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')); + if (!isset($cartItemData['data']['quantity'])) { + throw new GraphQlInputException(__('Missed "qty" in cart item data')); + } + $quantity = (float)$cartItemData['data']['quantity']; + + if ($quantity <= 0) { + throw new GraphQlInputException( + __('Please enter a number greater than 0 in this field.') + ); } - return (float)$qty; + return $quantity; } /** @@ -136,11 +125,17 @@ private function extractQty(array $cartItemData): float */ private function extractCustomizableOptions(array $cartItemData): array { - $customizableOptions = $this->arrayManager->get('customizable_options', $cartItemData, []); + if (!isset($cartItemData['customizable_options']) || empty($cartItemData['customizable_options'])) { + return []; + } $customizableOptionsData = []; - foreach ($customizableOptions as $customizableOption) { - $customizableOptionsData[$customizableOption['id']] = $customizableOption['value']; + foreach ($cartItemData['customizable_options'] as $customizableOption) { + if (isset($customizableOption['value_string'])) { + $customizableOptionsData[$customizableOption['id']] = $this->convertCustomOptionValue( + $customizableOption['value_string'] + ); + } } return $customizableOptionsData; } @@ -148,17 +143,33 @@ 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, ], ]); } + + /** + * Convert custom options vakue + * + * @param string $value + * @return string|array + */ + private function convertCustomOptionValue(string $value) + { + $value = trim($value); + if (substr($value, 0, 1) === "[" && + substr($value, strlen($value) - 1, 1) === "]") { + return explode(',', substr($value, 1, -1)); + } + return $value; + } } 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/AddSimpleProductsToCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/AddSimpleProductsToCart.php index 82ffd0970d672..cfae0e1267802 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/AddSimpleProductsToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/AddSimpleProductsToCart.php @@ -52,12 +52,12 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value } $maskedCartId = $args['input']['cart_id']; - if (!isset($args['input']['cartItems']) || empty($args['input']['cartItems']) - || !is_array($args['input']['cartItems']) + if (!isset($args['input']['cart_items']) || empty($args['input']['cart_items']) + || !is_array($args['input']['cart_items']) ) { - throw new GraphQlInputException(__('Required parameter "cartItems" is missing')); + throw new GraphQlInputException(__('Required parameter "cart_items" is missing')); } - $cartItems = $args['input']['cartItems']; + $cartItems = $args['input']['cart_items']; $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId()); $this->addProductsToCart->execute($cart, $cartItems); 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/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/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/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/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/AvailableShippingMethods.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php index a9e0ba59d15d9..958934ed18032 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php @@ -7,13 +7,16 @@ namespace Magento\QuoteGraphQl\Model\Resolver\ShippingAddress; +use Magento\Directory\Model\Currency; use Magento\Framework\Api\ExtensibleDataObjectConverter; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Quote\Api\Data\ShippingMethodInterface; use Magento\Quote\Model\Cart\ShippingMethodConverter; +use Magento\Store\Model\StoreManagerInterface; /** * @inheritdoc @@ -30,16 +33,24 @@ class AvailableShippingMethods implements ResolverInterface */ private $shippingMethodConverter; + /** + * @var StoreManagerInterface + */ + private $storeManager; + /** * @param ExtensibleDataObjectConverter $dataObjectConverter * @param ShippingMethodConverter $shippingMethodConverter + * @param StoreManagerInterface $storeManager */ public function __construct( ExtensibleDataObjectConverter $dataObjectConverter, - ShippingMethodConverter $shippingMethodConverter + ShippingMethodConverter $shippingMethodConverter, + StoreManagerInterface $storeManager ) { $this->dataObjectConverter = $dataObjectConverter; $this->shippingMethodConverter = $shippingMethodConverter; + $this->storeManager = $storeManager; } /** @@ -65,13 +76,44 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $shippingRates = $address->getGroupedAllShippingRates(); foreach ($shippingRates as $carrierRates) { foreach ($carrierRates as $rate) { - $methods[] = $this->dataObjectConverter->toFlatArray( + $methodData = $this->dataObjectConverter->toFlatArray( $this->shippingMethodConverter->modelToDataObject($rate, $cart->getQuoteCurrencyCode()), [], ShippingMethodInterface::class ); + $methods[] = $this->processMoneyTypeData($methodData, $cart->getQuoteCurrencyCode()); } } return $methods; } + + /** + * Process money type data + * + * @param array $data + * @param string $quoteCurrencyCode + * @return array + * @throws NoSuchEntityException + */ + private function processMoneyTypeData(array $data, string $quoteCurrencyCode): array + { + if (isset($data['amount'])) { + $data['amount'] = ['value' => $data['amount'], 'currency' => $quoteCurrencyCode]; + } + + if (isset($data['base_amount'])) { + /** @var Currency $currency */ + $currency = $this->storeManager->getStore()->getBaseCurrency(); + $data['base_amount'] = ['value' => $data['base_amount'], 'currency' => $currency->getCode()]; + } + + if (isset($data['price_excl_tax'])) { + $data['price_excl_tax'] = ['value' => $data['price_excl_tax'], 'currency' => $quoteCurrencyCode]; + } + + if (isset($data['price_incl_tax'])) { + $data['price_incl_tax'] = ['value' => $data['price_incl_tax'], 'currency' => $quoteCurrencyCode]; + } + return $data; + } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php index c58affa064c89..05bc196acfc22 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php @@ -7,16 +7,34 @@ namespace Magento\QuoteGraphQl\Model\Resolver\ShippingAddress; +use Magento\Directory\Model\Currency; 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\Address; +use Magento\Quote\Model\Quote\Address\Rate; +use Magento\Store\Model\StoreManagerInterface; /** * @inheritdoc */ class SelectedShippingMethod implements ResolverInterface { + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param StoreManagerInterface $storeManager + */ + public function __construct( + StoreManagerInterface $storeManager + ) { + $this->storeManager = $storeManager; + } + /** * @inheritdoc */ @@ -25,19 +43,51 @@ 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(); + $carrierTitle = null; + $methodTitle = null; - if ($address->getShippingMethod()) { + if (count($rates) > 0) { list($carrierCode, $methodCode) = explode('_', $address->getShippingMethod(), 2); - $shippingAmount = $address->getShippingAmount(); - } - return [ - 'carrier_code' => $carrierCode ?? null, - 'method_code' => $methodCode ?? null, - 'label' => $address->getShippingDescription(), - 'amount' => $shippingAmount ?? null, - ]; + /** @var Rate $rate */ + foreach ($rates as $rate) { + if ($rate->getCode() == $address->getShippingMethod()) { + $carrierTitle = $rate->getCarrierTitle(); + $methodTitle = $rate->getMethodTitle(); + break; + } + } + + /** @var Currency $currency */ + $currency = $this->storeManager->getStore()->getBaseCurrency(); + + $data = [ + 'carrier_code' => $carrierCode, + 'method_code' => $methodCode, + 'carrier_title' => $carrierTitle, + 'method_title' => $methodTitle, + 'amount' => [ + 'value' => $address->getShippingAmount(), + 'currency' => $address->getQuote()->getQuoteCurrencyCode(), + ], + 'base_amount' => [ + 'value' => $address->getBaseShippingAmount(), + 'currency' => $currency->getCode(), + ], + ]; + } else { + $data = [ + 'carrier_code' => null, + 'method_code' => null, + 'carrier_title' => $carrierTitle, + 'method_title' => $methodTitle, + '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..6f4d3969d1018 100644 --- a/app/code/Magento/QuoteGraphQl/composer.json +++ b/app/code/Magento/QuoteGraphQl/composer.json @@ -11,10 +11,12 @@ "magento/module-store": "*", "magento/module-customer": "*", "magento/module-customer-graph-ql": "*", - "magento/module-sales": "*" + "magento/module-sales": "*", + "magento/module-directory": "*" }, "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/di.xml b/app/code/Magento/QuoteGraphQl/etc/di.xml index 0697761a2a2a6..32b8b12987ffa 100644 --- a/app/code/Magento/QuoteGraphQl/etc/di.xml +++ b/app/code/Magento/QuoteGraphQl/etc/di.xml @@ -26,7 +26,7 @@ <item name="area" xsi:type="string">Magento\QuoteGraphQl\Model\CartItem\DataProvider\CustomizableOptionValue\Text</item> <item name="drop_down" xsi:type="string">Magento\QuoteGraphQl\Model\CartItem\DataProvider\CustomizableOptionValue\Dropdown</item> <item name="radio" xsi:type="string">Magento\QuoteGraphQl\Model\CartItem\DataProvider\CustomizableOptionValue\Dropdown</item> - <item name="checkbox" xsi:type="string">Magento\QuoteGraphQl\Model\CartItem\DataProvider\CustomizableOptionValue\Dropdown</item> + <item name="checkbox" xsi:type="string">Magento\QuoteGraphQl\Model\CartItem\DataProvider\CustomizableOptionValue\Multiple</item> <item name="multiple" xsi:type="string">Magento\QuoteGraphQl\Model\CartItem\DataProvider\CustomizableOptionValue\Multiple</item> </argument> </arguments> diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index 711e6cbc7f5f6..9e9c83b358206 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") @@ -21,9 +21,13 @@ type Mutation { placeOrder(input: PlaceOrderInput): PlaceOrderOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\PlaceOrder") } +input createEmptyCartInput { + cart_id: String +} + input AddSimpleProductsToCartInput { cart_id: String! - cartItems: [SimpleProductCartItemInput!]! + cart_items: [SimpleProductCartItemInput!]! } input SimpleProductCartItemInput { @@ -33,7 +37,7 @@ input SimpleProductCartItemInput { input AddVirtualProductsToCartInput { cart_id: String! - cartItems: [VirtualProductCartItemInput!]! + cart_items: [VirtualProductCartItemInput!]! } input VirtualProductCartItemInput { @@ -43,12 +47,12 @@ input VirtualProductCartItemInput { input CartItemInput { sku: String! - qty: Float! + quantity: Float! } input CustomizableOptionInput { id: Int! - value: String! + value_string: String! } input ApplyCouponToCartInput { @@ -58,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! } @@ -111,7 +115,6 @@ input SetShippingMethodsOnCartInput { } input ShippingMethodInput { - cart_address_id: Int! carrier_code: String! method_code: String! } @@ -176,15 +179,14 @@ type Cart { items: [CartItemInterface] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartItems") applied_coupon: AppliedCoupon @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\AppliedCoupon") email: String @resolver (class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartEmail") - shipping_addresses: [CartAddress]! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAddresses") - billing_address: CartAddress! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\BillingAddress") + 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 @@ -194,14 +196,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! @@ -220,37 +227,36 @@ 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! - price_excl_tax: Float! - price_incl_tax: Float! + amount: Money! + base_amount: Money @doc(description: "Could be null if method is not available") + price_excl_tax: Money! + price_incl_tax: Money! + 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.") purchase_order_number: String @doc(description: "The purchase order number.") } -enum AdressTypeEnum { - SHIPPING - BILLING -} - type AppliedCoupon { code: String! } @@ -293,15 +299,14 @@ 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! } type SelectedCustomizableOption { id: Int! label: String! - type: String! - is_required: Int! + is_required: Boolean! values: [SelectedCustomizableOptionValue!]! sort_order: Int! } @@ -309,9 +314,8 @@ type SelectedCustomizableOption { type SelectedCustomizableOptionValue { id: Int! label: String! - value: String! + value: String price: CartItemSelectedOptionValuePrice! - sort_order: Int! } type CartItemSelectedOptionValuePrice { 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/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/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/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/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/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/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/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/AdminCorrectnessInvoicedItemInBundleProductTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml index f869841153aea..1ad736ade37fc 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml @@ -11,6 +11,7 @@ <test name="AdminCorrectnessInvoicedItemInBundleProductTest"> <annotations> <features value="Sales"/> + <stories value="Product invoice"/> <title value="Check correctness of invoiced items in a Bundle Product"/> <description value="Check correctness of invoiced items in a Bundle Product"/> <severity value="CRITICAL"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutFieldsValidationTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutFieldsValidationTest.xml index d418751c736e1..1490fc1a1a388 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutFieldsValidationTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutFieldsValidationTest.xml @@ -13,6 +13,7 @@ <stories value="Create orders"/> <title value="Fields validation is required to create an order from Admin Panel"/> <description value="Admin should not be able to submit orders without invalid address fields"/> + <severity value="AVERAGE"/> <group value="sales"/> </annotations> <before> 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/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/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/AdminCreateCartPriceRuleActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleActionGroup.xml index cc165e0b5dc96..d77e30cda24eb 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleActionGroup.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleActionGroup.xml @@ -24,6 +24,11 @@ <see selector="{{AdminCartPriceRulesFormSection.successMessage}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> </actionGroup> + <actionGroup name="AdminCreateCartPriceRuleAndStayOnEditActionGroup" extends="AdminCreateCartPriceRuleActionGroup" > + <click selector="{{AdminCartPriceRulesFormSection.discardSubsequentRules}}" stepKey="setYesDiscardSubsequentRule" after="clickToExpandActions"/> + <click selector="{{AdminCartPriceRulesFormSection.saveAndContinue}}" stepKey="clickSaveButton"/> + </actionGroup> + <actionGroup name="AdminCreateCartPriceRuleWithCouponCode" extends="AdminCreateCartPriceRuleActionGroup"> <arguments> <argument name="couponCode" defaultValue="_defaultCoupon.code"/> @@ -91,4 +96,8 @@ <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="Second Website" stepKey="selectWebsites"/> </actionGroup> -</actionGroups> + + <actionGroup name="AdminInactiveCartPriceRuleActionGroup" extends="AdminCreateCartPriceRuleActionGroup"> + <click selector="{{AdminCartPriceRulesFormSection.active}}" stepKey="clickActiveToDisable" after="fillRuleName"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleActionsSectionActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleActionsSectionActionGroup.xml new file mode 100644 index 0000000000000..42608bf36096b --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleActionsSectionActionGroup.xml @@ -0,0 +1,32 @@ +<?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="AdminCreateCartPriceRuleActionsSectionDiscountFieldsActionGroup"> + <arguments> + <argument name="rule" type="entity"/> + </arguments> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.actionsHeader}}" visible="true" stepKey="clickToExpandActions"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.apply}}" userInput="{{rule.simple_action}}" stepKey="selectActionType"/> + <fillField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="{{rule.discount_amount}}" stepKey="fillDiscountAmount"/> + <fillField selector="{{AdminCartPriceRulesFormSection.maximumQtyDiscount}}" userInput="{{rule.maximumQtyDiscount}}" stepKey="fillMaximumQtyDiscount"/> + <fillField selector="{{AdminCartPriceRulesFormSection.discountStep}}" userInput="{{rule.discount_step}}" stepKey="fillDiscountStep"/> + </actionGroup> + <actionGroup name="AdminCreateCartPriceRuleActionsSectionShippingAmountActionGroup"> + <click selector="{{AdminCartPriceRulesFormSection.applyToShippingAmount}}" stepKey="clickApplyToShipping"/> + </actionGroup> + <actionGroup name="AdminCreateCartPriceRuleActionsSectionSubsequentRulesActionGroup"> + <click selector="{{AdminCartPriceRulesFormSection.discardSubsequentRules}}" stepKey="clickApplyToShipping"/> + </actionGroup> + <actionGroup name="AdminCreateCartPriceRuleActionsSectionFreeShippingActionGroup"> + <arguments> + <argument name="freeShippingOption" type="string"/> + </arguments> + <selectOption selector="{{AdminCartPriceRulesFormSection.freeShipping}}" userInput="{{freeShippingOption}}" stepKey="selectForMatchingItemsOnly"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleLabelsSectionActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleLabelsSectionActionGroup.xml new file mode 100644 index 0000000000000..e9d5f07701bd8 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleLabelsSectionActionGroup.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="AdminCreateCartPriceRuleLabelsSectionActionGroup"> + <arguments> + <argument name="rule" type="entity"/> + </arguments> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.labelsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.labelsHeader}}" visible="true" stepKey="clickToExpandLabels"/> + <fillField selector="{{AdminCartPriceRulesFormSection.defaultRuleLabelAllStoreViews}}" userInput="{{rule.defaultRuleLabelAllStoreViews}}" stepKey="fillDefaultRuleLabelAllStoreViews"/> + <fillField selector="{{AdminCartPriceRulesFormSection.defaultStoreView}}" userInput="{{rule.defaultStoreView}}" stepKey="fillDefaultStoreView"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleRuleInfoSectionActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleRuleInfoSectionActionGroup.xml new file mode 100644 index 0000000000000..27b9634cbe28c --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleRuleInfoSectionActionGroup.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="AdminCreateCartPriceRuleRuleInfoSectionActionGroup"> + <arguments> + <argument name="ruleName" type="entity"/> + </arguments> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForPriceList"/> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{ruleName.name}}" stepKey="fillRuleName"/> + <fillField selector="{{AdminCartPriceRulesFormSection.description}}" userInput="{{ruleName.description}}" stepKey="fillDescription"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="{{ruleName.websites}}" stepKey="selectWebsites"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.customerGroups}}" parameterArray="[{{ruleName.customerGroups}}]" stepKey="selectCustomerGroup"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.coupon}}" userInput="{{ruleName.coupon_type}}" stepKey="selectCouponType"/> + <waitForElementVisible selector="{{AdminCartPriceRulesFormSection.couponCode}}" stepKey="waitForElementVisible"/> + <fillField selector="{{AdminCartPriceRulesFormSection.couponCode}}" userInput="{{ruleName.coupon_code}}" stepKey="fillCouponCode"/> + <fillField selector="{{AdminCartPriceRulesFormSection.userPerCoupon}}" userInput="{{ruleName.uses_per_coupon}}" stepKey="fillUsesPerCoupon"/> + <fillField selector="{{AdminCartPriceRulesFormSection.userPerCustomer}}" userInput="{{ruleName.uses_per_customer}}" stepKey="fillUsesPerCustomer"/> + <generateDate date="+1 minute" format="m/d/Y" stepKey="startDateTime"/> + <fillField selector="{{AdminCartPriceRulesFormSection.fromDate}}" userInput="{$startDateTime}" stepKey="fillStartDate"/> + <fillField selector="{{AdminCartPriceRulesFormSection.priority}}" userInput="{{ruleName.sort_order}}" stepKey="fillPriority"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.publicInRSSFeed}}" dependentSelector="{{AdminCartPriceRulesFormSection.publicInRSSFeed}}" visible="false" stepKey="clickOnlyIfRSSIsDisabled"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminDeleteCartPriceRuleActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminDeleteCartPriceRuleActionGroup.xml new file mode 100644 index 0000000000000..88f9bf2fbe54d --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminDeleteCartPriceRuleActionGroup.xml @@ -0,0 +1,22 @@ +<?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="AdminDeleteCartPriceRuleActionGroup"> + <arguments> + <argument name="ruleName" type="entity"/> + </arguments> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="goToCartPriceRules"/> + <waitForPageLoad stepKey="waitForCartPriceRules"/> + <fillField selector="{{AdminCartPriceRulesSection.filterByNameInput}}" userInput="{{ruleName.name}}" stepKey="filterByName"/> + <click selector="{{AdminCartPriceRulesSection.searchButton}}" stepKey="doFilter"/> + <click selector="{{AdminCartPriceRulesSection.rowByIndex('1')}}" stepKey="goToEditRulePage"/> + <click selector="{{AdminCartPriceRulesFormSection.delete}}" stepKey="clickDeleteButton"/> + <click selector="{{AdminCartPriceRulesFormSection.modalAcceptButton}}" stepKey="confirmDelete"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminFilterCartPriceRuleActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminFilterCartPriceRuleActionGroup.xml index 35e1bee0952cf..7a3a20e603255 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminFilterCartPriceRuleActionGroup.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminFilterCartPriceRuleActionGroup.xml @@ -18,4 +18,9 @@ <click selector="{{AdminCartPriceRulesSection.searchButton}}" stepKey="doFilter"/> <click selector="{{AdminCartPriceRulesSection.rowByIndex('1')}}" stepKey="goToEditRulePage"/> </actionGroup> -</actionGroups> + <actionGroup name="AdminCartPriceRuleNotInGridActionGroup" extends="AdminFilterCartPriceRuleActionGroup"> + <remove keyForRemoval="goToEditRulePage"/> + <waitForPageLoad stepKey="waitForStoreToLoad"/> + <see selector="{{AdminCartPriceRulesSection.emptyText}}" userInput="We couldn't find any records." stepKey="seeAssertCartPriceRuleIsNotPresentedInGrid"/> + </actionGroup> +</actionGroups> \ No newline at end of file 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/AssertCartPriceRuleSuccessSaveMessageActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AssertCartPriceRuleSuccessSaveMessageActionGroup.xml new file mode 100644 index 0000000000000..dbfef4c006696 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AssertCartPriceRuleSuccessSaveMessageActionGroup.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="AssertCartPriceRuleSuccessSaveMessageActionGroup"> + <click selector="{{AdminCartPriceRulesFormSection.save}}" stepKey="clickSaveButton"/> + <see selector="{{AdminCartPriceRulesFormSection.successMessage}}" userInput="You saved the rule." stepKey="seeAssertSuccessSaveMessage"/> + </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/ActionGroup/AssertStorefrontMiniCartItemsActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AssertStorefrontMiniCartItemsActionGroup.xml new file mode 100644 index 0000000000000..f99e39411ac7a --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AssertStorefrontMiniCartItemsActionGroup.xml @@ -0,0 +1,24 @@ +<?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="AssertStorefrontMiniCartItemsActionGroup"> + <arguments> + <argument name="productName" type="string"/> + <argument name="productPrice" type="string"/> + <argument name="cartSubtotal" type="string"/> + <argument name="qty" type="string"/> + </arguments> + <see selector="{{StorefrontMinicartSection.miniCartItemsText}}" userInput="{{productName}}" stepKey="seeProductNameInMiniCart"/> + <see selector="{{StorefrontMinicartSection.miniCartItemsText}}" userInput="{{productPrice}}" stepKey="seeProductPriceInMiniCart"/> + <seeElement selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="seeCheckOutButtonInMiniCart"/> + <seeElement selector="{{StorefrontMinicartSection.productQuantity(productName, qty)}}" stepKey="seeProductQuantity1"/> + <seeElement selector="{{StorefrontMinicartSection.productImage}}" stepKey="seeProductImage"/> + <see selector="{{StorefrontMinicartSection.productSubTotal}}" userInput="{{cartSubtotal}}" stepKey="seeSubTotal"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontAddToTheCartActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontAddToTheCartActionGroup.xml new file mode 100644 index 0000000000000..57ed2b67be10e --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontAddToTheCartActionGroup.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="StorefrontAddToTheCartActionGroup"> + <waitForPageLoad stepKey="waitForPageLoad"/> + <scrollTo selector="{{StorefrontProductActionSection.addToCart}}" stepKey="scrollToAddToCartButton"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addToCart"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontClickOnMiniCartActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontClickOnMiniCartActionGroup.xml new file mode 100644 index 0000000000000..498695dee9336 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontClickOnMiniCartActionGroup.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="StorefrontClickOnMiniCartActionGroup"> + <scrollToTopOfPage stepKey="scrollToTheTopOfThePage"/> + <waitForElementVisible selector="{{StorefrontMinicartSection.showCart}}" stepKey="waitForElementToBeVisible"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickOnMiniCart"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml index 8f6e63534b0ca..219fac4e92424 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml @@ -193,4 +193,149 @@ <entity name="SalesRuleNoCouponWithFixedDiscount" extends="ApiCartRule"> <data key="simple_action">by_fixed</data> </entity> + <entity name="ActiveSalesRuleWithPercentPriceDiscountCoupon"> + <data key="name" unique="suffix">Cart Price Rule with Specific Coupon</data> + <data key="description">Description for Cart Price Rule</data> + <data key="is_active">Yes</data> + <data key="websites">Main Website</data> + <data key="customerGroups">'NOT LOGGED IN'</data> + <data key="coupon_type">Specific Coupon</data> + <data key="coupon_code" unique="suffix">123-abc-ABC-987</data> + <data key="apply">Percent of product price discount</data> + <data key="discountAmount">50</data> + <data key="defaultRuleLabelAllStoreViews">Cart Price Rule with Specific Coupon</data> + <data key="defaultStoreView">Cart Price Rule with Specific Coupon</data> + </entity> + <entity name="ActiveSalesRuleWithComplexConditions"> + <data key="name" unique="suffix">Cart Price Rule with complex conditions</data> + <data key="description">Cart Price Rule with complex conditions</data> + <data key="is_active">Yes</data> + <data key="websites">Main Website</data> + <data key="customerGroups">'NOT LOGGED IN', 'General', 'Wholesale', 'Retailer'</data> + <data key="coupon_type">Specific Coupon</data> + <data key="coupon_code" unique="suffix">123-abc-ABC-987</data> + <data key="uses_per_coupon">13</data> + <data key="uses_per_customer">63</data> + <data key="sort_order">1</data> + <data key="is_rss">Yes</data> + <data key="subtotal">300</data> + <data key="shippingCountry">US</data> + <data key="shippingPostcode">123456789a</data> + <data key="simple_action">Percent of product price discount</data> + <data key="discount_amount">50</data> + <data key="maximumQtyDiscount">0</data> + <data key="discount_step">0</data> + <data key="apply_to_shipping">Yes</data> + <data key="stop_rules_processing">Yes</data> + <data key="simple_free_shipping">For matching items only</data> + <data key="defaultRuleLabelAllStoreViews">Cart Price Rule with complex conditions</data> + <data key="defaultStoreView">Cart Price Rule with complex conditions</data> + </entity> + <entity name="InactiveSalesRule"> + <data key="name" unique="suffix">Inactive Cart Price Rule</data> + <data key="description">Inactive Cart Price Rule</data> + <data key="is_active">0</data> + <data key="websites">Main Website</data> + <data key="customerGroups">'NOT LOGGED IN'</data> + <data key="coupon_type">No Coupon</data> + <data key="apply">Percent of product price discount</data> + <data key="discountAmount">50</data> + </entity> + <entity name="CartPriceRuleConditionAndFreeShippingApplied"> + <data key="name" unique="suffix">Cart Price Rule For RuleCondition And FreeShipping</data> + <data key="description">Description for Cart Price Rule</data> + <data key="is_active">Yes</data> + <data key="websites">Main Website</data> + <data key="customerGroups">NOT LOGGED IN</data> + <data key="coupon_type">No Coupon</data> + <data key="simple_action">Percent of product price discount</data> + <data key="discount_amount">50</data> + <data key="maximumQtyDiscount">0</data> + <data key="discount_step">0</data> + <data key="apply_to_shipping">0</data> + <data key="simple_free_shipping">For matching items only</data> + <data key="defaultRuleLabelAllStoreViews">Free Shipping in conditions</data> + <data key="defaultStoreView">Free Shipping in conditions</data> + </entity> + <entity name="CartPriceRuleConditionAppliedForSubtotal"> + <data key="name" unique="suffix">Cart Price Rule For Rule Condition</data> + <data key="description">Description for Cart Price Rule</data> + <data key="is_active">Yes</data> + <data key="websites">Main Website</data> + <data key="customerGroups">NOT LOGGED IN</data> + <data key="coupon_type">No Coupon</data> + <data key="subtotal">560</data> + <data key="simple_action">Percent of product price discount</data> + <data key="discount_amount">50</data> + <data key="maximumQtyDiscount">0</data> + <data key="discount_step">0</data> + <data key="apply_to_shipping">0</data> + <data key="simple_free_shipping">No</data> + <data key="defaultRuleLabelAllStoreViews">Free Shipping in Rule conditions</data> + <data key="defaultStoreView">Free Shipping in Rule conditions</data> + </entity> + <entity name="CartPriceRuleConditionNotApplied"> + <data key="name" unique="suffix">Cart Price Rule Condition Not Applied</data> + <data key="description">Description for Cart Price Rule</data> + <data key="is_active">Yes</data> + <data key="websites">Main Website</data> + <data key="customerGroups">NOT LOGGED IN</data> + <data key="coupon_type">No Coupon</data> + <data key="totalItemQuantity">3</data> + <data key="simple_action">Percent of product price discount</data> + <data key="discount_amount">50</data> + <data key="maximumQtyDiscount">0</data> + <data key="discount_step">0</data> + <data key="apply_to_shipping">0</data> + <data key="simple_free_shipping">No</data> + <data key="defaultRuleLabelAllStoreViews">Total Items Quantity-Not Applied test</data> + <data key="defaultStoreView">Total Items Quantity-Not Applied test</data> + </entity> + <entity name="CartPriceRuleConditionAppliedForWeight"> + <data key="name" unique="suffix">Cart Price Rule With Weight Condition Applied</data> + <data key="description">Description for Cart Price Rule</data> + <data key="is_active">Yes</data> + <data key="websites">Main Website</data> + <data key="customerGroups">NOT LOGGED IN</data> + <data key="coupon_type">No Coupon</data> + <data key="totalWeight">200</data> + <data key="simple_action">Percent of product price discount</data> + <data key="discount_amount">50</data> + <data key="maximumQtyDiscount">0</data> + <data key="discount_step">0</data> + <data key="apply_to_shipping">0</data> + <data key="simple_free_shipping">No</data> + <data key="defaultRuleLabelAllStoreViews">Total Weight is 200</data> + <data key="defaultStoreView">Total Weight is 200</data> + </entity> + <entity name="CartPriceRuleConditionAppliedForCategory"> + <data key="name" unique="suffix">Cart Price Rule With Category Condition Applied</data> + <data key="description">Description for Cart Price Rule</data> + <data key="is_active">Yes</data> + <data key="websites">Main Website</data> + <data key="customerGroups">NOT LOGGED IN</data> + <data key="coupon_type">No Coupon</data> + <data key="simple_action">Percent of product price discount</data> + <data key="discount_amount">50</data> + <data key="maximumQtyDiscount">0</data> + <data key="discount_step">0</data> + <data key="apply_to_shipping">0</data> + <data key="simple_free_shipping">No</data> + <data key="defaultRuleLabelAllStoreViews">Product attribute combination - Category</data> + <data key="defaultStoreView">Product attribute combination - Category</data> + </entity> + <entity name="RetailerCartPriceRule" type="SalesRule"> + <data key="name" unique="suffix">Cart Price Rule</data> + <data key="websites">Main Website</data> + <data key="customerGroups">'Retailer'</data> + <data key="apply">Percent of product price discount</data> + <data key="discountAmount">50</data> + </entity> + <entity name="SegmentCartPriceRule" type="SalesRule"> + <data key="name" unique="suffix">Cart Price Rule</data> + <data key="websites">Main Website</data> + <data key="customerGroups">'General'</data> + <data key="apply">Percent of product price discount</data> + <data key="discountAmount">50</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..2f6c3e0af4dfa 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml @@ -16,8 +16,11 @@ <!-- Rule Information (the main form on the page) --> <element name="ruleInformationHeader" type="button" selector="div[data-index='rule_information']" timeout="30"/> <element name="ruleName" type="input" selector="input[name='name']"/> + <element name="description" type="textarea" selector="//div[@class='admin__field-control']/textarea[@name='description']"/> + <element name="active" type="checkbox" selector="//div[@class='admin__actions-switch']/input[@name='is_active']/../label"/> <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']"/> @@ -26,6 +29,7 @@ <element name="userPerCoupon" type="input" selector="//input[@name='uses_per_coupon']"/> <element name="userPerCustomer" type="input" selector="//input[@name='uses_per_customer']"/> <element name="priority" type="input" selector="//*[@name='sort_order']"/> + <element name="publicInRSSFeed" type="checkbox" selector="//div[@class='admin__actions-switch']/input[@name='is_rss']/../label"/> <!-- Conditions sub-form --> <element name="conditionsHeader" type="button" selector="div[data-index='conditions']" timeout="30"/> @@ -39,6 +43,21 @@ <element name="ruleParameterInput" type="input" selector="rule[conditions][{{arg}}][value]" parameterized="true"/> <element name="openChooser" type="button" selector="//label[@for='conditions__{{arg}}__value']" parameterized="true"/> <element name="categoryCheckbox" type="checkbox" selector="//span[contains(text(), '{{arg}}')]/parent::a/preceding-sibling::input[@type='checkbox']" parameterized="true"/> + <element name="newCondition" type="button" selector=".rule-param.rule-param-new-child" timeout="30"/> + <element name="conditionSelect" type="select" selector="select[name='rule[conditions][1][new_child]']"/> + <element name="targetEllipsis" type="input" selector="//ul[contains(@id, 'conditions')]//a[.='...']"/> + <element name="ruleFieldByIndex" type="input" selector="[id='conditions__{{index}}__value']" parameterized="true"/> + <element name="addNewCondition" type="button" selector="//ul[@id = 'conditions__{{nestedIndex}}__children']/li/span[contains(@class, 'rule-param-new-child')]" parameterized="true" timeout="30"/> + <element name="conditionSelectDropdown" type="select" selector="select[name='rule[conditions][{{nestedIndex}}][new_child]']" parameterized="true"/> + <element name="selectCountryDropdown" type="select" selector="(//*[contains(@value,'country_id')]/..//select)[last()]"/> + + <!--Conditions checker --> + <element name="openList" type="button" selector="a.rule-chooser-trigger>img.v-middle.rule-chooser-trigger"/> + <element name="searchSegmentName" type="input" selector="//div[@class='rule-chooser']//input[@name='grid_segment_name']"/> + <element name="searchButton" type="button" selector="div.admin__filter-actions>button.action-default.scalable.action-secondary" timeout="10" /> + <element name="selectAll" type="checkbox" selector="//*[@class='rule-chooser']//input[contains(@class, 'admin__control-checkbox')]" timeout="10"/> + <element name="resetSearchFilter" type="button" selector="//div[@class='admin__filter-actions']//button[@title='Reset Filter']"/> + <element name="setSegment" type="button" selector="//*[@class='rule-tree-wrapper']//a[@class='rule-param-apply']"/> <!-- Actions sub-form --> <element name="actionsTab" type="text" selector="//div[@data-index='actions']//span[contains(.,'Actions')][1]"/> @@ -58,10 +77,18 @@ <element name="applyDiscountToShipping" type="checkbox" selector="input[name='apply_to_shipping']"/> <element name="applyDiscountToShippingLabel" type="checkbox" selector="input[name='apply_to_shipping']+label"/> <element name="discountAmount" type="input" selector="input[name='discount_amount']"/> + <element name="maximumQtyDiscount" type="input" selector="input[name='discount_qty']"/> <element name="discountStep" type="input" selector="input[name='discount_step']"/> + <element name="applyToShippingAmount" type="checkbox" selector="//div[@class='admin__actions-switch']/input[@name='apply_to_shipping']/../label"/> + <element name="discardSubsequentRules" type="checkbox" selector="//div[@class='admin__actions-switch']/input[@name='stop_rules_processing']/../label"/> <element name="addRewardPoints" type="input" selector="input[name='extension_attributes[reward_points_delta]']"/> <element name="freeShipping" type="select" selector="//select[@name='simple_free_shipping']"/> + <!-- Labels sub-form --> + <element name="labelsHeader" type="button" selector="div[data-index='labels']" timeout="30"/> + <element name="defaultRuleLabelAllStoreViews" type="input" selector="input[name='store_labels[0]']"/> + <element name="defaultStoreView" type="input" selector="input[name='store_labels[1]']"/> + <!-- Manage Coupon Codes sub-form --> <element name="manageCouponCodesHeader" type="button" selector="div[data-index='manage_coupon_codes']" timeout="30"/> <element name="successMessage" type="text" selector="div.message.message-success.success"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml index 14d3a734408db..60bf3d63e7e54 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml @@ -18,5 +18,6 @@ <element name="rulesRow" type="text" selector="//tr[@data-role='row']"/> <element name="pageCurrent" type="text" selector="//label[@for='promo_quote_grid_page-current']"/> <element name="totalCount" type="text" selector="span[data-ui-id*='grid-total-count']"/> + <element name="emptyText" type="text" selector="//tr[@class='data-grid-tr-no-data even']/td"/> </section> </sections> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleAndVerifyRuleConditionAndFreeShippingIsAppliedTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleAndVerifyRuleConditionAndFreeShippingIsAppliedTest.xml new file mode 100644 index 0000000000000..33184f79f6f0c --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleAndVerifyRuleConditionAndFreeShippingIsAppliedTest.xml @@ -0,0 +1,113 @@ +<?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="AdminCreateCartPriceRuleAndVerifyRuleConditionAndFreeShippingIsAppliedTest"> + <annotations> + <stories value="Create Sales Rule"/> + <title value="Create Cart Price Rule And Verify Rule Condition And Free Shipping Is Applied"/> + <description value="Test log in to Cart Price Rules and Create Cart Price Rule And Verify Rule Condition And Free Shipping Is Applied"/> + <testCaseId value="MC-15604"/> + <severity value="CRITICAL"/> + <group value="SalesRule"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="defaultSimpleProduct" stepKey="initialSimpleProduct"/> + </before> + <after> + <deleteData stepKey="deleteProduct" createDataKey="initialSimpleProduct"/> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="deleteCreatedCartPriceRule"> + <argument name="ruleName" value="{{CartPriceRuleConditionAndFreeShippingApplied.name}}"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Create cart price rule as per data and verify AssertCartPriceRuleSuccessSaveMessage--> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForPriceList"/> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{CartPriceRuleConditionAndFreeShippingApplied.name}}" stepKey="fillRuleName"/> + <fillField selector="{{AdminCartPriceRulesFormSection.description}}" userInput="{{CartPriceRuleConditionAndFreeShippingApplied.description}}" stepKey="fillDescription"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="{{CartPriceRuleConditionAndFreeShippingApplied.websites}}" stepKey="selectWebsites"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.customerGroups}}" parameterArray="[{{CartPriceRuleConditionAndFreeShippingApplied.customerGroups}}]" stepKey="selectCustomerGroup"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.coupon}}" userInput="{{CartPriceRuleConditionAndFreeShippingApplied.coupon_type}}" stepKey="selectCouponType"/> + <scrollTo selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" stepKey="scrollToActionsHeader"/> + <actionGroup ref="AdminCreateCartPriceRuleActionsSectionDiscountFieldsActionGroup" stepKey="createActiveCartPriceRuleActionsSection"> + <argument name="rule" value="CartPriceRuleConditionAndFreeShippingApplied"/> + </actionGroup> + <actionGroup ref="AdminCreateCartPriceRuleActionsSectionFreeShippingActionGroup" stepKey="createActiveCartPriceRuleFreeShippingActionsSection"> + <argument name="freeShippingOption" value="{{CartPriceRuleConditionAndFreeShippingApplied.simple_free_shipping}}"/> + </actionGroup> + <actionGroup ref="AdminCreateCartPriceRuleLabelsSectionActionGroup" stepKey="createActiveCartPriceRuleLabelsSection"> + <argument name="rule" value="CartPriceRuleConditionAndFreeShippingApplied"/> + </actionGroup> + <actionGroup ref="AssertCartPriceRuleSuccessSaveMessageActionGroup" stepKey="seeAssertCartPriceRuleSuccessSaveMessage"/> + + <!--Search created cart price rule in grid--> + <actionGroup ref="AdminFilterCartPriceRuleActionGroup" stepKey="searchCreatedCartPriceRuleInGrid"> + <argument name="ruleName" value="CartPriceRuleConditionAndFreeShippingApplied.name"/> + </actionGroup> + + <!--Go to cart price rule form page and verify AssertCartPriceRuleForm--> + <waitForPageLoad stepKey="waitForAdminCartPriceRuleEditPageLoad"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{CartPriceRuleConditionAndFreeShippingApplied.name}}" stepKey="seeRuleName"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.description}}" userInput="{{CartPriceRuleConditionAndFreeShippingApplied.description}}" stepKey="seeDescription"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="{{CartPriceRuleConditionAndFreeShippingApplied.websites}}" stepKey="seeWebsites"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.customerGroups}}" userInput="{{CartPriceRuleConditionAndFreeShippingApplied.customerGroups}}" stepKey="seeCustomerGroup"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.coupon}}" userInput="{{CartPriceRuleConditionAndFreeShippingApplied.coupon_type}}" stepKey="seeCouponType"/> + <scrollTo selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" stepKey="clickActionsHeader"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.actionsHeader}}" visible="true" stepKey="clickExpandActions"/> + <see selector="{{AdminCartPriceRulesFormSection.apply}}" userInput="{{CartPriceRuleConditionAndFreeShippingApplied.simple_action}}" stepKey="seeActionApplyType"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="{{CartPriceRuleConditionAndFreeShippingApplied.discount_amount}}" stepKey="seeDiscountAmount"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.freeShipping}}" userInput="{{CartPriceRuleConditionAndFreeShippingApplied.simple_free_shipping}}" stepKey="seeFreeShipping"/> + <scrollTo selector="{{AdminCartPriceRulesFormSection.labelsHeader}}" stepKey="clickLabelsHeader"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.labelsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.labelsHeader}}" visible="true" stepKey="clickToExpandLabels"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.defaultRuleLabelAllStoreViews}}" userInput="{{CartPriceRuleConditionAndFreeShippingApplied.defaultRuleLabelAllStoreViews}}" stepKey="seeDefaultRuleLabelAllStoreViews"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.defaultStoreView}}" userInput="{{CartPriceRuleConditionAndFreeShippingApplied.defaultStoreView}}" stepKey="seeDefaultStoreView"/> + + <!--Go to storefront page and verify created product--> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$initialSimpleProduct$$"/> + </actionGroup> + <!--Click on Add To Cart button--> + <actionGroup ref="StorefrontAddToTheCartActionGroup" stepKey="clickOnAddToCartButton"/> + <!--Click on mini cart--> + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart"/> + + <!--Open mini cart and verify Shopping cart subtotal equals to grand total--> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="verifyCartSubtotalEqualsGrandTotal"> + <argument name="productName" value="$$initialSimpleProduct.name$$"/> + <argument name="productPrice" value="$560.00"/> + <argument name="cartSubtotal" value="$560.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!--Click on view and edit cart link--> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> + <waitForPageLoad stepKey="waitForViewAndEditCartToOpen"/> + <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" visible="true" stepKey="clickEstimateShippingAndTaxToOpen"/> + <waitForPageLoad stepKey="waitForEstimateShippingAndTaxToOpen"/> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="{{US_Address_CA.country_id}}" stepKey="selectUnitedStatesCountry"/> + <waitForPageLoad stepKey="waitToSelectCountry"/> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{US_Address_CA.state}}" stepKey="selectCaliforniaRegion"/> + <waitForPageLoad stepKey="waitToSelectState"/> + <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="{{US_Address_CA.postcode}}" stepKey="inputPostCode"/> + <!--After selecting country, province and postcode, verify AssertCartPriceRuleConditionIsApplied and AssertCartPriceRuleFreeShippingIsApplied--> + <actionGroup ref="StorefrontCheckCartActionGroup" stepKey="cartAssert"> + <argument name="subtotal" value="$560.00"/> + <argument name="shipping" value="$0.00"/> + <argument name="shippingMethod" value="Flat Rate - Fixed"/> + <argument name="total" value="$280.00"/> + </actionGroup> + <see selector="{{CheckoutCartSummarySection.shipping}}" userInput="$0.00" stepKey="seeAssertFreeShippingConditionApplied"/> + <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-$280.00" stepKey="seeAssertDiscountAmountAppliedForMatchingItemsConditionIsTrue"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleAndVerifyRuleConditionIsNotAppliedTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleAndVerifyRuleConditionIsNotAppliedTest.xml new file mode 100644 index 0000000000000..22628e599823d --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleAndVerifyRuleConditionIsNotAppliedTest.xml @@ -0,0 +1,115 @@ +<?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="AdminCreateCartPriceRuleAndVerifyRuleConditionIsNotAppliedTest"> + <annotations> + <stories value="Create Sales Rule"/> + <title value="Create Cart Price Rule And Verify Rule Condition Is Not Applied"/> + <description value="Test log in to Cart Price Rules and Create Cart Price Rule And Verify Rule Condition Is Not Applied"/> + <testCaseId value="MC-15606"/> + <severity value="CRITICAL"/> + <group value="SalesRule"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="defaultSimpleProduct" stepKey="initialSimpleProduct"/> + </before> + <after> + <deleteData stepKey="deleteProduct" createDataKey="initialSimpleProduct"/> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="deleteCreatedCartPriceRule"> + <argument name="ruleName" value="{{CartPriceRuleConditionNotApplied.name}}"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Create cart price rule as per data and verify AssertCartPriceRuleSuccessSaveMessage--> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForPriceList"/> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{CartPriceRuleConditionNotApplied.name}}" stepKey="fillRuleName"/> + <fillField selector="{{AdminCartPriceRulesFormSection.description}}" userInput="{{CartPriceRuleConditionNotApplied.description}}" stepKey="fillDescription"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="{{CartPriceRuleConditionNotApplied.websites}}" stepKey="selectWebsites"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.customerGroups}}" parameterArray="[{{CartPriceRuleConditionNotApplied.customerGroups}}]" stepKey="selectCustomerGroup"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.coupon}}" userInput="{{CartPriceRuleConditionNotApplied.coupon_type}}" stepKey="selectCouponType"/> + <scrollTo selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" stepKey="scrollToConditionsHeader"/> + + <!-- Fill condition 1: Total Items Quantity --> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" visible="true" stepKey="expandConditions"/> + <click selector="{{AdminCartPriceRulesFormSection.newCondition}}" stepKey="clickNewCondition"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.conditionSelect}}" userInput="Total Items Quantity" stepKey="selectCondition"/> + <waitForPageLoad stepKey="waitForConditionLoad"/> + <click selector="{{AdminCartPriceRulesFormSection.targetEllipsis}}" stepKey="clickEllipsis"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleFieldByIndex('1--1')}}" userInput="{{CartPriceRuleConditionNotApplied.totalItemQuantity}}" stepKey="fillTotalItemQuantity"/> + <actionGroup ref="AdminCreateCartPriceRuleActionsSectionDiscountFieldsActionGroup" stepKey="createActiveCartPriceRuleActionsSection"> + <argument name="rule" value="CartPriceRuleConditionNotApplied"/> + </actionGroup> + <actionGroup ref="AdminCreateCartPriceRuleActionsSectionFreeShippingActionGroup" stepKey="createActiveCartPriceRuleFreeShippingActionsSection"> + <argument name="freeShippingOption" value="{{CartPriceRuleConditionNotApplied.simple_free_shipping}}"/> + </actionGroup> + <scrollTo selector="{{AdminCartPriceRulesFormSection.labelsHeader}}" stepKey="scrollToLabelsHeader"/> + <actionGroup ref="AdminCreateCartPriceRuleLabelsSectionActionGroup" stepKey="createActiveCartPriceRuleLabelsSection"> + <argument name="rule" value="CartPriceRuleConditionNotApplied"/> + </actionGroup> + <actionGroup ref="AssertCartPriceRuleSuccessSaveMessageActionGroup" stepKey="seeAssertCartPriceRuleSuccessSaveMessage"/> + + <!--Search created cart price rule in grid--> + <actionGroup ref="AdminFilterCartPriceRuleActionGroup" stepKey="searchCreatedCartPriceRuleInGrid"> + <argument name="ruleName" value="CartPriceRuleConditionNotApplied.name"/> + </actionGroup> + + <!--Go to cart price rule form page and verify AssertCartPriceRuleForm--> + <waitForPageLoad stepKey="waitForAdminCartPriceRuleEditPageLoad"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{CartPriceRuleConditionNotApplied.name}}" stepKey="seeRuleName"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.description}}" userInput="{{CartPriceRuleConditionNotApplied.description}}" stepKey="seeDescription"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="{{CartPriceRuleConditionNotApplied.websites}}" stepKey="seeWebsites"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.customerGroups}}" userInput="{{CartPriceRuleConditionNotApplied.customerGroups}}" stepKey="seeCustomerGroup"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.coupon}}" userInput="{{CartPriceRuleConditionNotApplied.coupon_type}}" stepKey="seeCouponType"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" visible="true" stepKey="clickToExpandConditions"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.ruleFieldByIndex('1--1')}}" userInput="{{CartPriceRuleConditionNotApplied.totalItemQuantity}}" stepKey="seeTotalItemQuantity"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.actionsHeader}}" visible="true" stepKey="clickExpandActions"/> + <see selector="{{AdminCartPriceRulesFormSection.apply}}" userInput="{{CartPriceRuleConditionNotApplied.simple_action}}" stepKey="seeActionApplyType"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="{{CartPriceRuleConditionNotApplied.discount_amount}}" stepKey="seeDiscountAmount"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.freeShipping}}" userInput="{{CartPriceRuleConditionNotApplied.simple_free_shipping}}" stepKey="seeFreeShipping"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.labelsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.labelsHeader}}" visible="true" stepKey="clickToExpandLabels"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.defaultRuleLabelAllStoreViews}}" userInput="{{CartPriceRuleConditionNotApplied.defaultRuleLabelAllStoreViews}}" stepKey="seeDefaultRuleLabelAllStoreViews"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.defaultStoreView}}" userInput="{{CartPriceRuleConditionNotApplied.defaultStoreView}}" stepKey="seeDefaultStoreView"/> + + <!--Go to storefront page and verify created product--> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$initialSimpleProduct$$"/> + </actionGroup> + <fillField selector="{{StorefrontProductInfoMainSection.qty}}" userInput="2" stepKey="fillProductQuantity"/> + <!--Click on Add To Cart button--> + <actionGroup ref="StorefrontAddToTheCartActionGroup" stepKey="clickOnAddToCartButton"/> + <!--Click on mini cart--> + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart"/> + + <!--Open mini cart and verify Shopping cart subtotal equals to grand total--> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="verifyCartSubtotalEqualsGrandTotal"> + <argument name="productName" value="$$initialSimpleProduct.name$$"/> + <argument name="productPrice" value="$560.00"/> + <argument name="cartSubtotal" value="$1,120.00" /> + <argument name="qty" value="2"/> + </actionGroup> + + <!--Click on view and edit cart link--> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> + <waitForPageLoad stepKey="waitForViewAndEditCartToOpen"/> + <!--Verify AssertCartPriceRuleConditionIsNotApplied(Shopping cart subtotal equals to grand total - price rule has not been applied)--> + <actionGroup ref="StorefrontCheckCartActionGroup" stepKey="cartAssert"> + <argument name="subtotal" value="$1,120.00"/> + <argument name="shipping" value="$10.00"/> + <argument name="shippingMethod" value="Flat Rate - Fixed"/> + <argument name="total" value="$1,130.00"/> + </actionGroup> + <dontSee selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="dontSeeAssertDiscountAmountForRuleConditionNotApplied"/> + </test> +</tests> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForMatchingSubtotalAndVerifyRuleConditionIsAppliedTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForMatchingSubtotalAndVerifyRuleConditionIsAppliedTest.xml new file mode 100644 index 0000000000000..ab62e51414e85 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForMatchingSubtotalAndVerifyRuleConditionIsAppliedTest.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="AdminCreateCartPriceRuleForMatchingSubtotalAndVerifyRuleConditionIsAppliedTest"> + <annotations> + <stories value="Create Sales Rule"/> + <title value="Create Cart Price Rule For Matching Subtotal And Verify Rule Condition Is Applied"/> + <description value="Test log in to Cart Price Rules and Create Cart Price Rule For Matching Subtotal And Verify Rule Condition Is Applied"/> + <testCaseId value="MC-15605"/> + <severity value="CRITICAL"/> + <group value="SalesRule"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="defaultSimpleProduct" stepKey="initialSimpleProduct"/> + </before> + <after> + <deleteData stepKey="deleteProduct" createDataKey="initialSimpleProduct"/> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="deleteCreatedCartPriceRule"> + <argument name="ruleName" value="{{CartPriceRuleConditionAppliedForSubtotal.name}}"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Create cart price rule as per data and verify AssertCartPriceRuleSuccessSaveMessage--> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForPriceList"/> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{CartPriceRuleConditionAppliedForSubtotal.name}}" stepKey="fillRuleName"/> + <fillField selector="{{AdminCartPriceRulesFormSection.description}}" userInput="{{CartPriceRuleConditionAppliedForSubtotal.description}}" stepKey="fillDescription"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="{{CartPriceRuleConditionAppliedForSubtotal.websites}}" stepKey="selectWebsites"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.customerGroups}}" parameterArray="[{{CartPriceRuleConditionAppliedForSubtotal.customerGroups}}]" stepKey="selectCustomerGroup"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.coupon}}" userInput="{{CartPriceRuleConditionAppliedForSubtotal.coupon_type}}" stepKey="selectCouponType"/> + + <!-- Expand the conditions section --> + <scrollTo selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" stepKey="scrollToConditionsHeader"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" visible="true" stepKey="clickToExpandConditions"/> + <click selector="{{AdminCartPriceRulesFormSection.newCondition}}" stepKey="clickNewCondition"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.conditionSelect}}" userInput="Subtotal" stepKey="selectCondition1"/> + <waitForPageLoad stepKey="waitForConditionLoad"/> + <click selector="{{AdminCartPriceRulesFormSection.targetEllipsis}}" stepKey="clickEllipsis"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleFieldByIndex('1--1')}}" userInput="{{CartPriceRuleConditionAppliedForSubtotal.subtotal}}" stepKey="fillSubtotalParameter"/> + <actionGroup ref="AdminCreateCartPriceRuleActionsSectionDiscountFieldsActionGroup" stepKey="createActiveCartPriceRuleActionsSection"> + <argument name="rule" value="CartPriceRuleConditionAppliedForSubtotal"/> + </actionGroup> + <actionGroup ref="AdminCreateCartPriceRuleActionsSectionFreeShippingActionGroup" stepKey="createActiveCartPriceRuleFreeShippingActionsSection"> + <argument name="freeShippingOption" value="{{CartPriceRuleConditionAppliedForSubtotal.simple_free_shipping}}"/> + </actionGroup> + <scrollTo selector="{{AdminCartPriceRulesFormSection.labelsHeader}}" stepKey="scrollToLabelsHeader"/> + <actionGroup ref="AdminCreateCartPriceRuleLabelsSectionActionGroup" stepKey="createActiveCartPriceRuleLabelsSection"> + <argument name="rule" value="CartPriceRuleConditionAppliedForSubtotal"/> + </actionGroup> + <actionGroup ref="AssertCartPriceRuleSuccessSaveMessageActionGroup" stepKey="seeAssertCartPriceRuleSuccessSaveMessage"/> + + <!--Search created cart price rule in grid--> + <actionGroup ref="AdminFilterCartPriceRuleActionGroup" stepKey="searchCreatedCartPriceRuleInGrid"> + <argument name="ruleName" value="CartPriceRuleConditionAppliedForSubtotal.name"/> + </actionGroup> + + <!--Go to cart price rule form page and verify AssertCartPriceRuleForm--> + <waitForPageLoad stepKey="waitForAdminCartPriceRuleEditPageLoad"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{CartPriceRuleConditionAppliedForSubtotal.name}}" stepKey="seeRuleName"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.description}}" userInput="{{CartPriceRuleConditionAppliedForSubtotal.description}}" stepKey="seeDescription"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="{{CartPriceRuleConditionAppliedForSubtotal.websites}}" stepKey="seeWebsites"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.customerGroups}}" userInput="{{CartPriceRuleConditionAppliedForSubtotal.customerGroups}}" stepKey="seeCustomerGroup"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.coupon}}" userInput="{{CartPriceRuleConditionAppliedForSubtotal.coupon_type}}" stepKey="seeCouponType"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" visible="true" stepKey="clickToExpandConditions2"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.ruleFieldByIndex('1--1')}}" userInput="{{CartPriceRuleConditionAppliedForSubtotal.subtotal}}" stepKey="seeSubtotalParameter"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.actionsHeader}}" visible="true" stepKey="clickExpandActions"/> + <see selector="{{AdminCartPriceRulesFormSection.apply}}" userInput="{{CartPriceRuleConditionAppliedForSubtotal.simple_action}}" stepKey="seeActionApplyType"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="{{CartPriceRuleConditionAppliedForSubtotal.discount_amount}}" stepKey="seeDiscountAmount"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.freeShipping}}" userInput="{{CartPriceRuleConditionAppliedForSubtotal.simple_free_shipping}}" stepKey="seeFreeShipping"/> + <scrollTo selector="{{AdminCartPriceRulesFormSection.labelsHeader}}" stepKey="scrollToTheLabelsHeader"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.labelsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.labelsHeader}}" visible="true" stepKey="clickToExpandLabels"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.defaultRuleLabelAllStoreViews}}" userInput="{{CartPriceRuleConditionAppliedForSubtotal.defaultRuleLabelAllStoreViews}}" stepKey="seeDefaultRuleLabelAllStoreViews"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.defaultStoreView}}" userInput="{{CartPriceRuleConditionAppliedForSubtotal.defaultStoreView}}" stepKey="seeDefaultStoreView"/> + + <!--Go to storefront page and verify created product--> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$initialSimpleProduct$$"/> + </actionGroup> + <!--Click on Add To Cart button--> + <actionGroup ref="StorefrontAddToTheCartActionGroup" stepKey="clickOnAddToCartButton"/> + <!--Click on mini cart--> + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart"/> + + <!--Open mini cart and verify Shopping cart subtotal equals to grand total--> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="verifyCartSubtotalEqualsGrandTotal"> + <argument name="productName" value="$$initialSimpleProduct.name$$"/> + <argument name="productPrice" value="$560.00"/> + <argument name="cartSubtotal" value="$560.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!--Click on view and edit cart link--> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> + <waitForPageLoad stepKey="waitForViewAndEditCartToOpen"/> + <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" visible="true" stepKey="clickEstimateShippingAndTaxToOpen"/> + <waitForPageLoad stepKey="waitForEstimateShippingAndTaxToOpen"/> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="{{US_Address_CA.country_id}}" stepKey="selectUnitedStatesCountry"/> + <waitForPageLoad stepKey="waitToSelectCountry"/> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{US_Address_CA.state}}" stepKey="selectCaliforniaRegion"/> + <waitForPageLoad stepKey="waitToSelectState"/> + <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="{{US_Address_CA.postcode}}" stepKey="inputPostCode"/> + <!--After selecting country, province and postcode, verify AssertCartPriceRuleConditionIsApplied if Subtotal condition is True--> + <actionGroup ref="StorefrontCheckCartActionGroup" stepKey="cartAssert"> + <argument name="subtotal" value="$560.00"/> + <argument name="shipping" value="$5.00"/> + <argument name="shippingMethod" value="Flat Rate - Fixed"/> + <argument name="total" value="$285.00"/> + </actionGroup> + <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-$280.00" stepKey="seeAssertDiscountAmountAppliedForSubtotalConditionIsTrue"/> + </test> +</tests> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleWithMatchingCategoryAndVerifyRuleConditionIsAppliedTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleWithMatchingCategoryAndVerifyRuleConditionIsAppliedTest.xml new file mode 100644 index 0000000000000..e1a4ca40fd710 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleWithMatchingCategoryAndVerifyRuleConditionIsAppliedTest.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="AdminCreateCartPriceRuleWithMatchingCategoryAndVerifyRuleConditionIsAppliedTest"> + <annotations> + <stories value="Create Sales Rule"/> + <title value="Create Cart Price Rule With Matching Category And Verify Rule Condition Is Applied"/> + <description value="Test log in to Cart Price Rules and Create Cart Price Rule With Matching Category And Verify Rule Condition Is Applied"/> + <testCaseId value="MC-15608"/> + <severity value="CRITICAL"/> + <group value="SalesRule"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <actionGroup ref="FillAdminSimpleProductForm" stepKey="fillProductFieldsInAdmin"> + <argument name="category" value="$$createCategory$$"/> + <argument name="simpleProduct" value="_defaultProduct"/> + </actionGroup> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deletePreReqCategory"/> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteSimpleProduct"> + <argument name="product" value="_defaultProduct"/> + </actionGroup> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="deleteCreatedCartPriceRule"> + <argument name="ruleName" value="{{CartPriceRuleConditionAppliedForCategory.name}}"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Create cart price rule as per data and verify AssertCartPriceRuleSuccessSaveMessage--> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForPriceList"/> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{CartPriceRuleConditionAppliedForCategory.name}}" stepKey="fillRuleName"/> + <fillField selector="{{AdminCartPriceRulesFormSection.description}}" userInput="{{CartPriceRuleConditionAppliedForCategory.description}}" stepKey="fillDescription"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="{{CartPriceRuleConditionAppliedForCategory.websites}}" stepKey="selectWebsites"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.customerGroups}}" parameterArray="[{{CartPriceRuleConditionAppliedForCategory.customerGroups}}]" stepKey="selectCustomerGroup"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.coupon}}" userInput="{{CartPriceRuleConditionAppliedForCategory.coupon_type}}" stepKey="selectCouponType"/> + <scrollTo selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" stepKey="scrollToConditionsHeader"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" visible="true" stepKey="clickToExpandConditions"/> + <click selector="{{AdminCartPriceRulesFormSection.addNewCondition('1')}}" stepKey="clickOnAddNewCondition"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.conditionSelectDropdown('1')}}" userInput="Product attribute combination" stepKey="selectProductAttributeCombination"/> + <waitForPageLoad stepKey="waitForConditionLoad"/> + <click selector="{{AdminCartPriceRulesFormSection.addNewCondition('1--1')}}" stepKey="clickOnTheAddNewCondition"/> + <waitForPageLoad stepKey="waitForToggleLoad"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.conditionSelectDropdown('1--1')}}" userInput="Category" stepKey="selectCategory"/> + <waitForElementVisible selector="{{AdminCartPriceRulesFormSection.targetEllipsis}}" stepKey="waitForEllipsis"/> + <click selector="{{AdminCartPriceRulesFormSection.targetEllipsis}}" stepKey="clickOnEllipsis"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleFieldByIndex('1--1--1')}}" userInput="$$createCategory.id$$" stepKey="fillTotalItemQuantity"/> + <actionGroup ref="AdminCreateCartPriceRuleActionsSectionDiscountFieldsActionGroup" stepKey="createActiveCartPriceRuleActionsSection"> + <argument name="rule" value="CartPriceRuleConditionAppliedForCategory"/> + </actionGroup> + <actionGroup ref="AdminCreateCartPriceRuleActionsSectionFreeShippingActionGroup" stepKey="createActiveCartPriceRuleFreeShippingActionsSection"> + <argument name="freeShippingOption" value="{{CartPriceRuleConditionAppliedForCategory.simple_free_shipping}}"/> + </actionGroup> + <scrollTo selector="{{AdminCartPriceRulesFormSection.labelsHeader}}" stepKey="scrollToLabelsHeader"/> + <actionGroup ref="AdminCreateCartPriceRuleLabelsSectionActionGroup" stepKey="createActiveCartPriceRuleLabelsSection"> + <argument name="rule" value="CartPriceRuleConditionAppliedForCategory"/> + </actionGroup> + <actionGroup ref="AssertCartPriceRuleSuccessSaveMessageActionGroup" stepKey="seeAssertCartPriceRuleSuccessSaveMessage"/> + + <!--Search created cart price rule in grid--> + <actionGroup ref="AdminFilterCartPriceRuleActionGroup" stepKey="searchCreatedCartPriceRuleInGrid"> + <argument name="ruleName" value="CartPriceRuleConditionAppliedForCategory.name"/> + </actionGroup> + + <!--Go to cart price rule form page and verify AssertCartPriceRuleForm--> + <waitForPageLoad stepKey="waitForAdminCartPriceRuleEditPageLoad"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{CartPriceRuleConditionAppliedForCategory.name}}" stepKey="seeRuleName"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.description}}" userInput="{{CartPriceRuleConditionAppliedForCategory.description}}" stepKey="seeDescription"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="{{CartPriceRuleConditionAppliedForCategory.websites}}" stepKey="seeWebsites"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.customerGroups}}" userInput="{{CartPriceRuleConditionAppliedForCategory.customerGroups}}" stepKey="seeCustomerGroup"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.coupon}}" userInput="{{CartPriceRuleConditionAppliedForCategory.coupon_type}}" stepKey="seeCouponType"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" visible="true" stepKey="clickToExpandConditionHeader"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.ruleFieldByIndex('1--1--1')}}" userInput="$$createCategory.id$$" stepKey="seeCategoryParameter"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.actionsHeader}}" visible="true" stepKey="clickExpandActions"/> + <see selector="{{AdminCartPriceRulesFormSection.apply}}" userInput="{{CartPriceRuleConditionAppliedForCategory.simple_action}}" stepKey="seeActionApplyType"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="{{CartPriceRuleConditionAppliedForCategory.discount_amount}}" stepKey="seeDiscountAmount"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.freeShipping}}" userInput="{{CartPriceRuleConditionAppliedForCategory.simple_free_shipping}}" stepKey="seeFreeShipping"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.labelsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.labelsHeader}}" visible="true" stepKey="clickToExpandLabels"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.defaultRuleLabelAllStoreViews}}" userInput="{{CartPriceRuleConditionAppliedForCategory.defaultRuleLabelAllStoreViews}}" stepKey="seeDefaultRuleLabelAllStoreViews"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.defaultStoreView}}" userInput="{{CartPriceRuleConditionAppliedForCategory.defaultStoreView}}" stepKey="seeDefaultStoreView"/> + + <!--Go to storefront page and verify created product--> + <amOnPage url="{{StorefrontProductPage.url(_defaultProduct.urlKey)}}" stepKey="onCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="openProductPageAndVerifyProduct"> + <argument name="productName" value="_defaultProduct.name"/> + </actionGroup> + + <!--Click on mini cart--> + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart"/> + + <!--Open mini cart and verify Shopping cart subtotal equals to grand total--> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="verifyCartSubtotalEqualsGrandTotal"> + <argument name="productName" value="{{_defaultProduct.name}}"/> + <argument name="productPrice" value="$123.00"/> + <argument name="cartSubtotal" value="$123.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!--Click on view and edit cart link--> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> + <waitForPageLoad stepKey="waitForViewAndEditToOpen"/> + <!--Verify AssertCartPriceRuleConditionIsApplied if condition category id is matching--> + <actionGroup ref="StorefrontCheckCartActionGroup" stepKey="cartAssert"> + <argument name="subtotal" value="$123.00"/> + <argument name="shipping" value="$5.00"/> + <argument name="shippingMethod" value="Flat Rate - Fixed"/> + <argument name="total" value="$66.50"/> + </actionGroup> + <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-$61.50" stepKey="seeAssertDiscountAmountAppliedForMatchingCategory"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleWithMatchingTotalWeightAndVerifyRuleConditionIsAppliedTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleWithMatchingTotalWeightAndVerifyRuleConditionIsAppliedTest.xml new file mode 100644 index 0000000000000..c62b0dd869281 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleWithMatchingTotalWeightAndVerifyRuleConditionIsAppliedTest.xml @@ -0,0 +1,112 @@ +<?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="AdminCreateCartPriceRuleWithMatchingTotalWeightAndVerifyRuleConditionIsAppliedTest"> + <annotations> + <stories value="Create Sales Rule"/> + <title value="Create Cart Price Rule With Matching Total Weight And Verify Rule Condition Is Applied"/> + <description value="Test log in to Cart Price Rules and Create Cart Price Rule With Matching Total Weight And Verify Rule Condition Is Applied"/> + <testCaseId value="MC-15609"/> + <severity value="CRITICAL"/> + <group value="SalesRule"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="defaultSimpleProductWeight200" stepKey="initialSimpleProduct"/> + </before> + <after> + <deleteData stepKey="deleteProduct" createDataKey="initialSimpleProduct"/> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="deleteCreatedCartPriceRule"> + <argument name="ruleName" value="{{CartPriceRuleConditionAppliedForWeight.name}}"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Create cart price rule as per data and verify AssertCartPriceRuleSuccessSaveMessage--> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForPriceList"/> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{CartPriceRuleConditionAppliedForWeight.name}}" stepKey="fillRuleName"/> + <fillField selector="{{AdminCartPriceRulesFormSection.description}}" userInput="{{CartPriceRuleConditionAppliedForWeight.description}}" stepKey="fillDescription"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="{{CartPriceRuleConditionAppliedForWeight.websites}}" stepKey="selectWebsites"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.customerGroups}}" parameterArray="[{{CartPriceRuleConditionAppliedForWeight.customerGroups}}]" stepKey="selectCustomerGroup"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.coupon}}" userInput="{{CartPriceRuleConditionAppliedForWeight.coupon_type}}" stepKey="selectCouponType"/> + <scrollTo selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" stepKey="scrollToConditionsHeader"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" visible="true" stepKey="clickToExpandConditions"/> + <click selector="{{AdminCartPriceRulesFormSection.newCondition}}" stepKey="clickOnNewCondition"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.conditionSelect}}" userInput="Total Weight" stepKey="selectTotalWeight"/> + <waitForPageLoad stepKey="waitForThirdConditionLoad"/> + <click selector="{{AdminCartPriceRulesFormSection.targetEllipsis}}" stepKey="clickOnEllipsis"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleFieldByIndex('1--1')}}" userInput="{{CartPriceRuleConditionAppliedForWeight.totalWeight}}" stepKey="fillTotalItemQuantity"/> + <actionGroup ref="AdminCreateCartPriceRuleActionsSectionDiscountFieldsActionGroup" stepKey="createActiveCartPriceRuleActionsSection"> + <argument name="rule" value="CartPriceRuleConditionAppliedForWeight"/> + </actionGroup> + <actionGroup ref="AdminCreateCartPriceRuleActionsSectionFreeShippingActionGroup" stepKey="createActiveCartPriceRuleFreeShippingActionsSection"> + <argument name="freeShippingOption" value="{{CartPriceRuleConditionAppliedForWeight.simple_free_shipping}}"/> + </actionGroup> + <scrollTo selector="{{AdminCartPriceRulesFormSection.labelsHeader}}" stepKey="scrollToLabelsHeader"/> + <actionGroup ref="AdminCreateCartPriceRuleLabelsSectionActionGroup" stepKey="createActiveCartPriceRuleLabelsSection"> + <argument name="rule" value="CartPriceRuleConditionAppliedForWeight"/> + </actionGroup> + <actionGroup ref="AssertCartPriceRuleSuccessSaveMessageActionGroup" stepKey="seeAssertCartPriceRuleSuccessSaveMessage"/> + + <!--Search created cart price rule in grid--> + <actionGroup ref="AdminFilterCartPriceRuleActionGroup" stepKey="searchCreatedCartPriceRuleInGrid"> + <argument name="ruleName" value="CartPriceRuleConditionAppliedForWeight.name"/> + </actionGroup> + + <!--Go to cart price rule form page and verify AssertCartPriceRuleForm--> + <waitForPageLoad stepKey="waitForAdminCartPriceRuleEditPageLoad"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{CartPriceRuleConditionAppliedForWeight.name}}" stepKey="seeRuleName"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.description}}" userInput="{{CartPriceRuleConditionAppliedForWeight.description}}" stepKey="seeDescription"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="{{CartPriceRuleConditionAppliedForWeight.websites}}" stepKey="seeWebsites"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.customerGroups}}" userInput="{{CartPriceRuleConditionAppliedForWeight.customerGroups}}" stepKey="seeCustomerGroup"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.coupon}}" userInput="{{CartPriceRuleConditionAppliedForWeight.coupon_type}}" stepKey="seeCouponType"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" visible="true" stepKey="clickToExpandConditionHeader"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.ruleFieldByIndex('1--1')}}" userInput="{{CartPriceRuleConditionAppliedForWeight.totalWeight}}" stepKey="seeTotalWeightParameter"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.actionsHeader}}" visible="true" stepKey="clickExpandActions"/> + <see selector="{{AdminCartPriceRulesFormSection.apply}}" userInput="{{CartPriceRuleConditionAppliedForWeight.simple_action}}" stepKey="seeActionApplyType"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="{{CartPriceRuleConditionAppliedForWeight.discount_amount}}" stepKey="seeDiscountAmount"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.freeShipping}}" userInput="{{CartPriceRuleConditionAppliedForWeight.simple_free_shipping}}" stepKey="seeFreeShipping"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.labelsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.labelsHeader}}" visible="true" stepKey="clickToExpandLabels"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.defaultRuleLabelAllStoreViews}}" userInput="{{CartPriceRuleConditionAppliedForWeight.defaultRuleLabelAllStoreViews}}" stepKey="seeDefaultRuleLabelAllStoreViews"/> + <seeInField selector="{{AdminCartPriceRulesFormSection.defaultStoreView}}" userInput="{{CartPriceRuleConditionAppliedForWeight.defaultStoreView}}" stepKey="seeDefaultStoreView"/> + + <!--Go to storefront page and verify created product--> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$initialSimpleProduct$$"/> + </actionGroup> + <!--Click on Add To Cart button--> + <actionGroup ref="StorefrontAddToTheCartActionGroup" stepKey="clickOnAddToCartButton"/> + <!--Click on mini cart--> + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart"/> + + <!--Open mini cart and verify Shopping cart subtotal equals to grand total--> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="verifyCartSubtotalEqualsGrandTotal"> + <argument name="productName" value="$$initialSimpleProduct.name$$"/> + <argument name="productPrice" value="$560.00"/> + <argument name="cartSubtotal" value="$560.00" /> + <argument name="qty" value="1"/> + </actionGroup> + + <!--Click on view and edit cart link--> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> + <waitForPageLoad stepKey="waitForViewAndEditCartToOpen"/> + <!--Verify AssertCartPriceRuleConditionIsApplied if condition Total Weight equals 200 is true--> + <actionGroup ref="StorefrontCheckCartActionGroup" stepKey="cartAssert"> + <argument name="subtotal" value="$560.00"/> + <argument name="shipping" value="$5.00"/> + <argument name="shippingMethod" value="Flat Rate - Fixed"/> + <argument name="total" value="$285.00"/> + </actionGroup> + <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-$280.00" stepKey="seeAssertDiscountAmountAppliedForWeightConditionIsTrue"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminDeleteActiveSalesRuleWithComplexConditionsAndVerifyDeleteMessageTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminDeleteActiveSalesRuleWithComplexConditionsAndVerifyDeleteMessageTest.xml new file mode 100644 index 0000000000000..7baec93c73905 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminDeleteActiveSalesRuleWithComplexConditionsAndVerifyDeleteMessageTest.xml @@ -0,0 +1,76 @@ +<?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="AdminDeleteActiveSalesRuleWithComplexConditionsAndVerifyDeleteMessageTest"> + <annotations> + <stories value="Delete Sales Rule"/> + <title value="Delete Active Sales Rule With Complex Conditions And Verify Delete Message"/> + <description value="Test log in to Cart Price Rule and Delete Active Sales Rule With Complex Conditions Test"/> + <testCaseId value="MC-15450"/> + <severity value="CRITICAL"/> + <group value="salesRule"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create active cart price rule--> + <actionGroup ref="AdminCreateCartPriceRuleRuleInfoSectionActionGroup" stepKey="createActiveCartPriceRuleRuleInfoSection"> + <argument name="ruleName" value="ActiveSalesRuleWithComplexConditions"/> + </actionGroup> + <!-- Expand the conditions section --> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" visible="true" stepKey="clickToExpandConditions"/> + <!-- Fill condition 1: Subtotal --> + <click selector="{{AdminCartPriceRulesFormSection.newCondition}}" stepKey="clickNewCondition1"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.conditionSelect}}" userInput="Subtotal" stepKey="selectCondition1"/> + <waitForPageLoad stepKey="waitForConditionLoad1"/> + <click selector="{{AdminCartPriceRulesFormSection.targetEllipsis}}" stepKey="clickEllipsis1"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleFieldByIndex('1--1')}}" userInput="{{ActiveSalesRuleWithComplexConditions.subtotal}}" stepKey="fillSubtotalParameter1"/> + <!-- Fill condition 2: Country --> + <click selector="{{AdminCartPriceRulesFormSection.newCondition}}" stepKey="clickNewCondition2"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.conditionSelect}}" userInput="Shipping Country" stepKey="selectCondition2"/> + <waitForPageLoad stepKey="waitForConditionLoad2"/> + <click selector="{{AdminCartPriceRulesFormSection.targetEllipsis}}" stepKey="clickEllipsis2"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.selectCountryDropdown}}" userInput="{{ActiveSalesRuleWithComplexConditions.shippingCountry}}" stepKey="fillShippingCountryParameter2"/> + <!-- Fill condition 3: Shipping Postcode--> + <click selector="{{AdminCartPriceRulesFormSection.newCondition}}" stepKey="clickNewCondition3"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.conditionSelect}}" userInput="Shipping Postcode" stepKey="selectCondition3"/> + <waitForPageLoad stepKey="waitForConditionLoad3"/> + <click selector="{{AdminCartPriceRulesFormSection.targetEllipsis}}" stepKey="clickEllipsis3"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleFieldByIndex('1--3')}}" userInput="{{ActiveSalesRuleWithComplexConditions.shippingPostcode}}" stepKey="fillShippingPostcodeParameter"/> + <!--Fill values for Action Section--> + <actionGroup ref="AdminCreateCartPriceRuleActionsSectionDiscountFieldsActionGroup" stepKey="createActiveCartPriceRuleActionsSection"> + <argument name="rule" value="ActiveSalesRuleWithComplexConditions"/> + </actionGroup> + <actionGroup ref="AdminCreateCartPriceRuleActionsSectionShippingAmountActionGroup" stepKey="createActiveCartPriceRuleShippingAmountActionsSection"/> + <actionGroup ref="AdminCreateCartPriceRuleActionsSectionSubsequentRulesActionGroup" stepKey="createActiveCartPriceRuleDiscardSubsequentRulesActionsSection"/> + <actionGroup ref="AdminCreateCartPriceRuleActionsSectionFreeShippingActionGroup" stepKey="createActiveCartPriceRuleFreeShippingActionsSection"> + <argument name="freeShippingOption" value="{{ActiveSalesRuleWithComplexConditions.simple_free_shipping}}"/> + </actionGroup> + <!--Fill values for Labels Section--> + <actionGroup ref="AdminCreateCartPriceRuleLabelsSectionActionGroup" stepKey="createActiveCartPriceRuleLabelsSection"> + <argument name="rule" value="ActiveSalesRuleWithComplexConditions"/> + </actionGroup> + <actionGroup ref="AssertCartPriceRuleSuccessSaveMessageActionGroup" stepKey="assertVerifyCartPriceRuleSuccessSaveMessage"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Delete active cart price rule--> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="deleteActiveCartPriceRule"> + <argument name="ruleName" value="{{ActiveSalesRuleWithComplexConditions.name}}"/> + </actionGroup> + + <!--Go to grid and verify AssertCartPriceRuleIsNotPresentedInGrid--> + <actionGroup ref="AdminCartPriceRuleNotInGridActionGroup" stepKey="searchAndVerifyActiveCartPriceRuleNotInGrid"> + <argument name="ruleName" value="ActiveSalesRuleWithComplexConditions.name"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminDeleteActiveSalesRuleWithPercentPriceAndVerifyDeleteMessageTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminDeleteActiveSalesRuleWithPercentPriceAndVerifyDeleteMessageTest.xml new file mode 100644 index 0000000000000..41e4221a2c37f --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminDeleteActiveSalesRuleWithPercentPriceAndVerifyDeleteMessageTest.xml @@ -0,0 +1,44 @@ +<?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="AdminDeleteActiveSalesRuleWithPercentPriceAndVerifyDeleteMessageTest"> + <annotations> + <stories value="Delete Sales Rule"/> + <title value="Delete Active Sales Rule With Percent Price And Verify Delete Message"/> + <description value="Test log in to Cart Price Rule and Delete Active Sales Rule Test"/> + <testCaseId value="MC-15449"/> + <severity value="CRITICAL"/> + <group value="salesRule"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create active cart price rule--> + <actionGroup ref="AdminCreateCartPriceRuleWithCouponCode" stepKey="createActiveCartPriceRule"> + <argument name="ruleName" value="ActiveSalesRuleWithPercentPriceDiscountCoupon"/> + <argument name="couponCode" value="ActiveSalesRuleWithPercentPriceDiscountCoupon.coupon_code"/> + </actionGroup> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Delete active cart price rule--> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="deleteActiveCartPriceRule"> + <argument name="ruleName" value="{{ActiveSalesRuleWithPercentPriceDiscountCoupon.name}}"/> + </actionGroup> + + <!--Go to grid and verify AssertCartPriceRuleIsNotPresentedInGrid--> + <actionGroup ref="AdminCartPriceRuleNotInGridActionGroup" stepKey="searchAndVerifyActiveCartPriceRuleNotInGrid"> + <argument name="ruleName" value="ActiveSalesRuleWithPercentPriceDiscountCoupon.name"/> + </actionGroup> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminDeleteInactiveSalesRuleAndVerifyDeleteMessageTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminDeleteInactiveSalesRuleAndVerifyDeleteMessageTest.xml new file mode 100644 index 0000000000000..1570bfbdb7a23 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminDeleteInactiveSalesRuleAndVerifyDeleteMessageTest.xml @@ -0,0 +1,65 @@ +<?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="AdminDeleteInactiveSalesRuleAndVerifyDeleteMessageTest"> + <annotations> + <stories value="Delete Sales Rule"/> + <title value="Delete Inactive Sales Rule And Verify Delete Message"/> + <description value="Test log in to Cart Price Rule and Delete Inactive Sales Rule Test"/> + <testCaseId value="MC-15451"/> + <severity value="CRITICAL"/> + <group value="salesRule"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="defaultSimpleProduct" stepKey="initialSimpleProduct"/> + <!--Create inactive cart price rule--> + <actionGroup ref="AdminInactiveCartPriceRuleActionGroup" stepKey="createInactiveCartPriceRule"> + <argument name="ruleName" value="InactiveSalesRule"/> + <argument name="custGrp" value="NOT LOGGED IN"/> + </actionGroup> + </before> + <after> + <deleteData createDataKey="initialSimpleProduct" stepKey="deleteProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Delete inactive cart price rule--> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="deleteInactiveCartPriceRule"> + <argument name="ruleName" value="{{InactiveSalesRule.name}}"/> + </actionGroup> + + <!--Go to grid and verify AssertCartPriceRuleIsNotPresentedInGrid--> + <actionGroup ref="AdminCartPriceRuleNotInGridActionGroup" stepKey="searchAndVerifyInactiveCartPriceRuleNotInGrid"> + <argument name="ruleName" value="InactiveSalesRule.name"/> + </actionGroup> + + <!--Verify customer don't see updated virtual product link on category page --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$initialSimpleProduct$$"/> + </actionGroup> + + <!--Click on Add To Cart button--> + <actionGroup ref="StorefrontAddToTheCartActionGroup" stepKey="clickOnAddToCartButton"/> + + <!--Click on mini cart--> + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart"/> + + <!--Open mini cart and verify Shopping cart subtotal equals to grand total - price rule has not been applied.--> + <actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="verifyCartSubtotalEqualsGrandTotal"> + <argument name="productName" value="$$initialSimpleProduct.name$$"/> + <argument name="productPrice" value="$560.00"/> + <argument name="cartSubtotal" value="$560.00" /> + <argument name="qty" value="1"/> + </actionGroup> + </test> +</tests> \ No newline at end of file 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/SalesRule/view/adminhtml/ui_component/sales_rule_form.xml b/app/code/Magento/SalesRule/view/adminhtml/ui_component/sales_rule_form.xml index 570eb0bf151f0..639e12006232b 100644 --- a/app/code/Magento/SalesRule/view/adminhtml/ui_component/sales_rule_form.xml +++ b/app/code/Magento/SalesRule/view/adminhtml/ui_component/sales_rule_form.xml @@ -128,7 +128,7 @@ </validation> <dataType>number</dataType> <tooltip> - <link>http://docs.magento.com/m2/ce/user_guide/configuration/scope.html</link> + <link>https://docs.magento.com/m2/ce/user_guide/configuration/scope.html</link> <description>What is this?</description> </tooltip> <label translate="true">Websites</label> diff --git a/app/code/Magento/Search/Test/Mftf/ActionGroup/AdminEditSearchTermActionGroup.xml b/app/code/Magento/Search/Test/Mftf/ActionGroup/AdminEditSearchTermActionGroup.xml new file mode 100644 index 0000000000000..de330bb24a2e6 --- /dev/null +++ b/app/code/Magento/Search/Test/Mftf/ActionGroup/AdminEditSearchTermActionGroup.xml @@ -0,0 +1,24 @@ +<?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="AdminFillAllSearchTermFieldsActionGroup"> + <arguments> + <argument name="searchTerm" type="entity"/> + </arguments> + + <fillField selector="{{AdminCatalogSearchTermNewSection.searchQuery}}" userInput="{{searchTerm.query_text}}" stepKey="fillSearchQuery1"/> + <selectOption selector="{{AdminCatalogSearchTermNewSection.store}}" userInput="{{searchTerm.store_id}}" stepKey="selectStore1"/> + <fillField selector="{{AdminCatalogSearchTermNewSection.numberOfResults}}" userInput="{{searchTerm.number_of_results}}" stepKey="fillNumberOfResults1"/> + <fillField selector="{{AdminCatalogSearchTermNewSection.numberOfUses}}" userInput="{{searchTerm.number_of_uses}}" stepKey="fillNumberOfUses1"/> + <selectOption selector="{{AdminCatalogSearchTermNewSection.displayInSuggestedTerm}}" userInput="{{searchTerm.display_in_suggested_term}}" stepKey="selectDisplayInSuggestedTerms1"/> + + <click selector="{{AdminConfigSection.saveButton}}" stepKey="clickOnSaveButton1"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Search/Test/Mftf/ActionGroup/StoreFrontSelectDropDownSearchSuggestionActionGroup.xml b/app/code/Magento/Search/Test/Mftf/ActionGroup/StoreFrontSelectDropDownSearchSuggestionActionGroup.xml new file mode 100644 index 0000000000000..7084965297c45 --- /dev/null +++ b/app/code/Magento/Search/Test/Mftf/ActionGroup/StoreFrontSelectDropDownSearchSuggestionActionGroup.xml @@ -0,0 +1,21 @@ +<?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="StoreFrontSelectDropDownSearchSuggestionActionGroup"> + <arguments> + <argument name="searchQuery" type="string"/> + </arguments> + <waitForElementVisible selector="{{StorefrontQuickSearchResultsSection.searchTextBox}}" stepKey="waitForQuickSearchToBeVisible"/> + <fillField selector="{{StorefrontQuickSearchResultsSection.searchTextBox}}" userInput="{{searchQuery}}" stepKey="fillSearchInput"/> + <waitForElementVisible selector="{{StorefrontQuickSearchSection.searchDropDownSuggestion}}" stepKey="WaitForSearchDropDownSuggestion"/> + <see selector="{{StorefrontQuickSearchSection.searchDropDownSuggestion}}" userInput="{{searchQuery}}" stepKey="seeDropDownSuggestion"/> + <click selector="{{StorefrontQuickSearchSection.searchDropDownName(searchQuery)}}" stepKey="clickOnSearchSuggestion"/> + <waitForPageLoad stepKey="waitForSelectedProductToLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Search/Test/Mftf/Data/SearchTermData.xml b/app/code/Magento/Search/Test/Mftf/Data/SearchTermData.xml index 1518adad01347..8263ff996318e 100644 --- a/app/code/Magento/Search/Test/Mftf/Data/SearchTermData.xml +++ b/app/code/Magento/Search/Test/Mftf/Data/SearchTermData.xml @@ -14,4 +14,12 @@ <data key="redirect" unique="suffix">http://example.com/</data> <data key="display_in_terms">0</data> </entity> + <entity name="UpdatedSearchTermData1" type="searchTerm"> + <data key="query_text" unique="suffix">UpdatedSearchTerm</data> + <data key="store_id">Default Store View</data> + <data key="number_of_results">1</data> + <data key="number_of_uses">20</data> + <data key="redirect_url">http://example.com</data> + <data key="display_in_suggested_term">No</data> + </entity> </entities> diff --git a/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchSection.xml b/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchSection.xml index 3c2909b59c0de..34379fd6d2e4a 100644 --- a/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchSection.xml +++ b/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchSection.xml @@ -12,5 +12,7 @@ <element name="searchPhrase" type="input" selector="#search"/> <element name="searchButton" type="button" selector="button.action.search" timeout="30"/> <element name="searchMiniForm" type="input" selector="#search_mini_form"/> + <element name="searchDropDownSuggestion" type="text" selector="//div[@id='search_autocomplete']/ul/li/span"/> + <element name="searchDropDownName" type="text" selector="//div[@id='search_autocomplete']//span[contains(., '{{searchQuery}}')]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml new file mode 100644 index 0000000000000..9e3ed2def2f87 --- /dev/null +++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml @@ -0,0 +1,77 @@ +<?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="StorefrontVerifySearchSuggestionByProductDescriptionTest"> + <annotations> + <stories value="Search Term"/> + <title value="Create search query using product description and verify search suggestions"/> + <description value="Storefront search by product description, generate search query and verify dropdown product search suggestions"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14765"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Create product with description --> + <createData entity="SimpleProductWithDescription" stepKey="simpleProduct"/> + </before> + <after> + <!--Delete create product --> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + + <!--Go to the catalog search term page --> + <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> + <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> + + <!--Filter the search term --> + <actionGroup ref="searchTermFilterBySearchQuery" stepKey="filterByThirdSearchQuery"> + <argument name="searchQuery" value="{{ApiProductDescription.value}}"/> + </actionGroup> + + <!-- Delete created below search terms --> + <actionGroup ref="deleteSearchTerm" stepKey="deleteSearchTerms"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Go to storefront home page --> + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openStoreFrontHomePage"/> + + <!--Storefront quick search by product name --> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByProductName"> + <argument name="phrase" value="{{ApiProductDescription.value}}"/> + </actionGroup> + + <!--Verify search suggestions and select the suggestion from dropdown options --> + <actionGroup ref="StoreFrontSelectDropDownSearchSuggestionActionGroup" stepKey="seeDropDownSearchSuggestion"> + <argument name="searchQuery" value="{{ApiProductDescription.value}}"/> + </actionGroup> + + <!-- Assert Product storefront main page --> + <actionGroup ref="StorefrontAssertProductNameOnProductMainPageActionGroup" stepKey="seeProductName"> + <argument name="productName" value="$$simpleProduct.name$$"/> + </actionGroup> + + <!--Go to the catalog search term page --> + <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> + <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> + + <!--Filter the search term --> + <actionGroup ref="searchTermFilterBySearchQuery" stepKey="filterByThirdSearchQuery"> + <argument name="searchQuery" value="{{ApiProductDescription.value}}"/> + </actionGroup> + + <!--Assert Search Term in grid --> + <see stepKey="seeSearchTermInGrid" selector="{{AdminCatalogSearchTermIndexSection.gridRow}}" userInput="{{ApiProductDescription.value}}" /> + <see selector="{{AdminCatalogSearchTermIndexSection.numberOfSearchTermResults}}" userInput="1" stepKey="seeNumberOfSearchTermResultInGrid"/> + </test> +</tests> diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductNameTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductNameTest.xml new file mode 100644 index 0000000000000..119faef9f2f59 --- /dev/null +++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductNameTest.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="StorefrontVerifySearchSuggestionByProductNameTest"> + <annotations> + <stories value="Search Term"/> + <title value="Create search query using product name and verify search suggestions"/> + <description value="Storefront search by product name and verify dropdown product search suggestions"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14763"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!--Create Simple Product --> + <createData entity="defaultSimpleProduct" stepKey="simpleProduct"/> + </before> + <after> + <!-- Delete create product --> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + + <!-- Go to the catalog search term page --> + <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> + <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> + + <!--Filter the search term --> + <actionGroup ref="searchTermFilterBySearchQuery" stepKey="filterByThirdSearchQuery"> + <argument name="searchQuery" value="$$simpleProduct.name$$"/> + </actionGroup> + <!-- Delete created below search terms --> + <actionGroup ref="deleteSearchTerm" stepKey="deleteSearchTerms"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Go to storefront home page --> + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openStoreFrontHomePage"/> + + <!--Storefront quick search by product name --> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByProductName"> + <argument name="phrase" value="$$simpleProduct.name$$"/> + </actionGroup> + + <!--Verify search suggestions and select the suggestion from dropdown options --> + <actionGroup ref="StoreFrontSelectDropDownSearchSuggestionActionGroup" stepKey="seeDropDownSearchSuggestion"> + <argument name="searchQuery" value="$$simpleProduct.name$$"/> + </actionGroup> + + <!-- Assert Product storefront main page --> + <actionGroup ref="StorefrontAssertProductNameOnProductMainPageActionGroup" stepKey="seeProductName"> + <argument name="productName" value="$$simpleProduct.name$$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductShortDescriptionTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductShortDescriptionTest.xml new file mode 100644 index 0000000000000..ca48fb8565ca8 --- /dev/null +++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductShortDescriptionTest.xml @@ -0,0 +1,65 @@ +<?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="StorefrontVerifySearchSuggestionByProductShortDescriptionTest"> + <annotations> + <stories value="Search Term"/> + <title value="Create search query using product short description and verify search suggestions"/> + <description value="Storefront search by product short description, generate search query and verify dropdown product search suggestions"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14766"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Create product with short description --> + <createData entity="ApiProductWithDescription" stepKey="product"/> + </before> + <after> + + <!--Delete create product --> + <deleteData createDataKey="product" stepKey="deleteProduct"/> + + <!--Go to the catalog search term page --> + <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> + <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> + + <!--Filter the search term --> + <actionGroup ref="searchTermFilterBySearchQuery" stepKey="filterByThirdSearchQuery"> + <argument name="searchQuery" value="{{ApiProductShortDescription.value}}"/> + </actionGroup> + + <!-- Delete created below search terms --> + <actionGroup ref="deleteSearchTerm" stepKey="deleteSearchTerms"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Go to storefront home page --> + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openStoreFrontHomePage"/> + + <!--Storefront quick search by product name --> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByProductName"> + <argument name="phrase" value="{{ApiProductShortDescription.value}}"/> + </actionGroup> + + <!--Verify search suggestions and select the suggestion from dropdown options --> + <actionGroup ref="StoreFrontSelectDropDownSearchSuggestionActionGroup" stepKey="seeDropDownSearchSuggestion"> + <argument name="searchQuery" value="{{ApiProductShortDescription.value}}"/> + </actionGroup> + + <!-- Assert Product storefront main page --> + <actionGroup ref="StorefrontAssertProductNameOnProductMainPageActionGroup" stepKey="seeProductName"> + <argument name="productName" value="$$product.name$$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductSkuTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductSkuTest.xml new file mode 100644 index 0000000000000..6033ea8dee28b --- /dev/null +++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductSkuTest.xml @@ -0,0 +1,65 @@ +<?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="StorefrontVerifySearchSuggestionByProductSkuTest"> + <annotations> + <stories value="Search Term"/> + <title value="Create search query using product sku and verify search suggestions"/> + <description value="Storefront search by product sku, generate search query and verify dropdown product search suggestions"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-14764"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!--Create Simple Product --> + <createData entity="defaultSimpleProduct" stepKey="simpleProduct"/> + </before> + <after> + + <!-- Delete create product --> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + + <!-- Go to the catalog search term page --> + <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> + <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> + + <!--Filter the search term --> + <actionGroup ref="searchTermFilterBySearchQuery" stepKey="filterByThirdSearchQuery"> + <argument name="searchQuery" value="$$simpleProduct.sku$$"/> + </actionGroup> + + <!-- Delete created below search terms --> + <actionGroup ref="deleteSearchTerm" stepKey="deleteSearchTerms"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Go to storefront home page --> + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openStoreFrontHomePage"/> + + <!--Storefront quick search by product name --> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByProductName"> + <argument name="phrase" value="$$simpleProduct.sku$$"/> + </actionGroup> + + <!--Verify search suggestions and select the suggestion from dropdown options --> + <actionGroup ref="StoreFrontSelectDropDownSearchSuggestionActionGroup" stepKey="seeDropDownSearchSuggestion"> + <argument name="searchQuery" value="$$simpleProduct.sku$$"/> + </actionGroup> + + <!-- Assert Product storefront main page --> + <actionGroup ref="StorefrontAssertProductNameOnProductMainPageActionGroup" stepKey="seeProductName"> + <argument name="productName" value="$$simpleProduct.name$$"/> + </actionGroup> + </test> +</tests> 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/adminhtml/ui_component/search_synonyms_form.xml b/app/code/Magento/Search/view/adminhtml/ui_component/search_synonyms_form.xml index 9194bd5f741d6..09e5149004f3e 100644 --- a/app/code/Magento/Search/view/adminhtml/ui_component/search_synonyms_form.xml +++ b/app/code/Magento/Search/view/adminhtml/ui_component/search_synonyms_form.xml @@ -72,7 +72,7 @@ </validation> <dataType>int</dataType> <tooltip> - <link>http://docs.magento.com/m2/ce/user_guide/stores/websites-stores-views.html</link> + <link>https://docs.magento.com/m2/ce/user_guide/stores/websites-stores-views.html</link> <description translate="true">You can adjust the scope of this synonym group by selecting an option from the list.</description> </tooltip> <label translate="true">Scope</label> 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/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/AdminCreateStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreViewActionGroup.xml index d5ebb9b79fe07..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"/> @@ -62,4 +65,4 @@ <waitForElementVisible selector="{{errorMessageSelector}}" stepKey="waitForErrorMessage"/> <see selector="{{errorMessageSelector}}" userInput="{{errorMessage}}" stepKey="seeErrorMessage"/> </actionGroup> -</actionGroups> \ No newline at end of file +</actionGroups> 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 index 38030f59a7a33..e86a7d1b0c394 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreConfigurationBackendActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreConfigurationBackendActionGroup.xml @@ -5,15 +5,15 @@ * 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"/> + <!-- defaultValue is blank so that it can be used in cases where there is only one store view --> + <argument name="website" type="string" defaultValue=""/> + <argument name="customStore" type="string" defaultValue=""/> + <argument name="storeView1" type="string" defaultValue=""/> + <argument name="storeView2" type="string" defaultValue=""/> </arguments> <amOnPage url="{{AdminConfigPage.url}}" stepKey="goToConfigStoreConfigurationPage"/> <waitForPageLoad stepKey="waitForSystemStoreConfigurationPageLoad"/> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreFrontendActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreFrontendActionGroup.xml index afd64e33e8b36..e635d715a58c2 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreFrontendActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStoreFrontendActionGroup.xml @@ -5,14 +5,14 @@ * 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"/> + <!-- defaultValue is blank so that it can be used in cases where there is only one store view --> + <argument name="storeView1" type="string" defaultValue=""/> + <argument name="storeView2" type="string" defaultValue=""/> </arguments> <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStorefrontPage"/> <waitForPageLoad stepKey="waitForStorefrontHomePageLoad"/> 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/AssertStorefrontStoreNotVisibleInFooterActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStorefrontStoreNotVisibleInFooterActionGroup.xml new file mode 100644 index 0000000000000..2c2e07728b60a --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStorefrontStoreNotVisibleInFooterActionGroup.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="AssertStorefrontStoreNotVisibleInFooterActionGroup"> + <arguments> + <argument name="store" type="string"/> + </arguments> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStorefrontPage"/> + <waitForPageLoad stepKey="waitForStorefrontHomePageLoad"/> + <dontSee selector="{{StorefrontFooterSection.storeLink(store)}}" stepKey="AssertStoreNotOnStorefront"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStorefrontStoreVisibleInFooterActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStorefrontStoreVisibleInFooterActionGroup.xml new file mode 100644 index 0000000000000..5c63373f59f39 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStorefrontStoreVisibleInFooterActionGroup.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="AssertStorefrontStoreVisibleInFooterActionGroup"> + <arguments> + <argument name="customStore" 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"/> + <seeElement selector="{{StorefrontFooterSection.storeLink(customStore)}}" stepKey="seeAssertStoreOnStorefront"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStorefrontStoreVisibleInHeaderActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStorefrontStoreVisibleInHeaderActionGroup.xml new file mode 100644 index 0000000000000..03745ff832382 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AssertStorefrontStoreVisibleInHeaderActionGroup.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="AssertStorefrontStoreVisibleInHeaderActionGroup"> + <arguments> + <argument name="store" type="string"/> + </arguments> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStorefrontPage"/> + <waitForPageLoad stepKey="waitForStorefrontHomePageLoad"/> + <click selector="{{StorefrontHeaderSection.storeViewSwitcher}}" stepKey="selectStoreSwitcher"/> + <waitForPageLoad stepKey="waitForFirstStoreView"/> + <see selector="{{StorefrontHeaderSection.storeViewDropdown}}" userInput="{{store}}" stepKey="seeAssertStoreViewOnStorefront"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/StoreViewDisabledErrorSaveMessageActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/StoreViewDisabledErrorSaveMessageActionGroup.xml new file mode 100644 index 0000000000000..1cf48e9c82566 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/StoreViewDisabledErrorSaveMessageActionGroup.xml @@ -0,0 +1,29 @@ +<?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="StoreViewDisabledErrorSaveMessageActionGroup"> + <arguments> + <argument name="storeGroupName" type="string"/> + <argument name="storeView" type="string"/> + <argument name="storeViewStatus" type="string"/> + </arguments> + <amOnPage url="{{AdminSystemStoreViewPage.url}}" stepKey="amOnAdminSystemStoreViewPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <selectOption userInput="{{storeGroupName}}" selector="{{AdminNewStoreSection.storeGrpDropdown}}" stepKey="selectStoreGroup"/> + <fillField userInput="{{storeView}}" selector="{{AdminNewStoreSection.storeNameTextField}}" stepKey="fillStoreViewName"/> + <fillField userInput="{{storeView}}" selector="{{AdminNewStoreSection.storeCodeTextField}}" stepKey="fillStoreViewCode"/> + <selectOption userInput="{{storeViewStatus}}" selector="{{AdminNewStoreSection.statusDropdown}}" stepKey="selectStoreViewStatus"/> + <click selector="{{AdminStoresMainActionsSection.saveButton}}" stepKey="clickSaveStoreViewButton"/> + <waitForElementVisible selector="{{AdminNewStoreSection.acceptNewStoreViewCreation}}" stepKey="waitForAcceptNewStoreViewCreationButton"/> + <conditionalClick selector="{{AdminNewStoreSection.acceptNewStoreViewCreation}}" dependentSelector="{{AdminNewStoreSection.acceptNewStoreViewCreation}}" visible="true" stepKey="clickAcceptNewStoreViewCreationButton"/> + <waitForElementVisible selector="{{AdminStoresMainActionsSection.errorMessage}}" stepKey="waitForErrorMessage"/> + <see selector="{{AdminStoresMainActionsSection.errorMessage}}" userInput="The default store cannot be disabled" stepKey="AssertStoreDisabledErrorSaveMessage"/> + </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/Data/StoreData.xml b/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml index 4e043f9ff27db..f32b6dd83a2c6 100644 --- a/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml +++ b/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml @@ -166,4 +166,22 @@ <data key="store_type">store</data> <requiredEntity type="storeGroup">SecondStoreGroup</requiredEntity> </entity> -</entities> + <entity name="storeViewDataDisabled" type="store"> + <data key="group_id">1</data> + <data key="name" unique="suffix">storeView</data> + <data key="code" unique="suffix">storeView</data> + <data key="is_active">0</data> + <data key="store_id">null</data> + <data key="store_type">store</data> + <data key="store_action">add</data> + </entity> + <entity name="storeViewGermany" type="store"> + <data key="group_id">1</data> + <data key="name" unique="suffix">DE_</data> + <data key="code" unique="suffix">de_</data> + <data key="is_active">1</data> + <data key="store_id">null</data> + <data key="store_type">store</data> + <data key="store_action">add</data> + </entity> +</entities> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml index 7e08fb999308a..e8333bd307530 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml @@ -25,5 +25,6 @@ <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/Section/AdminStoresMainActionsSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresMainActionsSection.xml index e40aa76967bec..35f4507a80f81 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresMainActionsSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresMainActionsSection.xml @@ -13,5 +13,6 @@ <element name="saveButton" type="button" selector="#save" timeout="90"/> <element name="backButton" type="button" selector="#back" timeout="30"/> <element name="deleteButton" type="button" selector="#delete" timeout="30"/> + <element name="errorMessage" type="text" selector="//div[@class='message message-error error']/div"/> </section> -</sections> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateCustomStoreViewStatusDisabledVerifyErrorSaveMessageTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateCustomStoreViewStatusDisabledVerifyErrorSaveMessageTest.xml new file mode 100644 index 0000000000000..94e723c9afafb --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateCustomStoreViewStatusDisabledVerifyErrorSaveMessageTest.xml @@ -0,0 +1,47 @@ +<?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="AdminCreateCustomStoreViewStatusDisabledVerifyErrorSaveMessageTest"> + <annotations> + <stories value="Create Store View"/> + <title value="Create Custom Store View Status Disabled and Verify Error Save Message"/> + <description value="Test log in to Stores and Create Custom Store View Status Enabled and Verify Error Save Message Test"/> + <testCaseId value="MC-14309"/> + <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> + <!-- Create store --> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createStore"> + <argument name="website" value="{{customWebsite.name}}"/> + <argument name="storeGroupName" value="{{customStore.name}}"/> + <argument name="storeGroupCode" value="{{customStore.code}}"/> + </actionGroup> + </before> + <after> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> + <argument name="websiteName" value="{{customWebsite.name}}"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Create store view selecting store created, choose disabled status and verify AssertStoreDisabledErrorSaveMessage--> + <actionGroup ref="StoreViewDisabledErrorSaveMessageActionGroup" stepKey="createStoreViewAssertStoreDisabledErrorSaveMessage"> + <argument name="storeGroupName" value="{{customStore.name}}"/> + <argument name="storeView" value="storeViewDataDisabled"/> + <argument name="storeViewStatus" value="{{storeViewDataDisabled.is_active}}"/> + </actionGroup> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateCustomStoreViewStatusEnabledVerifyAbsenceOfDeleteButtonTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateCustomStoreViewStatusEnabledVerifyAbsenceOfDeleteButtonTest.xml new file mode 100644 index 0000000000000..7cd44d4d0ae88 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateCustomStoreViewStatusEnabledVerifyAbsenceOfDeleteButtonTest.xml @@ -0,0 +1,59 @@ +<?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="AdminCreateCustomStoreViewStatusEnabledVerifyAbsenceOfDeleteButtonTest"> + <annotations> + <stories value="Create Store View"/> + <title value="Create Custom Store View Status Enabled and Verify Absence Of Delete Button"/> + <description value="Test log in to Stores and Create Custom Store View Status Enabled and Verify Absence Of Delete Button Test"/> + <testCaseId value="MC-14308"/> + <severity value="CRITICAL"/> + <group value="store"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <!-- Create 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> + </before> + <after> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteStore"> + <argument name="storeGroupName" value="customStore.name"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Create custom store view--> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView"> + <argument name="StoreGroup" value="customStore"/> + <argument name="customStore" value="storeViewData"/> + </actionGroup> + + <!--Save the above store view and verify AssertStoreViewSuccessSaveMessage--> + <actionGroup ref="AdminCreateStoreViewActionSaveGroup" stepKey="verifyAssertStoreViewSuccessSaveMessage"/> + + <!--Search store view(from above step) in grid--> + <actionGroup ref="AssertStoreViewInGridActionGroup" stepKey="searchStoreViewInGrid"> + <argument name="storeViewName" value="{{storeViewData.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="{{storeViewData.name}}"/> + <argument name="storeViewCode" value="{{storeViewData.code}}"/> + <argument name="status" value="Enabled"/> + </actionGroup> + <!--Go to store view form page and verify AssertStoreNoDeleteButton--> + <dontSee selector="{{AdminNewStoreViewActionsSection.delete}}" stepKey="AssertStoreNoDeleteButton"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateCustomStoreViewStatusEnabledVerifyBackendAndFrontendTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateCustomStoreViewStatusEnabledVerifyBackendAndFrontendTest.xml new file mode 100644 index 0000000000000..18485abadc008 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateCustomStoreViewStatusEnabledVerifyBackendAndFrontendTest.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="AdminCreateCustomStoreViewStatusEnabledVerifyBackendAndFrontendTest"> + <annotations> + <stories value="Create Store View"/> + <title value="Create Custom Store View Status Enabled and Verify Backend and Frontend"/> + <description value="Test log in to Stores and Create Custom Store View Status Enabled and Verify Backend and Frontend Test"/> + <testCaseId value="MC-14306"/> + <severity value="CRITICAL"/> + <group value="store"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <!-- Create store --> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createStore"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + <argument name="storeGroupName" value="{{customStore.name}}"/> + <argument name="storeGroupCode" value="{{customStore.code}}"/> + </actionGroup> + </before> + <after> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteStore"> + <argument name="storeGroupName" value="customStore.name"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Create custom store view--> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView"> + <argument name="StoreGroup" value="customStore"/> + <argument name="customStore" value="storeViewData"/> + </actionGroup> + + <!--Save the above store view and verify AssertStoreViewSuccessSaveMessage--> + <actionGroup ref="AdminCreateStoreViewActionSaveGroup" stepKey="verifyAssertStoreViewSuccessSaveMessage"/> + + <!--Search store view(from above step) in grid and verify AssertStoreInGrid--> + <actionGroup ref="AssertStoreViewInGridActionGroup" stepKey="searchStoreViewInGrid"> + <argument name="storeViewName" value="{{storeViewData.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="{{storeViewData.name}}"/> + <argument name="storeViewCode" value="{{storeViewData.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="{{storeViewData.name}}"/> + </actionGroup> + + <!--Go to storefront and verify AssertStoreFrontend--> + <actionGroup ref="AssertStorefrontStoreVisibleInFooterActionGroup" stepKey="verifyValuesOnStoreFrontend"> + <argument name="customStore" value="{{customStore.name}}"/> + </actionGroup> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateNewLocalizedStoreViewStatusEnabledTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateNewLocalizedStoreViewStatusEnabledTest.xml new file mode 100644 index 0000000000000..8e9fe1e71ceb4 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateNewLocalizedStoreViewStatusEnabledTest.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="AdminCreateNewLocalizedStoreViewStatusEnabledTest"> + <annotations> + <stories value="Create Store View"/> + <title value="Create New Localized Store View Status Enabled and Verify Save Message"/> + <description value="Test log in to Stores and Create New Localized Store View Status Enabled Test"/> + <testCaseId value="MC-14307"/> + <severity value="CRITICAL"/> + <group value="store"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView"> + <argument name="customStore" value="storeViewGermany"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Create new localized store view--> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView"> + <argument name="StoreGroup" value="_defaultStoreGroup"/> + <argument name="customStore" value="storeViewGermany"/> + </actionGroup> + + <!--Save the above store view and verify AssertStoreViewSuccessSaveMessage--> + <actionGroup ref="AdminCreateStoreViewActionSaveGroup" stepKey="verifyAssertStoreViewSuccessSaveMessage"/> + + <!--Search store view(from above step) in grid and verify AssertStoreInGrid--> + <actionGroup ref="AssertStoreViewInGridActionGroup" stepKey="searchStoreViewInGrid"> + <argument name="storeViewName" value="{{storeViewGermany.name}}"/> + </actionGroup> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreViewStatusDisabledVerifyBackendAndFrontendTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreViewStatusDisabledVerifyBackendAndFrontendTest.xml new file mode 100644 index 0000000000000..6ccab965b7727 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreViewStatusDisabledVerifyBackendAndFrontendTest.xml @@ -0,0 +1,49 @@ +<?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="AdminCreateStoreViewStatusDisabledVerifyBackendAndFrontendTest"> + <annotations> + <stories value="Create Store View"/> + <title value="Create Store View Status Disabled and Verify Backend and Frontend"/> + <description value="Test log in to Stores and Create Store View Status Disabled Test"/> + <testCaseId value="MC-14305"/> + <severity value="CRITICAL"/> + <group value="store"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView"> + <argument name="customStore" value="storeViewDataDisabled"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Create store view--> + <actionGroup ref="CreateStoreView" stepKey="createStoreView"> + <argument name="storeView" value="storeViewDataDisabled"/> + <argument name="storeGroupName" value="_defaultStoreGroup.name"/> + <argument name="storeViewStatus" value="storeViewDataDisabled.is_active"/> + </actionGroup> + + <!--Save the above store view and verify AssertStoreViewSuccessSaveMessage--> + <actionGroup ref="AdminCreateStoreViewActionSaveGroup" stepKey="verifyAssertStoreViewSuccessSaveMessage"/> + + <!--Go to store configuration page and verify AssertStoreBackend--> + <actionGroup ref="AssertStoreConfigurationBackendActionGroup" stepKey="verifyValuesOnStoreBackend"> + <argument name="storeView1" value="{{storeViewDataDisabled.name}}"/> + </actionGroup> + + <!--Go to storefront and verify AssertStoreNotOnFrontend--> + <actionGroup ref="AssertStorefrontStoreNotVisibleInFooterActionGroup" stepKey="verifyValuesNotOnStorefront"> + <argument name="store" value="{{storeViewDataDisabled.name}"/> + </actionGroup> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreViewStatusEnabledVerifyBackendAndFrontendTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreViewStatusEnabledVerifyBackendAndFrontendTest.xml new file mode 100644 index 0000000000000..c2f12bd2673f7 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreViewStatusEnabledVerifyBackendAndFrontendTest.xml @@ -0,0 +1,48 @@ +<?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="AdminCreateStoreViewStatusEnabledVerifyBackendAndFrontendTest"> + <annotations> + <stories value="Create Store View"/> + <title value="Create Store View Status Enabled and Verify Backend and Frontend"/> + <description value="Test log in to Stores and Create Store View Status Enabled Test"/> + <testCaseId value="MC-14304"/> + <severity value="CRITICAL"/> + <group value="store"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView"> + <argument name="customStore" value="storeViewData"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Create store view--> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView"> + <argument name="StoreGroup" value="_defaultStoreGroup"/> + <argument name="customStore" value="storeViewData"/> + </actionGroup> + + <!--Save the above store view and verify AssertStoreViewSuccessSaveMessage--> + <actionGroup ref="AdminCreateStoreViewActionSaveGroup" stepKey="verifyAssertStoreViewSuccessSaveMessage"/> + + <!--Go to store configuration page and verify AssertStoreBackend--> + <actionGroup ref="AssertStoreConfigurationBackendActionGroup" stepKey="verifyValuesOnStoreBackend"> + <argument name="storeView1" value="{{storeViewData.name}}"/> + </actionGroup> + + <!--Go to storefront and verify AssertStoreFrontend--> + <actionGroup ref="AssertStorefrontStoreVisibleInHeaderActionGroup" stepKey="verifyValuesOnStoreFrontend"> + <argument name="store" value="{{storeViewData.name}}"/> + </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/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/Swatches/Test/Mftf/Test/StorefrontSwatchAttributesDisplayInWidgetCMSTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSwatchAttributesDisplayInWidgetCMSTest.xml index 1ab2cd793f3b8..323b10507fe28 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSwatchAttributesDisplayInWidgetCMSTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSwatchAttributesDisplayInWidgetCMSTest.xml @@ -11,6 +11,7 @@ <test name="StorefrontSwatchAttributesDisplayInWidgetCMSTest"> <annotations> <features value="ConfigurableProduct"/> + <stories value="Swatches"/> <title value="Swatch Attribute is not displayed in the Widget CMS"/> <description value="Swatch Attribute is not displayed in the Widget CMS"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js index 2571c0385dab7..cc77c07ad39e6 100644 --- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js +++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js @@ -697,7 +697,7 @@ define([ */ _sortImages: function (images) { return _.sortBy(images, function (image) { - return image.position; + return parseInt(image.position, 10); }); }, diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxCalcWithApplyTaxOnSettingTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxCalcWithApplyTaxOnSettingTest.xml index 732470d2558c7..d249af983f715 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxCalcWithApplyTaxOnSettingTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxCalcWithApplyTaxOnSettingTest.xml @@ -10,7 +10,8 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminTaxCalcWithApplyTaxOnSettingTest"> <annotations> - <features value="AdminTaxCalcWithApplyTaxOnSettingTest"/> + <features value="Tax"/> + <stories value="Tax calculation"/> <title value="Tax calculation process following 'Apply Tax On' setting"/> <description value="Tax calculation process following 'Apply Tax On' setting"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Tax/etc/config.xml b/app/code/Magento/Tax/etc/config.xml index 0b837944a3614..afe7bd7861168 100644 --- a/app/code/Magento/Tax/etc/config.xml +++ b/app/code/Magento/Tax/etc/config.xml @@ -49,7 +49,7 @@ <zero_tax>0</zero_tax> </sales_display> <notification> - <info_url>http://docs.magento.com/m2/ce/user_guide/tax/warning-messages.html</info_url> + <info_url>https://docs.magento.com/m2/ce/user_guide/tax/warning-messages.html</info_url> </notification> </tax> </default> 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/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/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/Test/AdminGridFilterDeleteAndVerifyErrorMessageTest.xml b/app/code/Magento/Ui/Test/Mftf/Test/AdminGridFilterDeleteAndVerifyErrorMessageTest.xml new file mode 100644 index 0000000000000..e2c3b157c1059 --- /dev/null +++ b/app/code/Magento/Ui/Test/Mftf/Test/AdminGridFilterDeleteAndVerifyErrorMessageTest.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="AdminGridFilterDeleteAndVerifyErrorMessageTest"> + <annotations> + <stories value="Delete Grid Filter"/> + <title value="Grid Filter Delete and Verify Error Message"/> + <description value="Test log in to uI and Delete Grid Filter Test"/> + <testCaseId value="MC-14272"/> + <severity value="CRITICAL"/> + <group value="uI"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <magentoCLI command="config:set system/backup/functionality_enabled 1" stepKey="setEnableBackupToYes"/> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="NewRootCategory" stepKey="rootCategory"/> + <createData entity="defaultSimpleProduct" stepKey="createProduct"> + <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 second store --> + <actionGroup ref="CreateCustomStore" stepKey="createCustomStore"> + <argument name="website" value="{{customWebsite.name}}"/> + <argument name="store" value="{{customStoreGroup.name}}"/> + <argument name="rootCategory" value="$$rootCategory.name$$"/> + </actionGroup> + <!-- Create second store view --> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createCustomStoreView"> + <argument name="StoreGroup" value="customStoreGroup"/> + <argument name="customStore" value="customStoreEN"/> + </actionGroup> + </before> + <after> + <magentoCLI command="config:set system/backup/functionality_enabled 0" stepKey="setEnableBackupToNo"/> + <deleteData stepKey="deleteRootCategory" createDataKey="rootCategory"/> + <deleteData stepKey="deleteProduct" createDataKey="createProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Filter created simple product in grid and add category and website created in create data--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="openProductCatalogPage"/> + <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterProduct"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowOfCreatedSimpleProduct"/> + <waitForPageLoad stepKey="waitUntilProductIsOpened"/> + <actionGroup ref="AddWebsiteToProductActionGroup" stepKey="updateSimpleProductAddingWebsiteCreated"> + <argument name="website" value="{{customWebsite.name}}"/> + </actionGroup> + + <!--Search updated simple product(from above step) in the grid by StoreView and Name--> + <actionGroup ref="FilterProductInGridByStoreViewAndNameActionGroup" stepKey="searchCreatedSimpleProductInGrid"> + <argument name="storeView" value="{{customStoreEN.name}}"/> + <argument name="productName" value="$$createProduct.name$$"/> + </actionGroup> + + <!--Go to stores and delete website created in create data--> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> + <argument name="websiteName" value="{{customWebsite.name}}"/> + </actionGroup> + + <!--Go to grid page and verify AssertErrorMessage--> + <actionGroup ref="AssertErrorMessageAfterDeletingWebsiteActionGroup" stepKey="verifyErrorMessage"> + <argument name="errorMessage" value="Something went wrong with processing the default view and we have restored the filter to its original state."/> + </actionGroup> + </test> +</tests> \ No newline at end of file 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/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/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/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/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/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 8289b3e730d5d..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,7 +11,7 @@ <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> 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/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/ActionGroup/AdminCreateAndSaveWidgetActionGroup.xml b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminCreateAndSaveWidgetActionGroup.xml new file mode 100644 index 0000000000000..2f144894111bc --- /dev/null +++ b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminCreateAndSaveWidgetActionGroup.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="AdminCreateAndSaveWidgetActionGroup" extends="AdminCreateWidgetActionGroup"> + <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSaveWidget"/> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="The widget instance has been saved" stepKey="seeSuccess"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Widget/Test/Mftf/Data/WidgetsData.xml b/app/code/Magento/Widget/Test/Mftf/Data/WidgetsData.xml index 27222298408de..ffd2d025548cf 100644 --- a/app/code/Magento/Widget/Test/Mftf/Data/WidgetsData.xml +++ b/app/code/Magento/Widget/Test/Mftf/Data/WidgetsData.xml @@ -32,4 +32,15 @@ <data key="display_mode">Cart Price Rule Related</data> <data key="restrict_type">Header</data> </entity> + <entity name="DefaultOrderBySKUWidget" type="widget"> + <data key="type">Order by SKU</data> + <data key="design_theme">Magento Luma</data> + <data key="name" unique="suffix">Test Widget</data> + <array key="store_ids"> + <item>All Store Views</item> + </array> + <data key="sort_order">0</data> + <data key="display_on">All Pages</data> + <data key="container">Sidebar Main</data> + </entity> </entities> 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/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_Catalog/web/css/source/module/_listings.less b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_listings.less index b7af69fd5ca82..f96538b59a70f 100644 --- a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_listings.less +++ b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_listings.less @@ -104,6 +104,7 @@ .actions-primary { display: inline-block; + vertical-align: middle; } } 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 ddf33de6820e7..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, 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_Catalog/web/css/source/module/_listings.less b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less index 77fb53a2ab02a..eeec441c74aca 100644 --- a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less +++ b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less @@ -107,6 +107,7 @@ .actions-primary { display: inline-block; + vertical-align: middle; } } diff --git a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_toolbar.less b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_toolbar.less index 6bddc46003cbf..b587c169f67eb 100644 --- a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_toolbar.less +++ b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_toolbar.less @@ -84,7 +84,7 @@ .page-products & { position: absolute; - right: @indent__s; + right: 0; top: 0; z-index: 1; } diff --git a/app/design/frontend/Magento/luma/Magento_LayeredNavigation/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_LayeredNavigation/web/css/source/_module.less index 76baec87e0054..423ccc075553e 100644 --- a/app/design/frontend/Magento/luma/Magento_LayeredNavigation/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_LayeredNavigation/web/css/source/_module.less @@ -364,10 +364,10 @@ content: ''; display: block; height: 40px; - left: -15px; + left: 0; margin-top: -60px; position: relative; - width: 100px; + width: 75px; z-index: 99; } } 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/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 47e7e419e3b50..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" /> @@ -1740,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> @@ -1765,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 cdbadfcd6b9b6..5d6633cee775f 100644 --- a/composer.json +++ b/composer.json @@ -160,6 +160,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 db753d6de6cff..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": "d5945e7c615def5bc906a9e876235934", + "content-hash": "597fe6a47b695221292482fead498d83", "packages": [ { "name": "braintree/braintree_php", @@ -164,21 +164,21 @@ }, { "name": "colinmollenhour/php-redis-session-abstract", - "version": "v1.4.0", + "version": "v1.4.1", "source": { "type": "git", "url": "https://github.com/colinmollenhour/php-redis-session-abstract.git", - "reference": "4cb15d557f58f45ad257cbcce3c12511e6deb5bc" + "reference": "4949ca28b86037abb44984c77bab9d0a4e075643" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/colinmollenhour/php-redis-session-abstract/zipball/4cb15d557f58f45ad257cbcce3c12511e6deb5bc", - "reference": "4cb15d557f58f45ad257cbcce3c12511e6deb5bc", + "url": "https://api.github.com/repos/colinmollenhour/php-redis-session-abstract/zipball/4949ca28b86037abb44984c77bab9d0a4e075643", + "reference": "4949ca28b86037abb44984c77bab9d0a4e075643", "shasum": "" }, "require": { "colinmollenhour/credis": "~1.6", - "php": "~5.5.0|~5.6.0|~7.0.0|~7.1.0|~7.2.0" + "php": "^5.5 || ^7.0" }, "type": "library", "autoload": { @@ -197,7 +197,7 @@ ], "description": "A Redis-based session handler with optimistic locking", "homepage": "https://github.com/colinmollenhour/php-redis-session-abstract", - "time": "2018-03-29T15:54:15+00:00" + "time": "2019-03-18T14:43:14+00:00" }, { "name": "composer/ca-bundle", @@ -337,16 +337,16 @@ }, { "name": "composer/semver", - "version": "1.4.2", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573" + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573", + "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e", "shasum": "" }, "require": { @@ -395,28 +395,27 @@ "validation", "versioning" ], - "time": "2016-08-30T16:08:34+00:00" + "time": "2019-03-19T17:25:45+00:00" }, { "name": "composer/spdx-licenses", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2" + "reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7a9556b22bd9d4df7cad89876b00af58ef20d3a2", - "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d", + "reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", - "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7" }, "type": "library", "extra": { @@ -456,7 +455,7 @@ "spdx", "validator" ], - "time": "2018-11-01T09:45:54+00:00" + "time": "2019-03-26T10:23:26+00:00" }, { "name": "composer/xdebug-handler", @@ -1106,21 +1105,21 @@ }, { "name": "paragonie/sodium_compat", - "version": "v1.8.1", + "version": "v1.9.1", "source": { "type": "git", "url": "https://github.com/paragonie/sodium_compat.git", - "reference": "57bb5ef079d3724148da3d5c99e30695ab17afda" + "reference": "87125d5b265f98c4d1b8d83a1f0726607c229421" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/57bb5ef079d3724148da3d5c99e30695ab17afda", - "reference": "57bb5ef079d3724148da3d5c99e30695ab17afda", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/87125d5b265f98c4d1b8d83a1f0726607c229421", + "reference": "87125d5b265f98c4d1b8d83a1f0726607c229421", "shasum": "" }, "require": { "paragonie/random_compat": ">=1", - "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7" + "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8" }, "require-dev": { "phpunit/phpunit": "^3|^4|^5" @@ -1184,7 +1183,7 @@ "secret-key cryptography", "side-channel resistant" ], - "time": "2019-01-03T21:00:55+00:00" + "time": "2019-03-20T17:19:05+00:00" }, { "name": "pelago/emogrifier", @@ -1382,16 +1381,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "2.0.14", + "version": "2.0.15", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "8ebfcadbf30524aeb75b2c446bc2519d5b321478" + "reference": "11cf67cf78dc4acb18dc9149a57be4aee5036ce0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/8ebfcadbf30524aeb75b2c446bc2519d5b321478", - "reference": "8ebfcadbf30524aeb75b2c446bc2519d5b321478", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/11cf67cf78dc4acb18dc9149a57be4aee5036ce0", + "reference": "11cf67cf78dc4acb18dc9149a57be4aee5036ce0", "shasum": "" }, "require": { @@ -1470,7 +1469,7 @@ "x.509", "x509" ], - "time": "2019-01-27T19:37:29+00:00" + "time": "2019-03-10T16:53:45+00:00" }, { "name": "psr/container", @@ -2127,16 +2126,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + "reference": "82ebae02209c21113908c229e9883c419720738a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", "shasum": "" }, "require": { @@ -2148,7 +2147,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -2170,7 +2169,7 @@ }, { "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "email": "backendtea@gmail.com" } ], "description": "Symfony polyfill for ctype functions", @@ -2181,20 +2180,20 @@ "polyfill", "portable" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", "shasum": "" }, "require": { @@ -2206,7 +2205,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -2240,7 +2239,7 @@ "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/process", @@ -4965,16 +4964,16 @@ }, { "name": "consolidation/annotated-command", - "version": "2.11.2", + "version": "2.12.0", "source": { "type": "git", "url": "https://github.com/consolidation/annotated-command.git", - "reference": "004af26391cd7d1cd04b0ac736dc1324d1b4f572" + "reference": "512a2e54c98f3af377589de76c43b24652bcb789" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/004af26391cd7d1cd04b0ac736dc1324d1b4f572", - "reference": "004af26391cd7d1cd04b0ac736dc1324d1b4f572", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/512a2e54c98f3af377589de76c43b24652bcb789", + "reference": "512a2e54c98f3af377589de76c43b24652bcb789", "shasum": "" }, "require": { @@ -5057,7 +5056,7 @@ } ], "description": "Initialize Symfony Console commands from annotated command class methods.", - "time": "2019-02-02T02:29:53+00:00" + "time": "2019-03-08T16:55:03+00:00" }, { "name": "consolidation/config", @@ -5232,16 +5231,16 @@ }, { "name": "consolidation/output-formatters", - "version": "3.4.0", + "version": "3.4.1", "source": { "type": "git", "url": "https://github.com/consolidation/output-formatters.git", - "reference": "a942680232094c4a5b21c0b7e54c20cce623ae19" + "reference": "0881112642ad9059071f13f397f571035b527cb9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/a942680232094c4a5b21c0b7e54c20cce623ae19", - "reference": "a942680232094c4a5b21c0b7e54c20cce623ae19", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/0881112642ad9059071f13f397f571035b527cb9", + "reference": "0881112642ad9059071f13f397f571035b527cb9", "shasum": "" }, "require": { @@ -5251,11 +5250,10 @@ "symfony/finder": "^2.5|^3|^4" }, "require-dev": { - "g1a/composer-test-scenarios": "^2", + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", "phpunit/phpunit": "^5.7.27", - "satooshi/php-coveralls": "^2", "squizlabs/php_codesniffer": "^2.7", - "symfony/console": "3.2.3", "symfony/var-dumper": "^2.8|^3|^4", "victorjonsson/markdowndocs": "^1.3" }, @@ -5264,6 +5262,52 @@ }, "type": "library", "extra": { + "scenarios": { + "symfony4": { + "require": { + "symfony/console": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^6" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony3": { + "require": { + "symfony/console": "^3.4", + "symfony/finder": "^3.4", + "symfony/var-dumper": "^3.4" + }, + "config": { + "platform": { + "php": "5.6.32" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + }, + "scenario-options": { + "create-lockfile": "false" + } + } + }, "branch-alias": { "dev-master": "3.x-dev" } @@ -5284,25 +5328,25 @@ } ], "description": "Format text by applying transformations provided by plug-in formatters.", - "time": "2018-10-19T22:35:38+00:00" + "time": "2019-03-14T03:45:44+00:00" }, { "name": "consolidation/robo", - "version": "1.4.6", + "version": "1.4.9", "source": { "type": "git", "url": "https://github.com/consolidation/Robo.git", - "reference": "d4805a1abbc730e9a6d64ede2eba56f91a2b4eb3" + "reference": "5c6b3840a45afda1cbffbb3bb1f94dd5f9f83345" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/Robo/zipball/d4805a1abbc730e9a6d64ede2eba56f91a2b4eb3", - "reference": "d4805a1abbc730e9a6d64ede2eba56f91a2b4eb3", + "url": "https://api.github.com/repos/consolidation/Robo/zipball/5c6b3840a45afda1cbffbb3bb1f94dd5f9f83345", + "reference": "5c6b3840a45afda1cbffbb3bb1f94dd5f9f83345", "shasum": "" }, "require": { "consolidation/annotated-command": "^2.10.2", - "consolidation/config": "^1.0.10", + "consolidation/config": "^1.2", "consolidation/log": "~1", "consolidation/output-formatters": "^3.1.13", "consolidation/self-update": "^1", @@ -5392,7 +5436,7 @@ } ], "description": "Modern task runner", - "time": "2019-02-17T05:32:27+00:00" + "time": "2019-03-19T18:07:19+00:00" }, { "name": "consolidation/self-update", @@ -5505,16 +5549,16 @@ }, { "name": "doctrine/annotations", - "version": "v1.6.0", + "version": "v1.6.1", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5" + "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", - "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/53120e0eb10355388d6ccbe462f1fea34ddadb24", + "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24", "shasum": "" }, "require": { @@ -5569,38 +5613,40 @@ "docblock", "parser" ], - "time": "2017-12-06T07:11:42+00:00" + "time": "2019-03-25T19:12:02+00:00" }, { "name": "doctrine/collections", - "version": "v1.5.0", + "version": "v1.6.1", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf" + "reference": "d2ae4ef05e25197343b6a39bae1d3c427a2f6956" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/a01ee38fcd999f34d9bfbcee59dbda5105449cbf", - "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf", + "url": "https://api.github.com/repos/doctrine/collections/zipball/d2ae4ef05e25197343b6a39bae1d3c427a2f6956", + "reference": "d2ae4ef05e25197343b6a39bae1d3c427a2f6956", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.1.3" }, "require-dev": { - "doctrine/coding-standard": "~0.1@dev", - "phpunit/phpunit": "^5.7" + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan-shim": "^0.9.2", + "phpunit/phpunit": "^7.0", + "vimeo/psalm": "^3.2.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "1.6.x-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Collections\\": "lib/" + "psr-4": { + "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" } }, "notification-url": "https://packagist.org/downloads/", @@ -5629,38 +5675,41 @@ "email": "schmittjoh@gmail.com" } ], - "description": "Collections Abstraction library", - "homepage": "http://www.doctrine-project.org", + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", "keywords": [ "array", "collections", - "iterator" + "iterators", + "php" ], - "time": "2017-07-22T10:37:32+00:00" + "time": "2019-03-25T19:03:48+00:00" }, { "name": "doctrine/instantiator", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + "reference": "a2c590166b2133a4633738648b6b064edae0814a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", "shasum": "" }, "require": { "php": "^7.1" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "doctrine/coding-standard": "^6.0", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "^6.2.3", - "squizlabs/php_codesniffer": "^3.0.2" + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { @@ -5685,12 +5734,12 @@ } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], - "time": "2017-07-22T11:58:36+00:00" + "time": "2019-03-17T17:37:11+00:00" }, { "name": "doctrine/lexer", @@ -6620,16 +6669,16 @@ }, { "name": "magento/magento-coding-standard", - "version": "1.0.2", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/magento/magento-coding-standard.git", - "reference": "f7de26fb6add389d1b42286f67ee87424588a868" + "reference": "489029a285c637825294e272d31c3f4ac00a454e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/magento-coding-standard/zipball/f7de26fb6add389d1b42286f67ee87424588a868", - "reference": "f7de26fb6add389d1b42286f67ee87424588a868", + "url": "https://api.github.com/repos/magento/magento-coding-standard/zipball/489029a285c637825294e272d31c3f4ac00a454e", + "reference": "489029a285c637825294e272d31c3f4ac00a454e", "shasum": "" }, "require": { @@ -6646,7 +6695,7 @@ "AFL-3.0" ], "description": "A set of Magento specific PHP CodeSniffer rules.", - "time": "2019-04-05T19:05:17+00:00" + "time": "2019-04-01T17:03:33+00:00" }, { "name": "magento/magento2-functional-testing-framework", @@ -9042,16 +9091,16 @@ }, { "name": "symfony/polyfill-php70", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "6b88000cdd431cd2e940caa2cb569201f3f84224" + "reference": "bc4858fb611bda58719124ca079baff854149c89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/6b88000cdd431cd2e940caa2cb569201f3f84224", - "reference": "6b88000cdd431cd2e940caa2cb569201f3f84224", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/bc4858fb611bda58719124ca079baff854149c89", + "reference": "bc4858fb611bda58719124ca079baff854149c89", "shasum": "" }, "require": { @@ -9061,7 +9110,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -9097,20 +9146,20 @@ "portable", "shim" ], - "time": "2018-09-21T06:26:08+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631" + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9050816e2ca34a8e916c3a0ae8b9c2fccf68b631", - "reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/ab50dcf166d5f577978419edd37aa2bb8eabce0c", + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c", "shasum": "" }, "require": { @@ -9119,7 +9168,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -9152,7 +9201,7 @@ "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/stopwatch", 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 e18a8c8e97c79..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 @@ -86,7 +86,12 @@ public function get(string $query, array $variables = [], string $operationName ]; array_filter($requestArray); - $responseBody = $this->curlClient->get($url, $requestArray, $headers); + 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); } @@ -116,6 +121,36 @@ private function processResponse(string $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]; + } + /** * Process errors * @@ -160,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 8abd97b4b744d..94eb5ddec8604 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php @@ -77,6 +77,29 @@ public function graphQlMutation( ); } + /** + * 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, + $this->composeHeaders($headers) + ); + } + /** * Compose headers * 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 99f1dc004c50f..6ed0f6ef7a132 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php @@ -39,10 +39,10 @@ protected function setUp() public function testAddProductIfQuantityIsNotAvailable() { $sku = 'simple'; - $qty = 200; + $quantity = 200; $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); - $query = $this->getQuery($maskedQuoteId, $sku, $qty); + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); $this->graphQlMutation($query); } @@ -58,10 +58,10 @@ 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'); - $query = $this->getQuery($maskedQuoteId, $sku, $qty); + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); $this->graphQlMutation($query); } @@ -71,33 +71,51 @@ public function testAddMoreProductsThatAllowed() * @expectedException \Exception * @expectedExceptionMessage Please enter a number greater than 0 in this field. */ - public function testAddSimpleProductToCartWithNegativeQty() + public function testAddSimpleProductToCartWithNegativeQuantity() { $sku = 'simple'; - $qty = -2; + $quantity = -2; $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); - $query = $this->getQuery($maskedQuoteId, $sku, $qty); + $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 testAddProductIfQuantityIsDecimal() + { + $sku = 'simple_product'; + $quantity = 0.2; + + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); + + $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 */ - private function getQuery(string $maskedQuoteId, string $sku, int $qty) : string + private function getQuery(string $maskedQuoteId, string $sku, float $quantity) : string { return <<<QUERY mutation { addSimpleProductsToCart( input: { cart_id: "{$maskedQuoteId}", - cartItems: [ + cart_items: [ { data: { - qty: $qty + quantity: $quantity sku: "$sku" } } @@ -106,7 +124,7 @@ private function getQuery(string $maskedQuoteId, string $sku, int $qty) : string ) { 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/CheckoutAgreements/Api/CheckoutAgreementsListTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CheckoutAgreements/GetCheckoutAgreementsTest.php similarity index 78% rename from dev/tests/api-functional/testsuite/Magento/GraphQl/CheckoutAgreements/Api/CheckoutAgreementsListTest.php rename to dev/tests/api-functional/testsuite/Magento/GraphQl/CheckoutAgreements/GetCheckoutAgreementsTest.php index 05f2afe0ed2cf..8fdfd5cf0108d 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CheckoutAgreements/Api/CheckoutAgreementsListTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CheckoutAgreements/GetCheckoutAgreementsTest.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\GraphQl\CheckoutAgreements\Api; +namespace Magento\GraphQl\CheckoutAgreements; use Magento\CheckoutAgreements\Api\Data\AgreementInterface; use Magento\CheckoutAgreements\Model\Agreement as AgreementModel; @@ -20,8 +20,14 @@ use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; -class CheckoutAgreementsListTest extends GraphQlAbstract +/** + * Get checkout agreements test + */ +class GetCheckoutAgreementsTest extends GraphQlAbstract { + /** + * @var string + */ private $agreementsXmlConfigPath = 'checkout/options/enable_agreements'; /** @@ -55,15 +61,15 @@ public function testGetActiveAgreement() $response = $this->graphQlQuery($query); - $this->assertArrayHasKey('checkoutAgreements', $response); + self::assertArrayHasKey('checkoutAgreements', $response); $agreements = $response['checkoutAgreements']; - $this->assertCount(1, $agreements); - $this->assertEquals('Checkout Agreement (active)', $agreements[0]['name']); - $this->assertEquals('Checkout agreement content: <b>HTML</b>', $agreements[0]['content']); - $this->assertEquals('200px', $agreements[0]['content_height']); - $this->assertEquals('Checkout agreement checkbox text.', $agreements[0]['checkbox_text']); - $this->assertEquals(true, $agreements[0]['is_html']); - $this->assertEquals(0, $agreements[0]['mode']); + self::assertCount(1, $agreements); + self::assertEquals('Checkout Agreement (active)', $agreements[0]['name']); + self::assertEquals('Checkout agreement content: <b>HTML</b>', $agreements[0]['content']); + self::assertEquals('200px', $agreements[0]['content_height']); + self::assertEquals('Checkout agreement checkbox text.', $agreements[0]['checkbox_text']); + self::assertEquals(true, $agreements[0]['is_html']); + self::assertEquals(0, $agreements[0]['mode']); } /** @@ -82,15 +88,15 @@ public function testGetActiveAgreementOnSecondStore() $headerMap['Store'] = $secondStoreCode; $response = $this->graphQlQuery($query, [], '', $headerMap); - $this->assertArrayHasKey('checkoutAgreements', $response); + self::assertArrayHasKey('checkoutAgreements', $response); $agreements = $response['checkoutAgreements']; - $this->assertCount(1, $agreements); - $this->assertEquals($agreementsName, $agreements[0]['name']); - $this->assertEquals('Checkout agreement content: <b>HTML</b>', $agreements[0]['content']); - $this->assertEquals('200px', $agreements[0]['content_height']); - $this->assertEquals('Checkout agreement checkbox text.', $agreements[0]['checkbox_text']); - $this->assertEquals(true, $agreements[0]['is_html']); - $this->assertEquals(0, $agreements[0]['mode']); + self::assertCount(1, $agreements); + self::assertEquals($agreementsName, $agreements[0]['name']); + self::assertEquals('Checkout agreement content: <b>HTML</b>', $agreements[0]['content']); + self::assertEquals('200px', $agreements[0]['content_height']); + self::assertEquals('Checkout agreement checkbox text.', $agreements[0]['checkbox_text']); + self::assertEquals(true, $agreements[0]['is_html']); + self::assertEquals(0, $agreements[0]['mode']); } /** @@ -108,9 +114,9 @@ public function testGetActiveAgreementFromSecondStoreOnDefaultStore() $response = $this->graphQlQuery($query); - $this->assertArrayHasKey('checkoutAgreements', $response); + self::assertArrayHasKey('checkoutAgreements', $response); $agreements = $response['checkoutAgreements']; - $this->assertCount(0, $agreements); + self::assertCount(0, $agreements); } public function testGetAgreementNotSet() @@ -119,9 +125,9 @@ public function testGetAgreementNotSet() $response = $this->graphQlQuery($query); - $this->assertArrayHasKey('checkoutAgreements', $response); + self::assertArrayHasKey('checkoutAgreements', $response); $agreements = $response['checkoutAgreements']; - $this->assertCount(0, $agreements); + self::assertCount(0, $agreements); } /** @@ -135,9 +141,9 @@ public function testDisabledAgreements() $response = $this->graphQlQuery($query); - $this->assertArrayHasKey('checkoutAgreements', $response); + self::assertArrayHasKey('checkoutAgreements', $response); $agreements = $response['checkoutAgreements']; - $this->assertCount(0, $agreements); + self::assertCount(0, $agreements); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php index d22cd14a4ae26..a6f02e4239f25 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php @@ -37,14 +37,14 @@ protected function setUp() public function testAddConfigurableProductToCart() { $variantSku = 'simple_41'; - $qty = 2; + $quantity = 2; $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); - $query = $this->getQuery($maskedQuoteId, $variantSku, $qty); + $query = $this->getQuery($maskedQuoteId, $variantSku, $quantity); $response = $this->graphQlMutation($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']); } @@ -57,10 +57,10 @@ public function testAddConfigurableProductToCart() public function testAddProductIfQuantityIsNotAvailable() { $variantSku = 'simple_41'; - $qty = 200; + $quantity = 200; $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); - $query = $this->getQuery($maskedQuoteId, $variantSku, $qty); + $query = $this->getQuery($maskedQuoteId, $variantSku, $quantity); $this->graphQlMutation($query); } @@ -73,31 +73,31 @@ public function testAddProductIfQuantityIsNotAvailable() public function testAddOutOfStockProduct() { $variantSku = 'simple_1010'; - $qty = 1; + $quantity = 1; $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); - $query = $this->getQuery($maskedQuoteId, $variantSku, $qty); + $query = $this->getQuery($maskedQuoteId, $variantSku, $quantity); $this->graphQlMutation($query); } /** * @param string $maskedQuoteId * @param string $variantSku - * @param int $qty + * @param int $quantity * @return string */ - private function getQuery(string $maskedQuoteId, string $variantSku, int $qty): string + private function getQuery(string $maskedQuoteId, string $variantSku, int $quantity): string { return <<<QUERY mutation { addConfigurableProductsToCart( input: { cart_id: "{$maskedQuoteId}" - cartItems: [ + cart_items: [ { variant_sku: "{$variantSku}" data: { - qty: {$qty} + quantity: {$quantity} sku: "{$variantSku}" } } @@ -107,7 +107,7 @@ private function getQuery(string $maskedQuoteId, string $variantSku, int $qty): cart { items { id - qty + quantity product { name sku 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 88eaeaa8f9dd5..e13c1974e3067 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GenerateCustomerTokenTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GenerateCustomerTokenTest.php @@ -68,4 +68,54 @@ public function testGenerateCustomerTokenWithInvalidCredentials() 'was incorrect or your account is disabled temporarily. Please wait and try again later.'); $this->graphQlMutation($mutation); } + + /** + * Verify customer with empty email + */ + public function testGenerateCustomerTokenWithEmptyEmail() + { + $email = ''; + $password = 'bad-password'; + + $mutation + = <<<MUTATION +mutation { + generateCustomerToken( + email: "{$email}" + password: "{$password}" + ) { + token + } +} +MUTATION; + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('GraphQL response contains errors: Specify the "email" value.'); + $this->graphQlMutation($mutation); + } + + /** + * Verify customer with empty password + */ + public function testGenerateCustomerTokenWithEmptyPassword() + { + $email = 'customer@example.com'; + $password = ''; + + $mutation + = <<<MUTATION +mutation { + generateCustomerToken( + email: "{$email}" + password: "{$password}" + ) { + token + } +} +MUTATION; + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('GraphQL response contains errors: Specify the "password" value.'); + $this->graphQlMutation($mutation); + } } 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..808fd95d331e1 --- /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}" + cart_items: [ + { + 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/AddSimpleProductWithCustomOptionsToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductWithCustomOptionsToCartTest.php index f33ccce82fcb7..53be8f84ffc83 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductWithCustomOptionsToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductWithCustomOptionsToCartTest.php @@ -46,45 +46,15 @@ protected function setUp() public function testAddSimpleProductWithOptions() { $sku = 'simple'; - $qty = 1; + $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)); + $queryCustomizableOptionValues = preg_replace('/"([^"]+)"\s*:\s*/', '$1:', json_encode($customOptionsValues)); - $query = <<<QUERY -mutation { - addSimpleProductsToCart( - input: { - cart_id: "{$maskedQuoteId}", - cartItems: [ - { - data: { - qty: $qty - sku: "$sku" - }, - customizable_options: $queryCustomizableOptions - } - ] - } - ) { - cart { - items { - ... on SimpleCartItem { - customizable_options { - label - values { - value - } - } - } - } - } - } -} -QUERY; + $customizableOptions = "customizable_options: {$queryCustomizableOptionValues}"; + $query = $this->getQuery($maskedQuoteId, $sku, $quantity, $customizableOptions); $response = $this->graphQlMutation($query); @@ -95,7 +65,7 @@ public function testAddSimpleProductWithOptions() $assignedOptionsCount = count($customOptionsValues); for ($counter = 0; $counter < $assignedOptionsCount; $counter++) { self::assertEquals( - $customOptionsValues[$counter]['value'], + $customOptionsValues[$counter]['value_string'], $customizableOptionsOutput[$counter]['values'][0]['value'] ); } @@ -107,23 +77,42 @@ public function testAddSimpleProductWithOptions() * @magentoApiDataFixture Magento/Catalog/_files/product_simple_with_options.php * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php */ - public function testAddSimpleProductWithNoRequiredOptionsSet() + public function testAddSimpleProductWithMissedRequiredOptionsSet() { - $sku = 'simple'; - $qty = 1; $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); + $sku = 'simple'; + $quantity = 1; + $customizableOptions = ''; + + $query = $this->getQuery($maskedQuoteId, $sku, $quantity, $customizableOptions); + + self::expectExceptionMessage( + 'The product\'s required option(s) weren\'t entered. Make sure the options are entered and try again.' + ); + $this->graphQlMutation($query); + } - $query = <<<QUERY + /** + * @param string $maskedQuoteId + * @param string $sku + * @param float $quantity + * @param string $customizableOptions + * @return string + */ + private function getQuery(string $maskedQuoteId, string $sku, float $quantity, string $customizableOptions): string + { + return <<<QUERY mutation { addSimpleProductsToCart( input: { cart_id: "{$maskedQuoteId}", - cartItems: [ + cart_items: [ { data: { - qty: $qty + quantity: $quantity sku: "$sku" } + {$customizableOptions} } ] } @@ -134,7 +123,7 @@ public function testAddSimpleProductWithNoRequiredOptionsSet() customizable_options { label values { - value + value } } } @@ -143,12 +132,6 @@ public function testAddSimpleProductWithNoRequiredOptionsSet() } } QUERY; - - self::expectExceptionMessage( - 'The product\'s required option(s) weren\'t entered. Make sure the options are entered and try again.' - ); - - $this->graphQlMutation($query); } /** @@ -168,13 +151,13 @@ private function getCustomOptionsValuesForQuery(string $sku): array if ($optionType == 'field' || $optionType == 'area') { $customOptionsValues[] = [ 'id' => (int) $customOption->getOptionId(), - 'value' => 'test' + 'value_string' => 'test' ]; } elseif ($optionType == 'drop_down') { $optionSelectValues = $customOption->getValues(); $customOptionsValues[] = [ 'id' => (int) $customOption->getOptionId(), - 'value' => reset($optionSelectValues)->getOptionTypeId() + 'value_string' => reset($optionSelectValues)->getOptionTypeId() ]; } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddVirtualProductWithCustomOptionsToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddVirtualProductWithCustomOptionsToCartTest.php index ffd52bcf7fb15..7b3c5de4ba61d 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddVirtualProductWithCustomOptionsToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddVirtualProductWithCustomOptionsToCartTest.php @@ -46,45 +46,15 @@ protected function setUp() public function testAddVirtualProductWithOptions() { $sku = 'virtual'; - $qty = 1; + $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)); + $queryCustomizableOptionValues = preg_replace('/"([^"]+)"\s*:\s*/', '$1:', json_encode($customOptionsValues)); - $query = <<<QUERY -mutation { - addVirtualProductsToCart( - input: { - cart_id: "{$maskedQuoteId}", - cartItems: [ - { - data: { - qty: $qty - sku: "$sku" - }, - customizable_options: $queryCustomizableOptions - } - ] - } - ) { - cart { - items { - ... on VirtualCartItem { - customizable_options { - label - values { - value - } - } - } - } - } - } -} -QUERY; + $customizableOptions = "customizable_options: {$queryCustomizableOptionValues}"; + $query = $this->getQuery($maskedQuoteId, $sku, $quantity, $customizableOptions); $response = $this->graphQlMutation($query); @@ -95,7 +65,7 @@ public function testAddVirtualProductWithOptions() $assignedOptionsCount = count($customOptionsValues); for ($counter = 0; $counter < $assignedOptionsCount; $counter++) { self::assertEquals( - $customOptionsValues[$counter]['value'], + $customOptionsValues[$counter]['value_string'], $customizableOptionsOutput[$counter]['values'][0]['value'] ); } @@ -107,23 +77,42 @@ public function testAddVirtualProductWithOptions() * @magentoApiDataFixture Magento/Catalog/_files/product_virtual_with_options.php * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php */ - public function testAddVirtualProductWithNoRequiredOptionsSet() + public function testAddSimpleProductWithMissedRequiredOptionsSet() { - $sku = 'virtual'; - $qty = 1; $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); + $sku = 'virtual'; + $quantity = 1; + $customizableOptions = ''; + + $query = $this->getQuery($maskedQuoteId, $sku, $quantity, $customizableOptions); + + self::expectExceptionMessage( + 'The product\'s required option(s) weren\'t entered. Make sure the options are entered and try again.' + ); + $this->graphQlMutation($query); + } - $query = <<<QUERY + /** + * @param string $maskedQuoteId + * @param string $sku + * @param float $quantity + * @param string $customizableOptions + * @return string + */ + private function getQuery(string $maskedQuoteId, string $sku, float $quantity, string $customizableOptions): string + { + return <<<QUERY mutation { addVirtualProductsToCart( input: { cart_id: "{$maskedQuoteId}", - cartItems: [ + cart_items: [ { data: { - qty: $qty + quantity: $quantity sku: "$sku" } + {$customizableOptions} } ] } @@ -134,7 +123,7 @@ public function testAddVirtualProductWithNoRequiredOptionsSet() customizable_options { label values { - value + value } } } @@ -143,12 +132,6 @@ public function testAddVirtualProductWithNoRequiredOptionsSet() } } QUERY; - - self::expectExceptionMessage( - 'The product\'s required option(s) weren\'t entered. Make sure the options are entered and try again.' - ); - - $this->graphQlMutation($query); } /** @@ -168,13 +151,13 @@ private function getCustomOptionsValuesForQuery(string $sku): array if ($optionType == 'field' || $optionType == 'area') { $customOptionsValues[] = [ 'id' => (int) $customOption->getOptionId(), - 'value' => 'test' + 'value_string' => 'test' ]; } elseif ($optionType == 'drop_down') { $optionSelectValues = $customOption->getValues(); $customOptionsValues[] = [ 'id' => (int) $customOption->getOptionId(), - 'value' => reset($optionSelectValues)->getOptionTypeId() + 'value_string' => reset($optionSelectValues)->getOptionTypeId() ]; } } 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..aca13e53875dd --- /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}", + cart_items: [ + { + 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..86573dcde2e35 --- /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}", + cart_items: [ + { + 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/CheckoutEndToEndTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CheckoutEndToEndTest.php new file mode 100644 index 0000000000000..5a4cc88d69623 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CheckoutEndToEndTest.php @@ -0,0 +1,527 @@ +<?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}" + cart_items: [ + { + 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 { + value + } + } + } + } + } +} +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::assertArrayHasKey('value', $availableShippingMethod['amount']); + self::assertNotEmpty($availableShippingMethod['amount']['value']); + + 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 fc66e4647e576..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); } @@ -79,7 +67,6 @@ public function testCreateEmptyCart() self::assertNotEmpty($response['createEmptyCart']); $guestCart = $this->guestCartRepository->get($response['createEmptyCart']); - $this->maskedQuoteId = $response['createEmptyCart']; self::assertNotNull($guestCart->getId()); self::assertEquals(1, $guestCart->getCustomer()->getId()); @@ -103,13 +90,71 @@ public function testCreateEmptyCartWithNotDefaultStore() /* 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/GetAvailableShippingMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php index 2b647f61c4c63..b01921ae67ed7 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php @@ -58,15 +58,27 @@ public function testGetAvailableShippingMethods() self::assertCount(1, $response['cart']['shipping_addresses'][0]['available_shipping_methods']); $expectedAddressData = [ - 'amount' => 10, - 'base_amount' => 10, + 'amount' => [ + 'value' => 10, + 'currency' => 'USD', + ], + 'base_amount' => [ + 'value' => 10, + 'currency' => 'USD', + ], 'carrier_code' => 'flatrate', 'carrier_title' => 'Flat Rate', 'error_message' => '', 'method_code' => 'flatrate', 'method_title' => 'Fixed', - 'price_incl_tax' => 10, - 'price_excl_tax' => 10, + 'price_incl_tax' => [ + 'value' => 10, + 'currency' => 'USD', + ], + 'price_excl_tax' => [ + 'value' => 10, + 'currency' => 'USD', + ], ]; self::assertEquals( $expectedAddressData, @@ -158,15 +170,27 @@ private function getQuery(string $maskedQuoteId): string cart (cart_id: "{$maskedQuoteId}") { shipping_addresses { available_shipping_methods { - amount - base_amount + amount { + value + currency + } + base_amount { + value + currency + } carrier_code carrier_title error_message method_code method_title - price_excl_tax - price_incl_tax + price_excl_tax { + value + currency + } + price_incl_tax { + value + currency + } } } } 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/GetSelectedPaymentMethodTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedPaymentMethodTest.php new file mode 100644 index 0000000000000..d876d74de661b --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedPaymentMethodTest.php @@ -0,0 +1,143 @@ +<?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 selected payment method from cart + */ +class GetSelectedPaymentMethodTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php + */ + public function testGetSelectedPaymentMethod() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + + $this->assertArrayHasKey('cart', $response); + $this->assertArrayHasKey('selected_payment_method', $response['cart']); + $this->assertArrayHasKey('code', $response['cart']['selected_payment_method']); + $this->assertEquals('checkmo', $response['cart']['selected_payment_method']['code']); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + */ + public function testGetSelectedPaymentMethodFromNonExistentCart() + { + $maskedQuoteId = 'non_existent_masked_id'; + $query = $this->getQuery($maskedQuoteId); + + $this->expectExceptionMessage( + 'Could not find a cart with ID "non_existent_masked_id"' + ); + + $this->graphQlQuery($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 + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php + */ + public function testGetSelectedPaymentMethodFromGuestCart() + { + $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 testGetSelectedPaymentMethodFromAnotherCustomerCart() + { + $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')); + } + + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + } + + /** + * @param string $maskedQuoteId + * @return string + */ + private function getQuery(string $maskedQuoteId): string + { + return <<<QUERY +{ + cart(cart_id:"$maskedQuoteId") { + selected_payment_method { + code + } + } +} +QUERY; + } + + /** + * @param string $username + * @param string $password + * @return array + * @throws \Magento\Framework\Exception\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/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..b6dde46871cbb 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 @@ -44,7 +44,7 @@ protected function setUp() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php */ - public function testGeSpecifiedBillingAddress() + public function testGetSpecifiedBillingAddress() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId); @@ -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']); @@ -83,7 +83,7 @@ public function testGeSpecifiedBillingAddress() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ - public function testGeSpecifiedBillingAddressIfBillingAddressIsNotSet() + public function testGetSpecifiedBillingAddressIfBillingAddressIsNotSet() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId); @@ -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']); @@ -121,7 +121,7 @@ public function testGeSpecifiedBillingAddressIfBillingAddressIsNotSet() * @expectedException \Exception * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" */ - public function testGeSpecifiedBillingAddressOfNonExistentCart() + public function testGetSpecifiedBillingAddressOfNonExistentCart() { $maskedQuoteId = 'non_existent_masked_id'; $query = $this->getQuery($maskedQuoteId); @@ -137,7 +137,7 @@ public function testGeSpecifiedBillingAddressOfNonExistentCart() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php */ - public function testGeSpecifiedBillingAddressFromAnotherGuestCart() + public function testGetSpecifiedBillingAddressFromAnotherGuestCart() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); @@ -155,7 +155,7 @@ public function testGeSpecifiedBillingAddressFromAnotherGuestCart() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php */ - public function testGeSpecifiedBillingAddressFromAnotherCustomerCart() + public function testGetSpecifiedBillingAddressFromAnotherCustomerCart() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); @@ -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/GetSpecifiedShippingAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedShippingAddressTest.php new file mode 100644 index 0000000000000..de3c384e65783 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedShippingAddressTest.php @@ -0,0 +1,219 @@ +<?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 get specified shipping address + */ +class GetSpecifiedShippingAddressTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @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 + */ + public function testGetSpecifiedShippingAddress() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + self::assertArrayHasKey('cart', $response); + self::assertArrayHasKey('shipping_addresses', $response['cart']); + + $expectedShippingAddressData = [ + 'firstname' => 'John', + 'lastname' => 'Smith', + 'company' => 'CompanyName', + 'street' => [ + 'Green str, 67' + ], + 'city' => 'CityM', + 'region' => [ + 'code' => 'AL', + 'label' => 'Alabama', + ], + 'postcode' => '75477', + 'country' => [ + 'code' => 'US', + 'label' => 'US', + ], + 'telephone' => '3468676', + '__typename' => 'ShippingCartAddress', + 'customer_notes' => null, + ]; + self::assertEquals($expectedShippingAddressData, current($response['cart']['shipping_addresses'])); + } + + /** + * @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 testGetSpecifiedShippingAddressIfShippingAddressIsNotSet() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + self::assertArrayHasKey('cart', $response); + self::assertArrayHasKey('shipping_addresses', $response['cart']); + + $expectedShippingAddressData = [ + 'firstname' => null, + 'lastname' => null, + 'company' => null, + 'street' => [ + '' + ], + 'city' => null, + 'region' => [ + 'code' => null, + 'label' => null, + ], + 'postcode' => null, + 'country' => [ + 'code' => null, + 'label' => null, + ], + 'telephone' => null, + '__typename' => 'ShippingCartAddress', + 'customer_notes' => null, + ]; + self::assertEquals($expectedShippingAddressData, current($response['cart']['shipping_addresses'])); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" + */ + public function testGetSpecifiedShippingAddressOfNonExistentCart() + { + $maskedQuoteId = 'non_existent_masked_id'; + $query = $this->getQuery($maskedQuoteId); + + $this->graphQlQuery($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 + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + */ + public function testGetSpecifiedShippingAddressFromGuestCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$maskedQuoteId\"" + ); + $this->graphQlQuery($this->getQuery($maskedQuoteId), [], '', $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 + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + */ + public function testGetSpecifiedShippingAddressFromAnotherCustomerCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$maskedQuoteId\"" + ); + $this->graphQlQuery( + $this->getQuery($maskedQuoteId), + [], + '', + $this->getHeaderMap('customer2@search.example.com') + ); + } + + /** + * @param string $maskedQuoteId + * @return string + */ + private function getQuery(string $maskedQuoteId): string + { + return <<<QUERY +{ + cart(cart_id: "$maskedQuoteId") { + shipping_addresses { + firstname + lastname + company + street + city + region + { + code + label + } + postcode + country + { + code + label + } + telephone + __typename + customer_notes + } + } +} +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/PlaceOrderTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/PlaceOrderTest.php index b7d3b546ba194..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; 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 39803f8d58447..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 @@ -107,7 +107,7 @@ public function testUpdateWithMissedItemRequiredParameters(string $input, string ) { cart { items { - qty + quantity } } } @@ -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 6b15f947a2477..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,7 +99,7 @@ public function testSetNewBillingAddress() code label } - address_type + __typename } } } @@ -159,7 +159,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() code label } - address_type + __typename } shipping_addresses { firstname @@ -173,7 +173,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() code label } - address_type + __typename } } } @@ -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'); } /** @@ -560,7 +560,7 @@ public function testSetNewBillingAddressWithRedundantStreetLine() * @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/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 20462220ff6f7..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,20 +48,27 @@ 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->graphQlMutation($query, [], '', $this->getHeaderMap()); @@ -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 2604ec5f0a0f9..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 @@ -196,7 +196,7 @@ public function testSetPaymentMethodWithoutRequiredParameters(string $input, str ) { cart { items { - qty + quantity } } } 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 6b097e028ffe5..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,7 +101,7 @@ public function testSetNewShippingAddressOnCartWithSimpleProduct() label code } - address_type + __typename } } } @@ -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 0fc52443e86b9..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 @@ -9,7 +9,6 @@ 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; @@ -24,11 +23,6 @@ class SetShippingMethodsOnCartTest extends GraphQlAbstract */ private $getMaskedQuoteIdByReservedOrderId; - /** - * @var GetQuoteShippingAddressIdByReservedQuoteId - */ - private $getQuoteShippingAddressIdByReservedQuoteId; - /** * @var CustomerTokenServiceInterface */ @@ -41,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); } @@ -59,13 +50,11 @@ 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->graphQlMutation($query, [], '', $this->getHeaderMap()); @@ -78,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']); } /** @@ -98,13 +109,11 @@ 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->graphQlMutation($query, [], '', $this->getHeaderMap()); @@ -138,8 +147,7 @@ public function testReSetShippingMethod() 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 { @@ -169,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" }]', @@ -183,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" }]', @@ -215,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" }]', @@ -223,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: "" }]', @@ -238,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" }]', @@ -246,7 +232,6 @@ 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" }]', @@ -254,7 +239,6 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array ], 'disabled_shipping_method' => [ 'cart_id: "cart_id_value", shipping_methods: [{ - cart_address_id: cart_address_id_value carrier_code: "freeshipping" method_code: "freeshipping" }]', @@ -276,7 +260,6 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array public function testSetMultipleShippingMethods() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); $query = <<<QUERY mutation { @@ -284,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" } @@ -323,12 +304,10 @@ 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( @@ -352,12 +331,10 @@ 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( @@ -366,46 +343,16 @@ public function testSetShippingMethodToAnotherCustomerCart() $this->graphQlMutation($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->graphQlMutation($query, [], '', $this->getHeaderMap()); - } - /** * @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 { @@ -413,7 +360,6 @@ private function getQuery( { cart_id: "$maskedQuoteId", shipping_methods: [{ - cart_address_id: $shippingAddressId carrier_code: "$shippingCarrierCode" method_code: "$shippingMethodCode" }] @@ -423,6 +369,16 @@ private function getQuery( selected_shipping_method { carrier_code method_code + carrier_title + method_title + amount { + value + currency + } + base_amount { + value + currency + } } } } @@ -444,13 +400,11 @@ public function testSetShippingMethodOnAnEmptyCart() $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->graphQlMutation($query, [], '', $this->getHeaderMap()); } 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 35e2d62214fb2..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,15 +58,15 @@ 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); + $query = $this->getQuery($maskedQuoteId, $itemId, $quantity); $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); $this->assertArrayHasKey('updateCartItems', $response); @@ -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,9 +88,9 @@ 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); + $query = $this->getQuery($maskedQuoteId, $itemId, $quantity); $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); $this->assertArrayHasKey('updateCartItems', $response); @@ -229,7 +229,7 @@ public function testUpdateWithMissedCartItemId() cart { items { id - qty + quantity } } } @@ -259,7 +259,7 @@ public function testUpdateWithMissedItemRequiredParameters(string $input, string cart { items { id - qty + quantity } } } @@ -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 index 25221b628e7fb..9a943cf4b204e 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php @@ -31,56 +31,91 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Catalog/_files/products.php - * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php */ public function testAddSimpleProductToCart() { - $sku = 'simple'; - $qty = 2; - $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); + $sku = 'simple_product'; + $quantity = 2; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $query = $this->getQuery($maskedQuoteId, $sku, $qty); + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); $response = $this->graphQlMutation($query); self::assertArrayHasKey('cart', $response['addSimpleProductsToCart']); - self::assertEquals($qty, $response['addSimpleProductsToCart']['cart']['items'][0]['qty']); + self::assertEquals($quantity, $response['addSimpleProductsToCart']['cart']['items'][0]['quantity']); self::assertEquals($sku, $response['addSimpleProductsToCart']['cart']['items'][0]['product']['sku']); } /** - * @magentoApiDataFixture Magento/Catalog/_files/products.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'; - $qty = 1; + $sku = 'simple_product'; + $quantity = 1; $maskedQuoteId = 'non_existent_masked_id'; - $query = $this->getQuery($maskedQuoteId, $sku, $qty); + $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 int $qty + * @param float $quantity * @return string */ - private function getQuery(string $maskedQuoteId, string $sku, int $qty): string + private function getQuery(string $maskedQuoteId, string $sku, float $quantity): string { return <<<QUERY mutation { addSimpleProductsToCart( input: { cart_id: "{$maskedQuoteId}" - cartItems: [ + cart_items: [ { data: { - qty: $qty + quantity: $quantity sku: "$sku" } } @@ -89,7 +124,7 @@ private function getQuery(string $maskedQuoteId, string $sku, int $qty): string ) { cart { items { - qty + quantity product { sku } 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..cadbec857c2d6 --- /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}" + cart_items: [ + { + data: { + quantity: {$quantity} + sku: "{$sku}" + } + } + ] + } + ) { + cart { + items { + quantity + product { + sku + } + } + } + } +} +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..ed5aa9303d875 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CheckoutEndToEndTest.php @@ -0,0 +1,438 @@ +<?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}" + cart_items: [ + { + 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 { + value + } + } + } + } + } +} +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::assertArrayHasKey('value', $availableShippingMethod['amount']); + self::assertNotEmpty($availableShippingMethod['amount']['value']); + + 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 adb2879e186b1..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,37 +25,26 @@ 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); } @@ -69,7 +57,6 @@ public function testCreateEmptyCart() self::assertNotEmpty($response['createEmptyCart']); $guestCart = $this->guestCartRepository->get($response['createEmptyCart']); - $this->maskedQuoteId = $response['createEmptyCart']; self::assertNotNull($guestCart->getId()); self::assertNull($guestCart->getCustomer()->getId()); @@ -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/GetAvailableShippingMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php index a8113657eff6e..8703a690c62e3 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php @@ -50,15 +50,27 @@ public function testGetAvailableShippingMethods() self::assertCount(1, $response['cart']['shipping_addresses'][0]['available_shipping_methods']); $expectedAddressData = [ - 'amount' => 10, - 'base_amount' => 10, + 'amount' => [ + 'value' => 10, + 'currency' => 'USD', + ], + 'base_amount' => [ + 'value' => 10, + 'currency' => 'USD', + ], 'carrier_code' => 'flatrate', 'carrier_title' => 'Flat Rate', 'error_message' => '', 'method_code' => 'flatrate', 'method_title' => 'Fixed', - 'price_incl_tax' => 10, - 'price_excl_tax' => 10, + 'price_incl_tax' => [ + 'value' => 10, + 'currency' => 'USD', + ], + 'price_excl_tax' => [ + 'value' => 10, + 'currency' => 'USD', + ], ]; self::assertEquals( $expectedAddressData, @@ -126,15 +138,27 @@ private function getQuery(string $maskedQuoteId): string cart (cart_id: "{$maskedQuoteId}") { shipping_addresses { available_shipping_methods { - amount - base_amount + amount { + value + currency + } + base_amount { + value + currency + } carrier_code carrier_title error_message method_code method_title - price_excl_tax - price_incl_tax + price_excl_tax { + value + currency + } + price_incl_tax { + value + currency + } } } } 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/GetSelectedPaymentMethodTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedPaymentMethodTest.php new file mode 100644 index 0000000000000..ef04da88ea67a --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedPaymentMethodTest.php @@ -0,0 +1,105 @@ +<?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 selected payment method from cart + */ +class GetSelectedPaymentMethodTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + 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/enable_offline_payment_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php + * + */ + public function testGetSelectedPaymentMethod() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query); + + $this->assertArrayHasKey('cart', $response); + $this->assertArrayHasKey('selected_payment_method', $response['cart']); + $this->assertArrayHasKey('code', $response['cart']['selected_payment_method']); + $this->assertEquals('checkmo', $response['cart']['selected_payment_method']['code']); + } + + /** + * @expectedException \Exception + */ + public function testGetSelectedPaymentMethodFromNonExistentCart() + { + $maskedQuoteId = 'non_existent_masked_id'; + $query = $this->getQuery($maskedQuoteId); + + $this->expectExceptionMessage( + 'Could not find a cart with ID "non_existent_masked_id"' + ); + + $this->graphQlQuery($query); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php + */ + public function testGetSelectedPaymentMethodFromCustomerCart() + { + $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") { + selected_payment_method { + code + } + } +} +QUERY; + } +} 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..8ddf1641ede8a 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 @@ -36,7 +36,7 @@ protected function setUp() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php */ - public function testGeSpecifiedBillingAddress() + public function testGetSpecifiedBillingAddress() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId); @@ -63,7 +63,7 @@ public function testGeSpecifiedBillingAddress() 'label' => 'US', ], 'telephone' => '3468676', - 'address_type' => 'BILLING', + '__typename' => 'BillingCartAddress', ]; self::assertEquals($expectedBillingAddressData, $response['cart']['billing_address']); } @@ -73,7 +73,7 @@ public function testGeSpecifiedBillingAddress() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ - public function testGeSpecifiedBillingAddressIfBillingAddressIsNotSet() + public function testGetSpecifiedBillingAddressIfBillingAddressIsNotSet() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId); @@ -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/GetSpecifiedShippingAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedShippingAddressTest.php new file mode 100644 index 0000000000000..f71915bab650f --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedShippingAddressTest.php @@ -0,0 +1,171 @@ +<?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 get specified shipping address + */ +class GetSpecifiedShippingAddressTest 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 + */ + public function testGetSpecifiedShippingAddress() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $response = $this->graphQlQuery($query); + self::assertArrayHasKey('cart', $response); + self::assertArrayHasKey('shipping_addresses', $response['cart']); + + $expectedShippingAddressData = [ + 'firstname' => 'John', + 'lastname' => 'Smith', + 'company' => 'CompanyName', + 'street' => [ + 'Green str, 67' + ], + 'city' => 'CityM', + 'region' => [ + 'code' => 'AL', + 'label' => 'Alabama', + ], + 'postcode' => '75477', + 'country' => [ + 'code' => 'US', + 'label' => 'US', + ], + 'telephone' => '3468676', + '__typename' => 'ShippingCartAddress', + ]; + self::assertEquals($expectedShippingAddressData, current($response['cart']['shipping_addresses'])); + } + + /** + * @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 testGetSpecifiedShippingAddressIfShippingAddressIsNotSet() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $response = $this->graphQlQuery($query); + self::assertArrayHasKey('cart', $response); + self::assertArrayHasKey('shipping_addresses', $response['cart']); + + $expectedShippingAddressData = [ + 'firstname' => null, + 'lastname' => null, + 'company' => null, + 'street' => [ + '' + ], + 'city' => null, + 'region' => [ + 'code' => null, + 'label' => null, + ], + 'postcode' => null, + 'country' => [ + 'code' => null, + 'label' => null, + ], + 'telephone' => null, + '__typename' => 'ShippingCartAddress', + ]; + self::assertEquals($expectedShippingAddressData, current($response['cart']['shipping_addresses'])); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" + */ + public function testGetShippingAddressOfNonExistentCart() + { + $maskedQuoteId = 'non_existent_masked_id'; + $query = $this->getQuery($maskedQuoteId); + $this->graphQlQuery($query); + } + + /** + * _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 + */ + public function testGetShippingAddressFromCustomerCart() + { + $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") { + shipping_addresses { + firstname + lastname + company + street + city + region + { + code + label + } + postcode + country + { + code + label + } + telephone + __typename + } + } +} +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 27f3f6367f662..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 @@ -96,7 +96,7 @@ public function testUpdateWithMissedItemRequiredParameters(string $input, string ) { cart { items { - qty + quantity } } } @@ -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 d2d53220f0042..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,7 +70,7 @@ public function testSetNewBillingAddress() code label } - address_type + __typename } } } @@ -129,7 +129,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() code label } - address_type + __typename } shipping_addresses { firstname @@ -143,7 +143,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() code label } - address_type + __typename } } } @@ -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'); } /** @@ -380,7 +380,7 @@ public function testSetNewBillingAddressRedundantStreetLine() * @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/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 2c1333aa77326..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,20 +40,27 @@ 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->graphQlMutation($query); @@ -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 4ea7eac290f80..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 @@ -164,7 +164,7 @@ public function testSetPaymentMethodWithoutRequiredParameters(string $input, str ) { cart { items { - qty + quantity } } } 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 888b0e87734b6..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,7 +72,7 @@ public function testSetNewShippingAddressOnCartWithSimpleProduct() code label } - address_type + __typename } } } @@ -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 59f53d2ad6856..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 @@ -9,7 +9,6 @@ use Exception; use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; -use Magento\GraphQl\Quote\GetQuoteShippingAddressIdByReservedQuoteId; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -23,11 +22,6 @@ class SetShippingMethodsOnCartTest extends GraphQlAbstract */ private $getMaskedQuoteIdByReservedOrderId; - /** - * @var GetQuoteShippingAddressIdByReservedQuoteId - */ - private $getQuoteShippingAddressIdByReservedQuoteId; - /** * @inheritdoc */ @@ -35,9 +29,6 @@ protected function setUp() { $objectManager = Bootstrap::getObjectManager(); $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); - $this->getQuoteShippingAddressIdByReservedQuoteId = $objectManager->get( - GetQuoteShippingAddressIdByReservedQuoteId::class - ); } /** @@ -51,13 +42,11 @@ 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->graphQlMutation($query); @@ -70,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']); } /** @@ -94,13 +105,11 @@ 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->graphQlMutation($query); } @@ -118,13 +127,11 @@ 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->graphQlMutation($query); @@ -157,8 +164,7 @@ public function testReSetShippingMethod() 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 { @@ -188,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" }]', @@ -202,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" }]', @@ -234,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" }]', @@ -242,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: "" }]', @@ -257,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" }]', @@ -265,7 +249,6 @@ 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" }]', @@ -273,7 +256,6 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array ], 'disabled_shipping_method' => [ 'cart_id: "cart_id_value", shipping_methods: [{ - cart_address_id: cart_address_id_value carrier_code: "freeshipping" method_code: "freeshipping" }]', @@ -294,7 +276,6 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array public function testSetMultipleShippingMethods() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); $query = <<<QUERY mutation { @@ -302,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" } @@ -341,12 +320,10 @@ 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( @@ -354,34 +331,7 @@ public function testSetShippingMethodToCustomerCart() ); $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 - */ - public function testSetShippingMethodIfGuestIsNotOwnerOfAddress() - { - $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->graphQlMutation($query); - } - + /** * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -395,13 +345,11 @@ public function testSetShippingMethodOnAnEmptyCart() $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->graphQlMutation($query); } @@ -410,14 +358,12 @@ public function testSetShippingMethodOnAnEmptyCart() * @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 { @@ -425,7 +371,6 @@ private function getQuery( { cart_id: "$maskedQuoteId", shipping_methods: [{ - cart_address_id: $shippingAddressId carrier_code: "$shippingCarrierCode" method_code: "$shippingMethodCode" }] @@ -435,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 1b8cf2e1c57f7..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,15 +51,15 @@ 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); + $query = $this->getQuery($maskedQuoteId, $itemId, $quantity); $response = $this->graphQlMutation($query); $this->assertArrayHasKey('updateCartItems', $response); @@ -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,9 +81,9 @@ 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); + $query = $this->getQuery($maskedQuoteId, $itemId, $quantity); $response = $this->graphQlMutation($query); $this->assertArrayHasKey('updateCartItems', $response); @@ -180,7 +180,7 @@ public function testUpdateWithMissedCartItemId() cart { items { id - qty + quantity } } } @@ -210,7 +210,7 @@ public function testUpdateWithMissedItemRequiredParameters(string $input, string cart { items { id - qty + quantity } } } @@ -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/Ups/SetUpsShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Ups/SetUpsShippingMethodsOnCartTest.php index fb0c205c86a2c..7de689a6ad0f5 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Ups/SetUpsShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Ups/SetUpsShippingMethodsOnCartTest.php @@ -8,7 +8,6 @@ namespace Magento\GraphQl\Ups; 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; @@ -49,9 +48,9 @@ class SetUpsShippingMethodsOnCartTest extends GraphQlAbstract { /** - * Defines carrier label for "UPS" shipping method + * Defines carrier title for "UPS" shipping method */ - const CARRIER_LABEL = 'United Parcel Service'; + const CARRIER_TITLE = 'United Parcel Service'; /** * Defines carrier code for "UPS" shipping method @@ -68,11 +67,6 @@ class SetUpsShippingMethodsOnCartTest extends GraphQlAbstract */ private $getMaskedQuoteIdByReservedOrderId; - /** - * @var GetQuoteShippingAddressIdByReservedQuoteId - */ - private $getQuoteShippingAddressIdByReservedQuoteId; - /** * @inheritdoc */ @@ -81,9 +75,6 @@ protected function setUp() $objectManager = Bootstrap::getObjectManager(); $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); - $this->getQuoteShippingAddressIdByReservedQuoteId = $objectManager->get( - GetQuoteShippingAddressIdByReservedQuoteId::class - ); } /** @@ -96,15 +87,15 @@ protected function setUp() * * @dataProvider dataProviderShippingMethods * @param string $methodCode - * @param string $methodLabel + * @param string $methodTitle + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function testSetUpsShippingMethod(string $methodCode, string $methodLabel) + public function testSetUpsShippingMethod(string $methodCode, string $methodTitle) { $quoteReservedId = 'test_quote'; $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($quoteReservedId); - $shippingAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute($quoteReservedId); - $query = $this->getQuery($maskedQuoteId, $shippingAddressId, self::CARRIER_CODE, $methodCode); + $query = $this->getQuery($maskedQuoteId, self::CARRIER_CODE, $methodCode); $response = $this->sendRequestWithToken($query); self::assertArrayHasKey('setShippingMethodsOnCart', $response); @@ -121,11 +112,11 @@ public function testSetUpsShippingMethod(string $methodCode, string $methodLabel self::assertArrayHasKey('method_code', $shippingAddress['selected_shipping_method']); self::assertEquals($methodCode, $shippingAddress['selected_shipping_method']['method_code']); - self::assertArrayHasKey('label', $shippingAddress['selected_shipping_method']); - self::assertEquals( - self::CARRIER_LABEL . ' - ' . $methodLabel, - $shippingAddress['selected_shipping_method']['label'] - ); + 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']); + self::assertEquals($methodTitle, $shippingAddress['selected_shipping_method']['method_title']); } /** @@ -152,15 +143,15 @@ public function dataProviderShippingMethods(): array * * @dataProvider dataProviderShippingMethodsBasedOnCanadaAddress * @param string $methodCode - * @param string $methodLabel + * @param string $methodTitle + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function testSetUpsShippingMethodBasedOnCanadaAddress(string $methodCode, string $methodLabel) + public function testSetUpsShippingMethodBasedOnCanadaAddress(string $methodCode, string $methodTitle) { $quoteReservedId = 'test_quote'; $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($quoteReservedId); - $shippingAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute($quoteReservedId); - $query = $this->getQuery($maskedQuoteId, $shippingAddressId, self::CARRIER_CODE, $methodCode); + $query = $this->getQuery($maskedQuoteId, self::CARRIER_CODE, $methodCode); $response = $this->sendRequestWithToken($query); self::assertArrayHasKey('setShippingMethodsOnCart', $response); @@ -177,11 +168,11 @@ public function testSetUpsShippingMethodBasedOnCanadaAddress(string $methodCode, self::assertArrayHasKey('method_code', $shippingAddress['selected_shipping_method']); self::assertEquals($methodCode, $shippingAddress['selected_shipping_method']['method_code']); - self::assertArrayHasKey('label', $shippingAddress['selected_shipping_method']); - self::assertEquals( - self::CARRIER_LABEL . ' - ' . $methodLabel, - $shippingAddress['selected_shipping_method']['label'] - ); + 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']); + self::assertEquals($methodTitle, $shippingAddress['selected_shipping_method']['method_title']); } /** @@ -201,7 +192,6 @@ public function dataProviderShippingMethodsBasedOnCanadaAddress(): array /** * Generates query for setting the specified shipping method on cart * - * @param int $shippingAddressId * @param string $maskedQuoteId * @param string $carrierCode * @param string $methodCode @@ -209,7 +199,6 @@ public function dataProviderShippingMethodsBasedOnCanadaAddress(): array */ private function getQuery( string $maskedQuoteId, - int $shippingAddressId, string $carrierCode, string $methodCode ): string { @@ -219,7 +208,6 @@ private function getQuery( cart_id: "$maskedQuoteId" shipping_methods: [ { - cart_address_id: $shippingAddressId carrier_code: "$carrierCode" method_code: "$methodCode" } @@ -230,7 +218,8 @@ private function getQuery( selected_shipping_method { carrier_code method_code - label + carrier_title + method_title } } } 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/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/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/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/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/Product/GridFilteringDeletedEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/GridFilteringDeletedEntityTest.xml index 687ed7038f40b..3bb0d5c0e1eb8 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/GridFilteringDeletedEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/GridFilteringDeletedEntityTest.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\Ui\Test\TestCase\GridFilteringDeletedEntityTest" summary="Grid filtering by deleted entity" ticketId="MAGETWO-88982"> <variation name="GridFilteringDeletedEntityTestVariation1"> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2, mftf_migrated:yes</data> <data name="steps" xsi:type="array"> <item name="0" xsi:type="array"> <item name="0" xsi:type="string">Magento\Store\Test\TestStep\DeleteWebsitesEntityStep</item> 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/ApplyCatalogPriceRulesTest.xml b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/ApplyCatalogPriceRulesTest.xml index 719e9bb95694e..8b36f78e4fc84 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/ApplyCatalogPriceRulesTest.xml +++ b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/ApplyCatalogPriceRulesTest.xml @@ -116,6 +116,7 @@ <constraint name="Magento\CatalogRule\Test\Constraint\AssertCatalogPriceRuleAppliedShoppingCart" /> </variation> <variation name="ApplyCatalogRuleForSimpleProductWithCustomOptions" summary="Apply Catalog Rule for Simple Product With Custom Options" ticketId="MAGETWO-23038"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="products/0" xsi:type="string">catalogProductSimple::with_default_custom_option</data> <data name="products/1" xsi:type="string">catalogProductSimple::with_percent_and_fixed_custom_option</data> <data name="products/2" xsi:type="string">catalogProductSimple::with_custom_options_and_price_56_78</data> @@ -149,6 +150,7 @@ <constraint name="Magento\CatalogRule\Test\Constraint\AssertCatalogPriceRuleOnOnepageCheckout" /> </variation> <variation name="ApplyCatalogRuleForSimpleProductAndConfigurableProduct" summary="Apply Catalog Rule For Simple Product And Configurable Product" ticketId="MAGETWO-20616"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="products/0" xsi:type="string">configurableProduct::product_with_price_10</data> <data name="products/1" xsi:type="string">catalogProductSimple::simple_10_dollar</data> <data name="catalogRules/0/data/customer_group_ids/option_0" xsi:type="string">NOT LOGGED IN</data> @@ -177,6 +179,7 @@ <constraint name="Magento\CatalogRule\Test\Constraint\AssertCatalogPriceRuleAppliedShoppingCart" /> </variation> <variation name="ApplyCatalogRuleForSimpleProductAndFixedMethod" summary="Apply Catalog Rule For Simple Product And Fixed Shipping Method" ticketId="MAGETWO-23039"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="products/0" xsi:type="string">catalogProductSimple::with_fixed_custom_option_price_100</data> <data name="customer/dataset" xsi:type="string">customer_US</data> <data name="catalogRules/0/data/name" xsi:type="string">CatalogPriceRule %isolation%</data> diff --git a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/CreateCatalogPriceRuleEntityTest.xml b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/CreateCatalogPriceRuleEntityTest.xml index 819fb7b25e497..49bf36b0325ba 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/CreateCatalogPriceRuleEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/CreateCatalogPriceRuleEntityTest.xml @@ -21,6 +21,7 @@ <constraint name="Magento\CatalogRule\Test\Constraint\AssertCatalogPriceRuleInGrid" /> </variation> <variation name="CatalogRule_Create_Inactive_AdminOnly"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="catalogPriceRule/data/name" xsi:type="string">CatalogPriceRule %isolation%</data> <data name="catalogPriceRule/data/description" xsi:type="string">Catalog Price Rule Description</data> <data name="catalogPriceRule/data/is_active" xsi:type="string">Inactive</data> @@ -29,6 +30,7 @@ <data name="catalogPriceRule/data/condition" xsi:type="string">-</data> <data name="catalogPriceRule/data/simple_action" xsi:type="string">Apply as fixed amount</data> <data name="catalogPriceRule/data/discount_amount" xsi:type="string">10</data> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <constraint name="Magento\CatalogRule\Test\Constraint\AssertCatalogPriceRuleSuccessSaveMessage" /> <constraint name="Magento\CatalogRule\Test\Constraint\AssertCatalogPriceRuleNoticeMessage" /> <constraint name="Magento\CatalogRule\Test\Constraint\AssertCatalogPriceRuleForm" /> diff --git a/dev/tests/functional/tests/app/Magento/CatalogRuleConfigurable/Test/TestCase/ApplyConfigurableProductCatalogPriceRulesTest.xml b/dev/tests/functional/tests/app/Magento/CatalogRuleConfigurable/Test/TestCase/ApplyConfigurableProductCatalogPriceRulesTest.xml index 2e9e2480f382f..1645c68f8fbbd 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogRuleConfigurable/Test/TestCase/ApplyConfigurableProductCatalogPriceRulesTest.xml +++ b/dev/tests/functional/tests/app/Magento/CatalogRuleConfigurable/Test/TestCase/ApplyConfigurableProductCatalogPriceRulesTest.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\CatalogRuleConfigurable\Test\TestCase\ApplyConfigurableProductCatalogPriceRulesTest" summary="Apply Catalog Price Rules"> <variation name="ApplyCatalogRuleForComfigurableProductWithOptions1" summary="Apply Catalog Rule For Configurable Product With Promo Simple Product" ticketId="MAGETWO-47186"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="products/0" xsi:type="string">configurableProduct::Stellar_Solar_Jacket_SIZE_S</data> <data name="products/1" xsi:type="string">configurableProduct::Stellar_Solar_Jacket_SIZE_M</data> <data name="products/2" xsi:type="string">configurableProduct::Stellar_Solar_Jacket_SIZE_L</data> @@ -39,6 +40,7 @@ <constraint name="Magento\CatalogRule\Test\Constraint\AssertCatalogPriceRuleAppliedShoppingCart" /> </variation> <variation name="ApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts" summary="Apply Catalog Rule For Configurable Product With Custom Options" ticketId="MAGETWO-23042"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="products/0" xsi:type="string">configurableProduct::first_product_with_custom_options_and_option_key_1</data> <data name="products/1" xsi:type="string">configurableProduct::first_product_with_custom_options_and_option_key_2</data> <data name="products/2" xsi:type="string">configurableProduct::second_product_with_custom_options_and_option_key_1</data> diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/SuggestSearchingResultEntityTest.xml b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/SuggestSearchingResultEntityTest.xml index e9e4bcca2e136..ffd9e902cdece 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/SuggestSearchingResultEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/SuggestSearchingResultEntityTest.xml @@ -8,22 +8,26 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\CatalogSearch\Test\TestCase\SuggestSearchingResultEntityTest" summary="Suggest Searching Results" ticketId="MAGETWO-24671, MAGETWO-23186"> <variation name="SuggestSearchingResultEntityTestVariation1" summary="Auto-complete search with product name" ticketId="MAGETWO-24671"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="searchTerm/data/query_text/value" xsi:type="string">catalogProductSimple::name</data> <data name="searchTerm/data/num_results" xsi:type="string">-</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertSuggestSearchingResult" /> </variation> <variation name="SuggestSearchingResultEntityTestVariation2" summary="Auto-complete search with product sku" ticketId="MAGETWO-24671"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="searchTerm/data/query_text/value" xsi:type="string">catalogProductSimple::sku</data> <data name="searchTerm/data/num_results" xsi:type="string">1</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertSuggestSearchingResult" /> </variation> <variation name="SuggestSearchingResultEntityTestVariation3" summary="Auto-complete search with product description" ticketId="MAGETWO-23186"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="searchTerm/data/query_text/value" xsi:type="string">catalogProductSimple::abc_dfj_simple_for_advancedsearch::adc_Full</data> <data name="searchTerm/data/num_results" xsi:type="string">1</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertSuggestSearchingResult" /> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermInGrid" /> </variation> <variation name="SuggestSearchingResultEntityTestVariation4" summary="Auto-complete search with product short description" ticketId="MAGETWO-23186"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="searchTerm/data/query_text/value" xsi:type="string">catalogProductSimple::abc_dfj_simple_for_advancedsearch::abc_short</data> <data name="searchTerm/data/num_results" xsi:type="string">1</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertSuggestSearchingResult" /> diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/UpdateSearchTermEntityTest.xml b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/UpdateSearchTermEntityTest.xml index 922973dbf316a..2ce4bbaa8a6d2 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/UpdateSearchTermEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/UpdateSearchTermEntityTest.xml @@ -14,6 +14,7 @@ <data name="searchTerm/data/popularity" xsi:type="string">20</data> <data name="searchTerm/data/redirect" xsi:type="string">http://example.com/</data> <data name="searchTerm/data/display_in_terms" xsi:type="string">No</data> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermSuccessSaveMessage" /> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermForm" /> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermInGrid" /> 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..6baeae29566ee 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 @@ -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\Checkout\Test\TestCase\AddProductsToShoppingCartEntityTest" summary="Add Products to Shopping Cart" ticketId="MAGETWO-25382"> <variation name="AddProductsToShoppingCartEntityTestVariation1" ticketId="MAGETWO-42677"> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2, mftf_migrated:yes</data> <data name="productsData/0" xsi:type="string">bundleProduct::bundle_dynamic_product</data> <data name="cart/data/grand_total" xsi:type="string">210</data> <data name="cart/data/subtotal" xsi:type="string">200</data> @@ -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> @@ -61,7 +58,7 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertSubtotalInMiniShoppingCart" /> </variation> <variation name="AddProductsToShoppingCartEntityTestVariation5"> - <data name="tag" xsi:type="string">severity:S0</data> + <data name="tag" xsi:type="string">severity:S0, mftf_migrated:yes</data> <data name="productsData/0" xsi:type="string">configurableProduct::default</data> <data name="cart/data/grand_total" xsi:type="string">135</data> <data name="cart/data/subtotal" xsi:type="string">120</data> @@ -74,7 +71,7 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertSubtotalInMiniShoppingCart" /> </variation> <variation name="AddProductsToShoppingCartEntityTestVariation6"> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2, mftf_migrated:yes</data> <data name="productsData/0" xsi:type="string">downloadableProduct::with_two_separately_links</data> <data name="cart/data/grand_total" xsi:type="string">22.43</data> <data name="cart/data/subtotal" xsi:type="string">22.43</data> @@ -87,7 +84,7 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertSubtotalInMiniShoppingCart" /> </variation> <variation name="AddProductsToShoppingCartEntityTestVariation7"> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2, mftf_migrated:yes</data> <data name="productsData/0" xsi:type="string">groupedProduct::three_simple_products</data> <data name="cart/data/grand_total" xsi:type="string">1950</data> <data name="cart/data/subtotal" xsi:type="string">1920</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> @@ -123,6 +119,7 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertSubtotalInMiniShoppingCart" /> </variation> <variation name="AddProductsToShoppingCartEntityTestVariation9" summary="Add dynamic bundle product to shopping cart with disabled mini shoping cart sidebar" ticketId="MAGETWO-35535"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="configData" xsi:type="string">disable_display_shopping_cart_sidebar</data> <data name="flushCache" xsi:type="boolean">true</data> <data name="productsData/0" xsi:type="string">bundleProduct::bundle_dynamic_product</data> @@ -136,6 +133,7 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertMyCartLinkRedirect" /> </variation> <variation name="AddProductsToShoppingCartEntityTestVariation10" summary="Verify Top Destinations for Country Options configuration applied in shopping cart" ticketId="MAGETWO-38700"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="productsData/0" xsi:type="string">catalogProductSimple::product_10_dollar</data> <data name="configData" xsi:type="string">top_destinations_DE_ES_GB</data> <data name="cart/data/grand_total" xsi:type="string">15.00</data> @@ -149,7 +147,7 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertTopDestinationsInSelect" /> </variation> <variation name="VariationWithCartItemsDisplayDefaultLimitation" summary="Verify visible items count in minicart and checkout summary block according to configuration" ticketId="MAGETWO-63442"> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2, mftf_migrated:yes</data> <data name="productsData/0" xsi:type="string">catalogProductSimple::default</data> <data name="productsData/1" xsi:type="string">catalogProductSimple::default</data> <data name="productsData/2" xsi:type="string">catalogProductSimple::default</data> @@ -170,7 +168,7 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertVisibleItemsQtyInMiniShoppingCart" /> </variation> <variation name="VariationWithCartItemsDisplayDefaultLimitationAndDefaultTotalQty" summary="Verify visible items count in minicart and checkout summary block according to configuration" ticketId="MAGETWO-63441"> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2, mftf_migrated:yes</data> <data name="productsData/0" xsi:type="string">catalogProductSimple::default</data> <data name="productsData/1" xsi:type="string">catalogProductSimple::default</data> <data name="productsData/2" xsi:type="string">catalogProductSimple::default</data> @@ -190,7 +188,7 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertVisibleItemsQtyInMiniShoppingCart" /> </variation> <variation name="VariationWithCartItemsDisplayDefaultLimitationOnCheckoutAndDefaultTotalQty" summary="Verify visible items count in minicart and checkout summary block according to configuration" ticketId="MAGETWO-63447"> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2, mftf_migrated:yes</data> <data name="productsData/0" xsi:type="string">catalogProductSimple::default</data> <data name="productsData/1" xsi:type="string">catalogProductSimple::default</data> <data name="productsData/2" xsi:type="string">catalogProductSimple::default</data> @@ -211,7 +209,7 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertLinkGoToCartNotPresentInSummaryBlock" /> </variation> <variation name="VariationWithCartItemsDisplayDefaultLimitationOnCheckout" summary="Verify visible items count in minicart and checkout summary block according to configuration" ticketId="MAGETWO-63448"> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2, mftf_migrated:yes</data> <data name="productsData/0" xsi:type="string">catalogProductSimple::default</data> <data name="productsData/1" xsi:type="string">catalogProductSimple::default</data> <data name="productsData/2" xsi:type="string">catalogProductSimple::default</data> @@ -233,7 +231,7 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertLinkGoToCartPresentInSummaryBlock" /> </variation> <variation name="VariationWithCartItemsDisplayCustomLimitations" summary="Verify visible items count in minicart and checkout summary block according to configuration" ticketId="MAGETWO-63445, MAGETWO-63449, MAGETWO-63452"> - <data name="tag" xsi:type="string">severity:S2</data> + <data name="tag" xsi:type="string">severity:S2, mftf_migrated:yes</data> <data name="productsData/0" xsi:type="string">catalogProductVirtual::default</data> <data name="productsData/1" xsi:type="string">catalogProductVirtual::default</data> <data name="productsData/2" xsi:type="string">catalogProductVirtual::default</data> @@ -252,6 +250,7 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertLinkGoToCartPresentInSummaryBlock" /> </variation> <variation name="AddProductsToShoppingCartEntityTestVariation11" summary="Create product with all types of custom options that are required and trigger JS validation" ticketId="MAGETWO-45389"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="productsData/0" xsi:type="string">catalogProductSimple::with_all_required_custom_option</data> <data name="cart/data/subtotal" xsi:type="string">6160.00</data> <data name="isValidationFailed" xsi:type="boolean">true</data> @@ -259,12 +258,14 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertProductsAbsentInShoppingCart" /> </variation> <variation name="AddProductsToShoppingCartEntityTestVariation12" summary="Create product with all types of custom options that are required and check that item options for product(s) display with correct information" ticketId="MAGETWO-45389"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="productsData/0" xsi:type="string">catalogProductSimple::with_all_required_custom_option</data> <data name="cart/data/subtotal" xsi:type="string">6160.00</data> <data name="isValidationFailed" xsi:type="boolean">true</data> <constraint name="Magento\Checkout\Test\Constraint\AssertCartItemsOptions" /> </variation> <variation name="AddProductsToShoppingCartEntityTestVariation13" summary="Check Bundle on Cart Configuration Page." ticketId="MAGETWO-71634"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="productsData/0" xsi:type="string">bundleProduct::bundle_with_multiselect_option_one_selection</data> <data name="cart/data/grand_total" xsi:type="string">105</data> <data name="cart/data/subtotal" xsi:type="string">100</data> @@ -277,6 +278,7 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertGrandTotalInShoppingCart" /> </variation> <variation name="AddProductsToShoppingCartEntityTestVariation14" summary="Check Bundle on Cart Configuration Page." ticketId="MAGETWO-71634"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="productsData/0" xsi:type="string">bundleProduct::bundle_with_multiselect_option_two_selections</data> <data name="cart/data/grand_total" xsi:type="string">665</data> <data name="cart/data/subtotal" xsi:type="string">660</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/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/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/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/Downloadable/Test/TestCase/CreateDownloadableProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/CreateDownloadableProductEntityTest.xml index 5afe815edad45..b61a244345a58 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/CreateDownloadableProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/CreateDownloadableProductEntityTest.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\Downloadable\Test\TestCase\CreateDownloadableProductEntityTest" summary="Create Downloadable Product" ticketId="MAGETWO-23425"> <variation name="CreateDownloadableProductEntityTestVariation1" summary="Create Downloadable Product with Required Fields Only and Assign it to the Category" ticketId="MAGETWO-13595"> - <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="product/data/name" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/sku" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">10</data> @@ -24,6 +24,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductInCart" /> </variation> <variation name="CreateDownloadableProductEntityTestVariation2" summary="Create product with default set links"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/sku" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/price/value" xsi:type="string">100</data> @@ -61,6 +62,7 @@ <constraint name="Magento\Downloadable\Test\Constraint\AssertDownloadableLinksData" /> </variation> <variation name="CreateDownloadableProductEntityTestVariation4" summary="Create product with custom options"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/sku" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/price/value" xsi:type="string">33</data> @@ -102,6 +104,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductSearchableBySku" /> </variation> <variation name="CreateDownloadableProductEntityTestVariation6" summary="Create product with out of stock status"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/sku" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/price/value" xsi:type="string">100</data> @@ -117,6 +120,7 @@ <constraint name="Magento\Downloadable\Test\Constraint\AssertDownloadableProductForm" /> </variation> <variation name="CreateDownloadableProductEntityTestVariation7" summary="Create product with manage stock"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/sku" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/price/value" xsi:type="string">9999</data> @@ -133,6 +137,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductOutOfStock" /> </variation> <variation name="CreateDownloadableProductEntityTestVariation8" summary="Create product without tax class id"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/sku" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/price/value" xsi:type="string">98</data> @@ -217,6 +222,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductCustomOptionsOnProductPage" /> </variation> <variation name="CreateDownloadableProductEntityTestVariation12" summary="Create product without filling quantity and stock"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/sku" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/price/value" xsi:type="string">100</data> @@ -231,6 +237,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductPage" /> </variation> <variation name="CreateDownloadableProductEntityTestVariation13" summary="Create product with special price"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/sku" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/price/value" xsi:type="string">10</data> @@ -248,6 +255,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductSpecialPriceOnProductPage" /> </variation> <variation name="CreateDownloadableProductEntityTestVariation14" summary="Create product with group price"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/sku" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/price/value" xsi:type="string">365</data> @@ -263,6 +271,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductPage" /> </variation> <variation name="CreateDownloadableProductEntityTestVariation15" summary="Create product with tier price"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/sku" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/price/value" xsi:type="string">250</data> @@ -280,6 +289,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductTierPriceOnProductPage" /> </variation> <variation name="CreateDownloadableProductEntityTestVariation16" summary="Create downloadable product and assign it to custom website"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/sku" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/price/value" xsi:type="string">350</data> 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/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/SalesRule/Test/TestCase/CreateSalesRuleEntityPartOneTest.xml b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/CreateSalesRuleEntityPartOneTest.xml index 7ea54daccf5a8..23256b192b2d0 100644 --- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/CreateSalesRuleEntityPartOneTest.xml +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/CreateSalesRuleEntityPartOneTest.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\SalesRule\Test\TestCase\CreateSalesRuleEntityPartOneTest" summary="Create Cart Price Rule" ticketId="MAGETWO-24855"> <variation name="CreateSalesRuleEntityTestVariation7"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="address/data/country_id" xsi:type="string">United States</data> <data name="address/data/region_id" xsi:type="string">California</data> <data name="address/data/postcode" xsi:type="string">95814</data> @@ -37,6 +38,7 @@ <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleFreeShippingIsApplied" /> </variation> <variation name="CreateSalesRuleEntityTestVariation8"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="address/data/country_id" xsi:type="string">United States</data> <data name="address/data/region_id" xsi:type="string">California</data> <data name="address/data/postcode" xsi:type="string">95814</data> @@ -66,6 +68,7 @@ <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleConditionIsApplied" /> </variation> <variation name="CreateSalesRuleEntityTestVariation9"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="salesRule/data/name" xsi:type="string">Cart Price Rule9 %isolation%</data> <data name="salesRule/data/description" xsi:type="string">Cart Price Rule Description %isolation%</data> <data name="salesRule/data/is_active" xsi:type="string">Yes</data> @@ -85,6 +88,7 @@ <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleConditionIsNotApplied" /> </variation> <variation name="CreateSalesRuleEntityTestVariation10"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="salesRule/data/name" xsi:type="string">Cart Price Rule10 %isolation%</data> <data name="salesRule/data/description" xsi:type="string">Cart Price Rule Description %isolation%</data> <data name="salesRule/data/is_active" xsi:type="string">Yes</data> @@ -110,6 +114,7 @@ <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleConditionIsApplied" /> </variation> <variation name="CreateSalesRuleEntityTestVariation11"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="salesRule/data/name" xsi:type="string">Cart Price Rule11 %isolation%</data> <data name="salesRule/data/description" xsi:type="string">Cart Price Rule Description %isolation%</data> <data name="salesRule/data/is_active" xsi:type="string">Yes</data> diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/DeleteSalesRuleEntityTest.xml b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/DeleteSalesRuleEntityTest.xml index ec3ccf331c2f3..c9af05b8f779a 100644 --- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/DeleteSalesRuleEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/DeleteSalesRuleEntityTest.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\SalesRule\Test\TestCase\DeleteSalesRuleEntityTest" summary="Delete Sales Rule" ticketId="MAGETWO-24985"> <variation name="DeleteSalesRuleEntityTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="salesRule/dataset" xsi:type="string">active_sales_rule_with_percent_price_discount_coupon</data> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleSuccessDeleteMessage" /> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleIsNotPresentedInGrid" /> </variation> <variation name="DeleteSalesRuleEntityTestVariation2"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="salesRule/dataset" xsi:type="string">active_sales_rule_with_complex_conditions</data> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleSuccessDeleteMessage" /> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleIsNotPresentedInGrid" /> </variation> <variation name="DeleteSalesRuleEntityTestVariation3" summary="Assert That Cart Price Rule Condition Is Not Applied" ticketId="MAGETWO-16987"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="salesRule/dataset" xsi:type="string">inactive_sales_rule</data> <data name="productForSalesRule1/dataset" xsi:type="string">simple_for_salesrule_1</data> <data name="productQuantity/productForSalesRule1" xsi:type="string">1</data> 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/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/CreateStoreEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreEntityTest.xml index 31e7f611a12e8..d86c36c984dbd 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreEntityTest.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\CreateStoreEntityTest" summary="Create Store View" ticketId="MAGETWO-27647"> <variation name="CreateStoreEntityTestVariation1"> - <data name="tag" xsi:type="string">severity:S1</data> + <data name="tag" xsi:type="string">severity:S1, mftf_migrated:yes</data> <data name="store/data/group_id/dataset" xsi:type="string">default</data> <data name="store/data/name" xsi:type="string">store_name_%isolation%</data> <data name="store/data/code" xsi:type="string">storecode_%isolation%</data> @@ -18,7 +18,7 @@ <constraint name="Magento\Store\Test\Constraint\AssertStoreFrontend" /> </variation> <variation name="CreateStoreEntityTestVariation2"> - <data name="tag" xsi:type="string">severity:S3</data> + <data name="tag" xsi:type="string">severity:S3, mftf_migrated:yes</data> <data name="store/data/group_id/dataset" xsi:type="string">default</data> <data name="store/data/name" xsi:type="string">store_name_%isolation%</data> <data name="store/data/code" xsi:type="string">storecode_%isolation%</data> @@ -28,7 +28,7 @@ <constraint name="Magento\Store\Test\Constraint\AssertStoreNotOnFrontend" /> </variation> <variation name="CreateStoreEntityTestVariation3"> - <data name="tag" xsi:type="string">severity:S1</data> + <data name="tag" xsi:type="string">severity:S1, mftf_migrated:yes</data> <data name="store/data/group_id/dataset" xsi:type="string">custom</data> <data name="store/data/name" xsi:type="string">store_name_%isolation%</data> <data name="store/data/code" xsi:type="string">storecode_%isolation%</data> @@ -40,7 +40,7 @@ <constraint name="Magento\Store\Test\Constraint\AssertStoreFrontend" /> </variation> <variation name="CreateStoreEntityTestVariation4" summary="Create New Localized Store View" ticketId="MAGETWO-12405"> - <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, severity:S0</data> + <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, severity:S0, mftf_migrated:yes</data> <data name="store/data/group_id/dataset" xsi:type="string">default</data> <data name="store/data/name" xsi:type="string">DE_%isolation%</data> <data name="store/data/code" xsi:type="string">de_%isolation%</data> @@ -51,12 +51,14 @@ <constraint name="Magento\Store\Test\Constraint\AssertStoreInGrid" /> </variation> <variation name="CreateStoreEntityTestVariation5" summary="Check the absence of delete button" ticketId="MAGETWO-17475"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="store/dataset" xsi:type="string">custom_store</data> <constraint name="Magento\Store\Test\Constraint\AssertStoreSuccessSaveMessage" /> <constraint name="Magento\Store\Test\Constraint\AssertStoreForm" /> <constraint name="Magento\Store\Test\Constraint\AssertStoreNoDeleteButton" /> </variation> <variation name="CreateDisabledStoreView" summary="Create disabled store view" ticketId="MAGETWO-17475"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="store/data/group_id/dataset" xsi:type="string">custom_new_group</data> <data name="store/data/name" xsi:type="string">store_name_%isolation%</data> <data name="store/data/code" xsi:type="string">storecode_%isolation%</data> 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/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/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/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/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/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/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/Config/Model/Config/Structure/Reader/_files/config.xml b/dev/tests/integration/testsuite/Magento/Config/Model/Config/Structure/Reader/_files/config.xml index 95b9c7bd73e63..f473af1bce37c 100644 --- a/dev/tests/integration/testsuite/Magento/Config/Model/Config/Structure/Reader/_files/config.xml +++ b/dev/tests/integration/testsuite/Magento/Config/Model/Config/Structure/Reader/_files/config.xml @@ -74,7 +74,7 @@ <label>Payments Pro</label> <attribute type="activity_path">payment/paypal_payment_pro/active</attribute> <group id="configuration_details"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-pro.html</comment> + <comment>https://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-pro.html</comment> </group> <group id="paypal_payflow_required" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> <field id="enable_paypal_payflow"> @@ -94,7 +94,7 @@ <comment>Accept credit card and PayPal payments securely.</comment> <attribute type="activity_path">payment/wps_express/active</attribute> <group id="configuration_details"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-standard.html</comment> + <comment>https://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-standard.html</comment> </group> <group id="express_checkout_required"> <group id="express_checkout_required_express_checkout"> @@ -162,7 +162,7 @@ <comment>Accept credit card and PayPal payments securely.</comment> <attribute type="activity_path">payment/wps_express/active</attribute> <group id="configuration_details"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-standard.html</comment> + <comment>https://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-standard.html</comment> </group> <group id="express_checkout_required"> <group id="express_checkout_required_express_checkout"> @@ -233,7 +233,7 @@ <comment>Accept credit card and PayPal payments securely.</comment> <attribute type="activity_path">payment/wps_express/active</attribute> <group id="configuration_details"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-standard.html</comment> + <comment>https://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-standard.html</comment> </group> <group id="express_checkout_required"> <group id="express_checkout_required_express_checkout"> @@ -278,7 +278,7 @@ <label>Website Payments Pro</label> <attribute type="activity_path">payment/paypal_payment_pro/active</attribute> <group id="configuration_details"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-pro.html</comment> + <comment>https://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-pro.html</comment> </group> <group id="paypal_payflow_required" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> <group id="paypal_payflow_api_settings"> 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/GraphQl/Config/GraphQlReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php index 10a6b9d8caae4..af8d1c7af134b 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php @@ -35,6 +35,9 @@ class GraphQlReaderTest extends \PHPUnit\Framework\TestCase /** @var SerializerInterface */ private $jsonSerializer; + /** + * @inheritdoc + */ protected function setUp() { $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); @@ -183,6 +186,7 @@ enumValues(includeDeprecated: true) { $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/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/Ups/_files/enable_ups_shipping_method.php b/dev/tests/integration/testsuite/Magento/GraphQl/Ups/_files/enable_ups_shipping_method.php index ffdc215d50a68..42931db75a433 100644 --- a/dev/tests/integration/testsuite/Magento/GraphQl/Ups/_files/enable_ups_shipping_method.php +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Ups/_files/enable_ups_shipping_method.php @@ -16,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/GraphQl/Ups/_files/enable_ups_shipping_method_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Ups/_files/enable_ups_shipping_method_rollback.php index 243ec0217a0bb..cf6dc08dd91a4 100644 --- a/dev/tests/integration/testsuite/Magento/GraphQl/Ups/_files/enable_ups_shipping_method_rollback.php +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Ups/_files/enable_ups_shipping_method_rollback.php @@ -15,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_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/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/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/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/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/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/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/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/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 696b1964101a5..a8a7e419373b8 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -374,6 +374,16 @@ <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> @@ -444,6 +454,11 @@ <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> @@ -781,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", ""); @@ -2220,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"> @@ -39930,7 +39999,7 @@ vars.putObject("category", categories[number]); <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 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> @@ -40727,7 +40796,7 @@ vars.putObject("randomIntGenerator", random); <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":"{\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> @@ -40878,7 +40947,7 @@ vars.putObject("randomIntGenerator", random); <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":"{\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> @@ -40917,7 +40986,7 @@ vars.putObject("randomIntGenerator", random); <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 address_type\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> @@ -40941,7 +41010,7 @@ vars.putObject("randomIntGenerator", random); <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"},"address_type":"SHIPPING"}]}}}}</stringProp> + <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> @@ -41068,7 +41137,7 @@ vars.putObject("randomIntGenerator", random); <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":"{\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> @@ -41107,7 +41176,7 @@ vars.putObject("randomIntGenerator", random); <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 address_type\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <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> @@ -41131,7 +41200,7 @@ vars.putObject("randomIntGenerator", random); <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"},"address_type":"BILLING"}}}}}</stringProp> + <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> @@ -41278,7 +41347,7 @@ vars.put("product_sku", product.get("sku")); <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":"mutation { \n addSimpleProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cart_items: [\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> @@ -41498,7 +41567,7 @@ vars.put("product_sku", product.get("sku")); <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 cart_items: [\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> @@ -41670,7 +41739,7 @@ vars.put("product_sku", product.get("sku")); <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":"mutation { \n addSimpleProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cart_items: [\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> @@ -41710,7 +41779,7 @@ vars.put("product_sku", product.get("sku")); <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":"{\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> @@ -41757,7 +41826,7 @@ vars.put("product_sku", product.get("sku")); <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 qty\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <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> @@ -41781,7 +41850,7 @@ vars.put("product_sku", product.get("sku")); <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}","qty":5}]}}}}</stringProp> + <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> @@ -41976,7 +42045,7 @@ vars.put("product_sku", product.get("sku")); <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 cart_items: [\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> @@ -42016,7 +42085,7 @@ vars.put("product_sku", product.get("sku")); <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":"{\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> @@ -42063,7 +42132,7 @@ vars.put("product_sku", product.get("sku")); <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 qty\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <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> @@ -42087,7 +42156,7 @@ vars.put("product_sku", product.get("sku")); <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}","qty":5}]}}}}</stringProp> + <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> @@ -42234,7 +42303,7 @@ vars.put("product_sku", product.get("sku")); <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":"mutation { \n addSimpleProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cart_items: [\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> @@ -42274,7 +42343,7 @@ vars.put("product_sku", product.get("sku")); <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":"{\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> @@ -42321,7 +42390,7 @@ vars.put("product_sku", product.get("sku")); <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 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> @@ -42540,7 +42609,7 @@ vars.put("product_sku", product.get("sku")); <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 cart_items: [\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> @@ -42580,7 +42649,7 @@ vars.put("product_sku", product.get("sku")); <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":"{\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> @@ -42627,7 +42696,7 @@ vars.put("product_sku", product.get("sku")); <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 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> @@ -42662,11 +42731,11 @@ vars.put("product_sku", product.get("sku")); </hashTree> - <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="GraphQL Checkout By Guest" enabled="true"> + <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">${graphqlCheckoutByGuestPercentage}</stringProp> + <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"> @@ -42687,7 +42756,7 @@ if (testLabel <hashTree/> <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> <stringProp name="BeanShellSampler.query"> - vars.put("testLabel", "GraphQL Checkout By Guest"); + vars.put("testLabel", "GraphQL Apply Coupon To Cart"); </stringProp> <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> </BeanShellSampler> @@ -42772,13 +42841,33 @@ vars.putObject("randomIntGenerator", random); <hashTree/> </hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Empty 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":"{\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 addSimpleProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cart_items: [\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> @@ -42797,12 +42886,68 @@ vars.putObject("randomIntGenerator", random); <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> + <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="1901638450">{"data":{"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> + + <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> @@ -42810,14 +42955,126 @@ vars.putObject("randomIntGenerator", random); </ResponseAssertion> <hashTree/> </hashTree> + </hashTree> + - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Configurable Product Data" enabled="true"> + <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("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")); @@ -42828,16 +43085,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 cart_items: [\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> @@ -42856,36 +43113,853 @@ 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/> - - <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> + + <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/> - </hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Configurable Product To Cart" enabled="true"> + <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 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 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 cart_items: [\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> @@ -42945,7 +44019,7 @@ vars.put("product_sku", product.get("sku")); <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":"mutation { \n addSimpleProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cart_items: [\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> @@ -42985,7 +44059,7 @@ vars.put("product_sku", product.get("sku")); <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 address_type\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <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> @@ -43009,7 +44083,7 @@ vars.put("product_sku", product.get("sku")); <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"},"address_type":"BILLING"}}}}}</stringProp> + <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> @@ -43024,7 +44098,7 @@ vars.put("product_sku", product.get("sku")); <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 address_type\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> @@ -43048,7 +44122,7 @@ vars.put("product_sku", product.get("sku")); <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"},"address_type":"SHIPPING"}]}}}}</stringProp> + <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> @@ -43102,7 +44176,7 @@ vars.put("product_sku", product.get("sku")); <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 address_id\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <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> @@ -43123,16 +44197,7 @@ vars.put("product_sku", product.get("sku")); <stringProp name="HTTPSampler.embedded_url_re"/> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_current_shipping_address.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">address_id</stringProp> - <stringProp name="JSONPATH">$.data.cart.shipping_addresses[0].address_id</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - </hashTree> + <hashTree/> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Set Shipping Method On Cart" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> @@ -43140,7 +44205,7 @@ vars.put("product_sku", product.get("sku")); <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 cart_address_id: ${address_id}\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.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> @@ -43172,6 +44237,100 @@ vars.put("product_sku", product.get("sku")); </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> </hashTree> 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()); + } +}