From 30fbdcba0d6b9756402bb13bce303f001b3fc17b Mon Sep 17 00:00:00 2001 From: aplapana Date: Wed, 7 Feb 2024 15:25:50 +0200 Subject: [PATCH 01/38] ACP2E-2770: Double-byte characters (special characters) in Product Name field blocks product creation in backend --- app/code/Magento/Catalog/Helper/Product.php | 8 +++++--- app/code/Magento/Catalog/Model/Product/Url.php | 9 ++++++++- app/code/Magento/Catalog/etc/adminhtml/system.xml | 4 ++++ app/code/Magento/Catalog/etc/config.xml | 1 + app/code/Magento/Catalog/i18n/en_US.csv | 1 + 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Catalog/Helper/Product.php b/app/code/Magento/Catalog/Helper/Product.php index 73b5e4af78d4..2adaee10fe47 100644 --- a/app/code/Magento/Catalog/Helper/Product.php +++ b/app/code/Magento/Catalog/Helper/Product.php @@ -18,11 +18,13 @@ */ class Product extends \Magento\Framework\Url\Helper\Data { - const XML_PATH_PRODUCT_URL_USE_CATEGORY = 'catalog/seo/product_use_categories'; + public const XML_PATH_PRODUCT_URL_USE_CATEGORY = 'catalog/seo/product_use_categories'; - const XML_PATH_USE_PRODUCT_CANONICAL_TAG = 'catalog/seo/product_canonical_tag'; + public const XML_PATH_USE_PRODUCT_CANONICAL_TAG = 'catalog/seo/product_canonical_tag'; - const XML_PATH_AUTO_GENERATE_MASK = 'catalog/fields_masks'; + public const XML_PATH_AUTO_GENERATE_MASK = 'catalog/fields_masks'; + + public const XML_PATH_APPLY_TRANSLITERATION_TO_URL = 'catalog/seo/product_url_transliteration'; /** * Flag that shows if Magento has to check product to be saleable (enabled and/or inStock) diff --git a/app/code/Magento/Catalog/Model/Product/Url.php b/app/code/Magento/Catalog/Model/Product/Url.php index 1ca946c2c47a..1a9f4ea8a750 100644 --- a/app/code/Magento/Catalog/Model/Product/Url.php +++ b/app/code/Magento/Catalog/Model/Product/Url.php @@ -114,7 +114,14 @@ public function getProductUrl($product, $useSid = null) */ public function formatUrlKey($str) { - return $this->filter->translitUrl($str); + if ($this->scopeConfig->getValue( + \Magento\Catalog\Helper\Product::XML_PATH_APPLY_TRANSLITERATION_TO_URL, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + )) { + return $this->filter->translitUrl($str); + } + + return $str; } /** diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml index 5df7412ccdf6..18e0e256ae81 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/system.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml @@ -127,6 +127,10 @@ Magento\Config\Model\Config\Source\Yesno + + + Magento\Config\Model\Config\Source\Yesno + diff --git a/app/code/Magento/Catalog/etc/config.xml b/app/code/Magento/Catalog/etc/config.xml index f8a1b600bb0b..04f5e8fb3e88 100644 --- a/app/code/Magento/Catalog/etc/config.xml +++ b/app/code/Magento/Catalog/etc/config.xml @@ -48,6 +48,7 @@ - 0 0 + 1 m,d,y diff --git a/app/code/Magento/Catalog/i18n/en_US.csv b/app/code/Magento/Catalog/i18n/en_US.csv index b9fdf6b4324c..914c250dd770 100644 --- a/app/code/Magento/Catalog/i18n/en_US.csv +++ b/app/code/Magento/Catalog/i18n/en_US.csv @@ -665,6 +665,7 @@ Comma-separated.,Comma-separated. "Page Title Separator","Page Title Separator" "Use Canonical Link Meta Tag For Categories","Use Canonical Link Meta Tag For Categories" "Use Canonical Link Meta Tag For Products","Use Canonical Link Meta Tag For Products" +"Apply transliteration for product URL","Apply transliteration for product URL" "Catalog Price Scope","Catalog Price Scope" "This defines the base currency scope (""Currency Setup"" > ""Currency Options"" > ""Base Currency"").","This defines the base currency scope (""Currency Setup"" > ""Currency Options"" > ""Base Currency"")." "Category Top Navigation","Category Top Navigation" From ed998c3ec083415d3813b36e88c98c2172444f44 Mon Sep 17 00:00:00 2001 From: aplapana Date: Wed, 7 Feb 2024 18:21:54 +0200 Subject: [PATCH 02/38] ACP2E-2770: Double-byte characters (special characters) in Product Name field blocks product creation in backend --- app/code/Magento/Catalog/Helper/Product.php | 4 ---- .../Test/Unit/Model/Product/UrlTest.php | 20 ++++++++++++++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Catalog/Helper/Product.php b/app/code/Magento/Catalog/Helper/Product.php index 2adaee10fe47..11806685b664 100644 --- a/app/code/Magento/Catalog/Helper/Product.php +++ b/app/code/Magento/Catalog/Helper/Product.php @@ -49,8 +49,6 @@ class Product extends \Magento\Framework\Url\Helper\Data protected $_assetRepo; /** - * Core registry - * * @var \Magento\Framework\Registry */ protected $_coreRegistry; @@ -61,8 +59,6 @@ class Product extends \Magento\Framework\Url\Helper\Data protected $_attributeConfig; /** - * Catalog session - * * @var \Magento\Catalog\Model\Session */ protected $_catalogSession; diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php index 227ee5739ba6..b576c3089422 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php @@ -11,6 +11,7 @@ use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Url; use Magento\Catalog\Model\Product\Url as ProductUrl; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Filter\FilterManager; use Magento\Framework\Session\SidResolverInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; @@ -53,6 +54,11 @@ class UrlTest extends TestCase */ protected $sidResolver; + /** + * @var ScopeConfigInterface + */ + private ScopeConfigInterface $scopeConfig; + protected function setUp(): void { $this->filter = $this->getMockBuilder( @@ -87,6 +93,8 @@ protected function setUp(): void $urlFactory->method('create') ->willReturn($this->url); + $this->scopeConfig = $this->createMock(ScopeConfigInterface::class); + $objectManager = new ObjectManager($this); $this->model = $objectManager->getObject( ProductUrl::class, @@ -96,11 +104,15 @@ protected function setUp(): void 'storeManager' => $storeManager, 'urlFactory' => $urlFactory, 'sidResolver' => $this->sidResolver, + 'scopeConfig' => $this->scopeConfig ] ); } - public function testFormatUrlKey() + /** + * @return void + */ + public function testFormatUrlKey(): void { $strIn = 'Some string'; $resultString = 'some'; @@ -115,6 +127,12 @@ public function testFormatUrlKey() $resultString ); + $this->scopeConfig->expects($this->once()) + ->method('getValue') + ->with( + \Magento\Catalog\Helper\Product::XML_PATH_APPLY_TRANSLITERATION_TO_URL, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + )->willReturn(true); $this->assertEquals($resultString, $this->model->formatUrlKey($strIn)); } From 2cc8a847962123e0f43b0347a2dbc9f8e4ac959a Mon Sep 17 00:00:00 2001 From: aplapana Date: Wed, 7 Feb 2024 19:43:17 +0200 Subject: [PATCH 03/38] ACP2E-2770: Double-byte characters (special characters) in Product Name field blocks product creation in backend --- app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php index b576c3089422..3a3138fe502f 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php @@ -22,6 +22,9 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class UrlTest extends TestCase { /** From 9d73f27a30138040889d93d1d0da0858a06aae5c Mon Sep 17 00:00:00 2001 From: aplapana Date: Fri, 9 Feb 2024 12:17:53 +0200 Subject: [PATCH 04/38] ACP2E-2770: Double-byte characters (special characters) in Product Name field blocks product creation in backend --- app/code/Magento/Catalog/Model/Product/Url.php | 4 ++++ .../Catalog/Test/Unit/Model/Product/UrlTest.php | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/app/code/Magento/Catalog/Model/Product/Url.php b/app/code/Magento/Catalog/Model/Product/Url.php index 1a9f4ea8a750..2f10ba1a0cfd 100644 --- a/app/code/Magento/Catalog/Model/Product/Url.php +++ b/app/code/Magento/Catalog/Model/Product/Url.php @@ -119,6 +119,10 @@ public function formatUrlKey($str) \Magento\Store\Model\ScopeInterface::SCOPE_STORE )) { return $this->filter->translitUrl($str); + } else { + $str = preg_replace('/\s+/', '-', $str); + $str = mb_strtolower($str); + $str = trim($str, '-'); } return $str; diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php index 3a3138fe502f..14b613606329 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php @@ -139,6 +139,23 @@ public function testFormatUrlKey(): void $this->assertEquals($resultString, $this->model->formatUrlKey($strIn)); } + /** + * @return void + */ + public function testFormatUrlKeyWithoutTransliteration(): void + { + $strIn = 'Some string '; + $resultString = 'some-string'; + + $this->scopeConfig->expects($this->once()) + ->method('getValue') + ->with( + \Magento\Catalog\Helper\Product::XML_PATH_APPLY_TRANSLITERATION_TO_URL, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + )->willReturn(false); + $this->assertEquals($resultString, $this->model->formatUrlKey($strIn)); + } + /** * @dataProvider getUrlDataProvider * @covers \Magento\Catalog\Model\Product\Url::getUrl From f23f4a8e5de9ee72856d74192e1141ebdc97037f Mon Sep 17 00:00:00 2001 From: aplapana Date: Fri, 9 Feb 2024 15:25:19 +0200 Subject: [PATCH 05/38] ACP2E-2770: Double-byte characters (special characters) in Product Name field blocks product creation in backend --- app/code/Magento/Catalog/etc/adminhtml/system.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml index 18e0e256ae81..e82b08b5229b 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/system.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml @@ -116,21 +116,21 @@ - + + + Magento\Config\Model\Config\Source\Yesno + + - + Magento\Config\Model\Config\Source\Yesno - + Magento\Config\Model\Config\Source\Yesno - - - Magento\Config\Model\Config\Source\Yesno - From 2505e1c77668c2783e5821e558726cb0410c7282 Mon Sep 17 00:00:00 2001 From: flowers Date: Mon, 12 Feb 2024 12:24:47 -0600 Subject: [PATCH 06/38] ACP2E-2734: Emails are failing to send --- app/code/Magento/Sales/Model/EmailSenderHandler.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/code/Magento/Sales/Model/EmailSenderHandler.php b/app/code/Magento/Sales/Model/EmailSenderHandler.php index 3a7a5727d834..68eae2052c40 100644 --- a/app/code/Magento/Sales/Model/EmailSenderHandler.php +++ b/app/code/Magento/Sales/Model/EmailSenderHandler.php @@ -136,6 +136,11 @@ public function sendEmails() $item->setEmailSent(true), 'email_sent' ); + } else { + $this->entityResource->saveAttribute( + $item->setEmailSent(false), + 'email_sent' + ); } } } From b8429cb2e2e1e087129ea0c1671594f0625eec4c Mon Sep 17 00:00:00 2001 From: flowers Date: Mon, 12 Feb 2024 14:26:41 -0600 Subject: [PATCH 07/38] ACP2E-2734: Emails are failing to send --- app/code/Magento/Sales/Model/EmailSenderHandler.php | 4 ++++ .../Sales/Test/Unit/Model/EmailSenderHandlerTest.php | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/app/code/Magento/Sales/Model/EmailSenderHandler.php b/app/code/Magento/Sales/Model/EmailSenderHandler.php index 68eae2052c40..ddcaa989a0aa 100644 --- a/app/code/Magento/Sales/Model/EmailSenderHandler.php +++ b/app/code/Magento/Sales/Model/EmailSenderHandler.php @@ -105,6 +105,7 @@ public function __construct( /** * Handles asynchronous email sending + * * @return void */ public function sendEmails() @@ -137,6 +138,9 @@ public function sendEmails() 'email_sent' ); } else { + // When the email is failed to send, the email_sent attribute + // should be set to false, preventing this email from being + // sent over and over again in the next cron runs. $this->entityResource->saveAttribute( $item->setEmailSent(false), 'email_sent' diff --git a/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php b/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php index af044c866978..9dbc8002e5da 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php @@ -256,6 +256,12 @@ public function testExecute( ->expects($this->once()) ->method('saveAttribute') ->with($collectionItem); + } else { + $collectionItem + ->expects($this->once()) + ->method('setEmailSent') + ->with(false) + ->willReturn($collectionItem); } } } From f005d0e7bd69c43c34276c23295994b20506e55a Mon Sep 17 00:00:00 2001 From: flowers Date: Tue, 13 Feb 2024 10:20:42 -0600 Subject: [PATCH 08/38] ACP2E-2734: Emails are failing to send --- .../Sales/Model/EmailSenderHandler.php | 19 ++++--------- .../Unit/Model/EmailSenderHandlerTest.php | 28 +++++++------------ 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/app/code/Magento/Sales/Model/EmailSenderHandler.php b/app/code/Magento/Sales/Model/EmailSenderHandler.php index ddcaa989a0aa..82d3e6482404 100644 --- a/app/code/Magento/Sales/Model/EmailSenderHandler.php +++ b/app/code/Magento/Sales/Model/EmailSenderHandler.php @@ -132,20 +132,11 @@ public function sendEmails() /** @var \Magento\Sales\Model\AbstractModel $item */ foreach ($entityCollection->getItems() as $item) { - if ($this->emailSender->send($item, true)) { - $this->entityResource->saveAttribute( - $item->setEmailSent(true), - 'email_sent' - ); - } else { - // When the email is failed to send, the email_sent attribute - // should be set to false, preventing this email from being - // sent over and over again in the next cron runs. - $this->entityResource->saveAttribute( - $item->setEmailSent(false), - 'email_sent' - ); - } + $isEmailSent = $this->emailSender->send($item, true); + $this->entityResource->saveAttribute( + $item->setEmailSent($isEmailSent), + 'email_sent' + ); } } } diff --git a/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php b/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php index 9dbc8002e5da..4cbdf8ebfc35 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php @@ -245,24 +245,16 @@ public function testExecute( ->method('isEnabled') ->willReturn(true); - if ($emailSendingResult) { - $collectionItem - ->expects($this->once()) - ->method('setEmailSent') - ->with(true) - ->willReturn($collectionItem); - - $this->entityResource - ->expects($this->once()) - ->method('saveAttribute') - ->with($collectionItem); - } else { - $collectionItem - ->expects($this->once()) - ->method('setEmailSent') - ->with(false) - ->willReturn($collectionItem); - } + $collectionItem + ->expects($this->once()) + ->method('setEmailSent') + ->with($emailSendingResult) + ->willReturn($collectionItem); + + $this->entityResource + ->expects($this->once()) + ->method('saveAttribute') + ->with($collectionItem); } } From b171dc29fe363b8f1cc7433cb44764e6f878273e Mon Sep 17 00:00:00 2001 From: Olga Moyseyenko Date: Tue, 13 Feb 2024 21:15:31 -0600 Subject: [PATCH 09/38] ACP2E-2763: Table Rates Still Showing Even Though Free Shipping Is Applied --- app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php index 01328e0df63a..6e78d9d03209 100644 --- a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php +++ b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php @@ -151,9 +151,6 @@ public function collectRates(RateRequest $request) $freeWeight += (int) $item->getWeight(); } } - - $request->setPackageValue($request->getPackageValue() - $freePackageValue); - $request->setPackageValueWithDiscount($request->getPackageValueWithDiscount() - $freePackageValue); } if ($freeWeight > 0) { From 8a8b44b032abf5d690459b806197ac81d3bf4709 Mon Sep 17 00:00:00 2001 From: flowers Date: Thu, 15 Feb 2024 10:51:12 -0600 Subject: [PATCH 10/38] ACP2E-2734: Emails are failing to send --- .../Sales/Model/EmailSenderHandler.php | 36 +++++++++++++++---- app/code/Magento/Sales/etc/db_schema.xml | 8 ++--- app/code/Magento/Sales/etc/di.xml | 1 + 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/app/code/Magento/Sales/Model/EmailSenderHandler.php b/app/code/Magento/Sales/Model/EmailSenderHandler.php index 82d3e6482404..33d7a024a143 100644 --- a/app/code/Magento/Sales/Model/EmailSenderHandler.php +++ b/app/code/Magento/Sales/Model/EmailSenderHandler.php @@ -5,11 +5,15 @@ */ namespace Magento\Sales\Model; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Config\ValueFactory; use Magento\Framework\App\Config\ValueInterface; use Magento\Framework\App\ObjectManager; use Magento\Sales\Model\Order\Email\Container\IdentityInterface; +use Magento\Sales\Model\Order\Email\Sender; use Magento\Sales\Model\ResourceModel\Collection\AbstractCollection; +use Magento\Sales\Model\ResourceModel\EntityAbstract; +use Magento\Store\Model\StoreManagerInterface; /** * Sales emails sending @@ -68,16 +72,18 @@ class EmailSenderHandler * @var string */ private $modifyStartFromDate; + private int $maxSendAttempts; /** - * @param \Magento\Sales\Model\Order\Email\Sender $emailSender - * @param \Magento\Sales\Model\ResourceModel\EntityAbstract $entityResource + * @param Sender $emailSender + * @param EntityAbstract $entityResource * @param AbstractCollection $entityCollection - * @param \Magento\Framework\App\Config\ScopeConfigInterface $globalConfig + * @param ScopeConfigInterface $globalConfig * @param IdentityInterface|null $identityContainer - * @param \Magento\Store\Model\StoreManagerInterface|null $storeManager + * @param StoreManagerInterface|null $storeManager * @param ValueFactory|null $configValueFactory * @param string|null $modifyStartFromDate + * @param int $maxSendAttempts */ public function __construct( \Magento\Sales\Model\Order\Email\Sender $emailSender, @@ -87,7 +93,8 @@ public function __construct( IdentityInterface $identityContainer = null, \Magento\Store\Model\StoreManagerInterface $storeManager = null, ?ValueFactory $configValueFactory = null, - ?string $modifyStartFromDate = null + ?string $modifyStartFromDate = null, + int $maxSendAttempts = null ) { $this->emailSender = $emailSender; $this->entityResource = $entityResource; @@ -101,6 +108,7 @@ public function __construct( $this->configValueFactory = $configValueFactory ?: ObjectManager::getInstance()->get(ValueFactory::class); $this->modifyStartFromDate = $modifyStartFromDate ?: $this->modifyStartFromDate; + $this->maxSendAttempts = $maxSendAttempts ?? 3; } /** @@ -112,7 +120,13 @@ public function sendEmails() { if ($this->globalConfig->getValue('sales_email/general/async_sending')) { $this->entityCollection->addFieldToFilter('send_email', ['eq' => 1]); - $this->entityCollection->addFieldToFilter('email_sent', ['null' => true]); + $this->entityCollection->addFieldToFilter( + 'email_sent', + [ + ['null' => true], + ['lteq' => -1] + ] + ); $this->filterCollectionByStartFromDate($this->entityCollection); $this->entityCollection->setPageSize( $this->globalConfig->getValue('sales_email/general/sending_limit') @@ -132,9 +146,17 @@ public function sendEmails() /** @var \Magento\Sales\Model\AbstractModel $item */ foreach ($entityCollection->getItems() as $item) { + $sendAttempts = $item->getEmailSent() ?? -$this->maxSendAttempts; $isEmailSent = $this->emailSender->send($item, true); + + if ($isEmailSent) { + $sendAttempts = 1; + } else { + $sendAttempts++; + } + $this->entityResource->saveAttribute( - $item->setEmailSent($isEmailSent), + $item->setEmailSent($sendAttempts), 'email_sent' ); } diff --git a/app/code/Magento/Sales/etc/db_schema.xml b/app/code/Magento/Sales/etc/db_schema.xml index bdadd8df0b3c..c01354500eb7 100644 --- a/app/code/Magento/Sales/etc/db_schema.xml +++ b/app/code/Magento/Sales/etc/db_schema.xml @@ -149,7 +149,7 @@ - @@ -708,7 +708,7 @@ comment="Total Weight"/> - @@ -981,7 +981,7 @@ identity="false" comment="Is Used For Refund"/> - @@ -1278,7 +1278,7 @@ comment="Tax Amount"/> - diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml index d5dc1938bdab..1289cf4da01f 100644 --- a/app/code/Magento/Sales/etc/di.xml +++ b/app/code/Magento/Sales/etc/di.xml @@ -220,6 +220,7 @@ -1 day + 3 From 3b30eba011668a3b16059473754ce25cf5ffe5c8 Mon Sep 17 00:00:00 2001 From: flowers Date: Thu, 15 Feb 2024 12:55:11 -0600 Subject: [PATCH 11/38] ACP2E-2734: Emails are failing to send --- .../Sales/Model/EmailSenderHandler.php | 4 +++ .../Unit/Model/EmailSenderHandlerTest.php | 25 +++++++++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/Sales/Model/EmailSenderHandler.php b/app/code/Magento/Sales/Model/EmailSenderHandler.php index 33d7a024a143..2a4a3ab17b2b 100644 --- a/app/code/Magento/Sales/Model/EmailSenderHandler.php +++ b/app/code/Magento/Sales/Model/EmailSenderHandler.php @@ -72,6 +72,10 @@ class EmailSenderHandler * @var string */ private $modifyStartFromDate; + + /** + * @var int + */ private int $maxSendAttempts; /** diff --git a/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php b/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php index 4cbdf8ebfc35..b9cf2560633f 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php @@ -151,6 +151,7 @@ protected function setUp(): void * @param int $configValue * @param array|null $collectionItems * @param bool|null $emailSendingResult + * @param int|null $expectedIsEmailSent * * @return void * @dataProvider executeDataProvider @@ -159,7 +160,8 @@ protected function setUp(): void public function testExecute( int $configValue, ?array $collectionItems, - ?bool $emailSendingResult + ?bool $emailSendingResult, + ?int $expectedIsEmailSent ): void { $path = 'sales_email/general/async_sending'; @@ -175,7 +177,12 @@ public function testExecute( ->method('addFieldToFilter') ->withConsecutive( ['send_email', ['eq' => 1]], - ['email_sent', ['null' => true]], + ['email_sent', + [ + ['null' => true], + ['lteq' => -1] + ] + ], ['created_at', ['from' => $fromDate]] ); @@ -248,7 +255,7 @@ public function testExecute( $collectionItem ->expects($this->once()) ->method('setEmailSent') - ->with($emailSendingResult) + ->with($expectedIsEmailSent) ->willReturn($collectionItem); $this->entityResource @@ -280,22 +287,26 @@ public function executeDataProvider(): array [ 'configValue' => 1, 'collectionItems' => [clone $entityModel], - 'emailSendingResult' => true + 'emailSendingResult' => true, + 'expectedIsEmailSent' => 1 ], [ 'configValue' => 1, 'collectionItems' => [clone $entityModel], - 'emailSendingResult' => false + 'emailSendingResult' => false, + 'expectedIsEmailSent' => -2 ], [ 'configValue' => 1, 'collectionItems' => [], - 'emailSendingResult' => null + 'emailSendingResult' => null, + 'expectedIsEmailSent' => 1 ], [ 'configValue' => 0, 'collectionItems' => null, - 'emailSendingResult' => null + 'emailSendingResult' => null, + 'expectedIsEmailSent' => 1 ] ]; } From 9668008d9956199e220df4c27e348f7c87f619b4 Mon Sep 17 00:00:00 2001 From: flowers Date: Fri, 16 Feb 2024 13:22:25 -0600 Subject: [PATCH 12/38] ACP2E-2734: Emails are failing to send --- .../Sales/Model/EmailSenderHandler.php | 40 ++++++++----------- .../Unit/Model/EmailSenderHandlerTest.php | 13 ++++-- app/code/Magento/Sales/etc/config.xml | 1 + app/code/Magento/Sales/etc/di.xml | 1 - 4 files changed, 27 insertions(+), 28 deletions(-) diff --git a/app/code/Magento/Sales/Model/EmailSenderHandler.php b/app/code/Magento/Sales/Model/EmailSenderHandler.php index 2a4a3ab17b2b..7d59ea4566b9 100644 --- a/app/code/Magento/Sales/Model/EmailSenderHandler.php +++ b/app/code/Magento/Sales/Model/EmailSenderHandler.php @@ -26,14 +26,14 @@ class EmailSenderHandler /** * Email sender model. * - * @var \Magento\Sales\Model\Order\Email\Sender + * @var Sender */ protected $emailSender; /** * Entity resource model. * - * @var \Magento\Sales\Model\ResourceModel\EntityAbstract + * @var EntityAbstract */ protected $entityResource; @@ -47,7 +47,7 @@ class EmailSenderHandler /** * Global configuration storage. * - * @var \Magento\Framework\App\Config\ScopeConfigInterface + * @var ScopeConfigInterface */ protected $globalConfig; @@ -57,7 +57,7 @@ class EmailSenderHandler private $identityContainer; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ private $storeManager; @@ -73,11 +73,6 @@ class EmailSenderHandler */ private $modifyStartFromDate; - /** - * @var int - */ - private int $maxSendAttempts; - /** * @param Sender $emailSender * @param EntityAbstract $entityResource @@ -87,18 +82,16 @@ class EmailSenderHandler * @param StoreManagerInterface|null $storeManager * @param ValueFactory|null $configValueFactory * @param string|null $modifyStartFromDate - * @param int $maxSendAttempts */ public function __construct( - \Magento\Sales\Model\Order\Email\Sender $emailSender, - \Magento\Sales\Model\ResourceModel\EntityAbstract $entityResource, - AbstractCollection $entityCollection, - \Magento\Framework\App\Config\ScopeConfigInterface $globalConfig, - IdentityInterface $identityContainer = null, - \Magento\Store\Model\StoreManagerInterface $storeManager = null, - ?ValueFactory $configValueFactory = null, - ?string $modifyStartFromDate = null, - int $maxSendAttempts = null + Sender $emailSender, + EntityAbstract $entityResource, + AbstractCollection $entityCollection, + ScopeConfigInterface $globalConfig, + IdentityInterface $identityContainer = null, + StoreManagerInterface $storeManager = null, + ?ValueFactory $configValueFactory = null, + ?string $modifyStartFromDate = null, ) { $this->emailSender = $emailSender; $this->entityResource = $entityResource; @@ -108,11 +101,10 @@ public function __construct( $this->identityContainer = $identityContainer ?: ObjectManager::getInstance() ->get(\Magento\Sales\Model\Order\Email\Container\NullIdentity::class); $this->storeManager = $storeManager ?: ObjectManager::getInstance() - ->get(\Magento\Store\Model\StoreManagerInterface::class); + ->get(StoreManagerInterface::class); $this->configValueFactory = $configValueFactory ?: ObjectManager::getInstance()->get(ValueFactory::class); $this->modifyStartFromDate = $modifyStartFromDate ?: $this->modifyStartFromDate; - $this->maxSendAttempts = $maxSendAttempts ?? 3; } /** @@ -139,6 +131,8 @@ public function sendEmails() /** @var \Magento\Store\Api\Data\StoreInterface[] $stores */ $stores = $this->getStores(clone $this->entityCollection); + $maxSendAttempts = $this->globalConfig->getValue('sales_email/general/async_sending_attempts'); + /** @var \Magento\Store\Model\Store $store */ foreach ($stores as $store) { $this->identityContainer->setStore($store); @@ -150,7 +144,7 @@ public function sendEmails() /** @var \Magento\Sales\Model\AbstractModel $item */ foreach ($entityCollection->getItems() as $item) { - $sendAttempts = $item->getEmailSent() ?? -$this->maxSendAttempts; + $sendAttempts = $item->getEmailSent() ?? -$maxSendAttempts; $isEmailSent = $this->emailSender->send($item, true); if ($isEmailSent) { @@ -183,7 +177,7 @@ private function getStores( $entityCollection->addAttributeToSelect('store_id')->getSelect()->group('store_id'); /** @var \Magento\Sales\Model\EntityInterface $item */ foreach ($entityCollection->getItems() as $item) { - /** @var \Magento\Store\Model\StoreManagerInterface $store */ + /** @var StoreManagerInterface $store */ $store = $this->storeManager->getStore($item->getStoreId()); $stores[$item->getStoreId()] = $store; } diff --git a/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php b/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php index b9cf2560633f..599888a0d9a8 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php @@ -163,12 +163,17 @@ public function testExecute( ?bool $emailSendingResult, ?int $expectedIsEmailSent ): void { - $path = 'sales_email/general/async_sending'; - $this->globalConfig ->method('getValue') - ->withConsecutive([$path]) - ->willReturnOnConsecutiveCalls($configValue); + ->willReturnCallback(function ($path) use ($configValue) { + if ($path === 'sales_email/general/async_sending') { + return $configValue; + } + if ($path === 'sales_email/general/async_sending_attempts') { + return 3; + } + return null; + }); if ($configValue) { $nowDate = date('Y-m-d H:i:s'); diff --git a/app/code/Magento/Sales/etc/config.xml b/app/code/Magento/Sales/etc/config.xml index 92e1a46bb2d5..16543fd9f7e4 100644 --- a/app/code/Magento/Sales/etc/config.xml +++ b/app/code/Magento/Sales/etc/config.xml @@ -36,6 +36,7 @@ 0 + 3 50 diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml index 1289cf4da01f..d5dc1938bdab 100644 --- a/app/code/Magento/Sales/etc/di.xml +++ b/app/code/Magento/Sales/etc/di.xml @@ -220,7 +220,6 @@ -1 day - 3 From 49c9cce0f68679b55a6a928721f96ff7892ff3e4 Mon Sep 17 00:00:00 2001 From: flowers Date: Fri, 16 Feb 2024 13:24:44 -0600 Subject: [PATCH 13/38] ACP2E-2734: Emails are failing to send --- .../Magento/Sales/Model/EmailSenderHandler.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/Sales/Model/EmailSenderHandler.php b/app/code/Magento/Sales/Model/EmailSenderHandler.php index 7d59ea4566b9..748a12dff340 100644 --- a/app/code/Magento/Sales/Model/EmailSenderHandler.php +++ b/app/code/Magento/Sales/Model/EmailSenderHandler.php @@ -84,14 +84,14 @@ class EmailSenderHandler * @param string|null $modifyStartFromDate */ public function __construct( - Sender $emailSender, - EntityAbstract $entityResource, - AbstractCollection $entityCollection, - ScopeConfigInterface $globalConfig, - IdentityInterface $identityContainer = null, + Sender $emailSender, + EntityAbstract $entityResource, + AbstractCollection $entityCollection, + ScopeConfigInterface $globalConfig, + IdentityInterface $identityContainer = null, StoreManagerInterface $storeManager = null, - ?ValueFactory $configValueFactory = null, - ?string $modifyStartFromDate = null, + ?ValueFactory $configValueFactory = null, + ?string $modifyStartFromDate = null, ) { $this->emailSender = $emailSender; $this->entityResource = $entityResource; From a787a48cbc550d00b3afc385e5ff453739e45bab Mon Sep 17 00:00:00 2001 From: Dmytro Yushkin Date: Mon, 19 Feb 2024 14:06:07 -0600 Subject: [PATCH 14/38] ACP2E-2630: Issues saving advanced pricing on bundle products --- .../Bundle/Model/Option/SaveAction.php | 95 +++++++++++++++---- .../Bundle/Model/Product/SaveHandler.php | 26 ++--- .../Test/Unit/Model/Option/SaveActionTest.php | 3 - .../Magento/Framework/Data/Collection.php | 2 +- 4 files changed, 90 insertions(+), 36 deletions(-) diff --git a/app/code/Magento/Bundle/Model/Option/SaveAction.php b/app/code/Magento/Bundle/Model/Option/SaveAction.php index 2776f11db33f..7904a00f244d 100644 --- a/app/code/Magento/Bundle/Model/Option/SaveAction.php +++ b/app/code/Magento/Bundle/Model/Option/SaveAction.php @@ -84,19 +84,29 @@ public function __construct( * Bulk options save * * @param ProductInterface $bundleProduct - * @param OptionInterface[] $options + * @param array $options + * @param array $existingBundleProductOptions * @return void * @throws CouldNotSaveException - * @throws NoSuchEntityException * @throws InputException + * @throws NoSuchEntityException */ - public function saveBulk(ProductInterface $bundleProduct, array $options): void - { + public function saveBulk( + ProductInterface $bundleProduct, + array $options, + array $existingBundleProductOptions = [] + ): void { $metadata = $this->metadataPool->getMetadata(ProductInterface::class); $optionCollection = $this->type->getOptionsCollection($bundleProduct); foreach ($options as $option) { - $this->saveOptionItem($bundleProduct, $option, $optionCollection, $metadata); + $this->saveOptionItem( + $bundleProduct, + $option, + $optionCollection, + $metadata, + $existingBundleProductOptions + ); } $bundleProduct->setIsRelationsChanged(true); @@ -109,16 +119,18 @@ public function saveBulk(ProductInterface $bundleProduct, array $options): void * @param OptionInterface $option * @param Collection $optionCollection * @param EntityMetadataInterface $metadata + * @param array $existingBundleProductOptions * @return void * @throws CouldNotSaveException - * @throws NoSuchEntityException * @throws InputException + * @throws NoSuchEntityException */ private function saveOptionItem( ProductInterface $bundleProduct, OptionInterface $option, Collection $optionCollection, - EntityMetadataInterface $metadata + EntityMetadataInterface $metadata, + array $existingBundleProductOptions = [] ) : void { $linksToAdd = []; @@ -126,10 +138,8 @@ private function saveOptionItem( $parentId = $bundleProduct->getData($metadata->getLinkField()); $option->setParentId($parentId); $optionId = $option->getOptionId(); + $existingOption = $this->retrieveExistingOption($optionCollection, $option, $existingBundleProductOptions); - /** @var \Magento\Bundle\Model\Option $existingOption */ - $existingOption = $optionCollection->getItemById($option->getOptionId()) - ?? $optionCollection->getNewEmptyItem(); if (!$optionId || $existingOption->getParentId() != $parentId) { $option->setOptionId(null); $option->setDefaultTitle($option->getTitle()); @@ -137,14 +147,14 @@ private function saveOptionItem( $linksToAdd = $option->getProductLinks(); } } else { - if (!$existingOption->getOptionId()) { + if (!$existingOption || !$existingOption->getOptionId()) { throw new NoSuchEntityException( __("The option that was requested doesn't exist. Verify the entity and try again.") ); } $option->setData(array_merge($existingOption->getData(), $option->getData())); - $this->updateOptionSelection($bundleProduct, $option); + $this->updateOptionSelection($bundleProduct, $option, $existingOption); } try { @@ -183,15 +193,21 @@ public function save(ProductInterface $bundleProduct, OptionInterface $option) * * @param ProductInterface $product * @param OptionInterface $option + * @param OptionInterface|null $existingOption * @return void + * @throws CouldNotSaveException + * @throws InputException + * @throws NoSuchEntityException */ - private function updateOptionSelection(ProductInterface $product, OptionInterface $option) - { - $optionId = $option->getOptionId(); - $existingLinks = $this->linkManagement->getChildren($product->getSku(), $optionId); + private function updateOptionSelection( + ProductInterface $product, + OptionInterface $option, + ?OptionInterface $existingOption = null + ):void { $linksToAdd = []; $linksToUpdate = []; $linksToDelete = []; + if (is_array($option->getProductLinks())) { $productLinks = $option->getProductLinks(); foreach ($productLinks as $productLink) { @@ -201,13 +217,16 @@ private function updateOptionSelection(ProductInterface $product, OptionInterfac $linksToUpdate[] = $productLink; } } - /** @var LinkInterface[] $linksToDelete */ - $linksToDelete = $this->compareLinks($existingLinks, $linksToUpdate); - $linksToUpdate = $this->verifyLinksToUpdate($existingLinks, $linksToUpdate); + if (!empty($existingOption) && !empty($existingOption->getProductLinks())) { + $linksToDelete = $this->compareLinks($existingOption->getProductLinks(), $linksToUpdate); + $linksToUpdate = $this->verifyLinksToUpdate($existingOption->getProductLinks(), $linksToUpdate); + } } + foreach ($linksToUpdate as $linkedProduct) { $this->linkManagement->saveChild($product->getSku(), $linkedProduct); } + foreach ($linksToDelete as $linkedProduct) { $this->linkManagement->removeChild( $product->getSku(), @@ -215,6 +234,7 @@ private function updateOptionSelection(ProductInterface $product, OptionInterfac $linkedProduct->getSku() ); } + $this->addChildren->addChildren($product, (int)$option->getOptionId(), $linksToAdd); } @@ -300,4 +320,41 @@ private function compareLinks(array $firstArray, array $secondArray) return $result; } + + /** + * Retrieve option from list. + * + * @param Collection $optionCollection + * @param OptionInterface $option + * @param array $existingBundleProductOptions + * @return OptionInterface + */ + private function retrieveExistingOption( + Collection $optionCollection, + OptionInterface $option, + array $existingBundleProductOptions + ): OptionInterface { + $existingOption = $optionCollection->getItemById($option->getOptionId()); + + $incomingOption = current( + array_filter($existingBundleProductOptions, function ($obj) use ($option) { + return $obj->getData()['option_id'] == $option->getId(); + }) + ); + + if (!empty($incomingOption)) { + $existingOption->setData( + array_merge( + $existingOption->getData(), + $incomingOption->getData() + ) + ); + } + + if (empty($existingOption)) { + $existingOption = $optionCollection->getNewEmptyItem(); + } + + return $existingOption; + } } diff --git a/app/code/Magento/Bundle/Model/Product/SaveHandler.php b/app/code/Magento/Bundle/Model/Product/SaveHandler.php index 412783565486..62359a104ab7 100644 --- a/app/code/Magento/Bundle/Model/Product/SaveHandler.php +++ b/app/code/Magento/Bundle/Model/Product/SaveHandler.php @@ -21,6 +21,7 @@ /** * Bundle product save handler + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class SaveHandler implements ExtensionInterface { @@ -107,8 +108,7 @@ public function execute($entity, $arguments = []) if (!$entity->getCopyFromView()) { $this->processRemovedOptions($entity, $existingOptionsIds, $optionIds); - $newOptionsIds = array_diff($optionIds, $existingOptionsIds); - $this->saveOptions($entity, $bundleProductOptions, $newOptionsIds); + $this->saveOptions($entity, $bundleProductOptions, $existingBundleProductOptions); } else { //save only labels and not selections + product links $this->saveOptions($entity, $bundleProductOptions); @@ -150,20 +150,20 @@ protected function removeOptionLinks($entitySku, $option) /** * Perform save for all options entities. * - * @param object $entity + * @param ProductInterface $entity * @param array $options - * @param array $newOptionsIds - * + * @param array $existingBundleProductOptions * @return void + * @throws InputException + * @throws NoSuchEntityException + * @throws \Magento\Framework\Exception\CouldNotSaveException */ - private function saveOptions($entity, array $options, array $newOptionsIds = []): void - { - foreach ($options as $option) { - if (in_array($option->getOptionId(), $newOptionsIds)) { - $option->setOptionId(null); - } - } - $this->optionSave->saveBulk($entity, $options); + private function saveOptions( + ProductInterface $entity, + array $options, + array $existingBundleProductOptions = [] + ): void { + $this->optionSave->saveBulk($entity, $options, $existingBundleProductOptions); } /** diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Option/SaveActionTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Option/SaveActionTest.php index 1b8fd65455f3..932e70067827 100644 --- a/app/code/Magento/Bundle/Test/Unit/Model/Option/SaveActionTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Model/Option/SaveActionTest.php @@ -109,9 +109,6 @@ public function testSaveBulk() ->method('getMetadata') ->willReturn($metadata); - $this->linkManagement->expects($this->once()) - ->method('getChildren') - ->willReturn([]); $this->product->expects($this->once()) ->method('setIsRelationsChanged') ->with(true); diff --git a/lib/internal/Magento/Framework/Data/Collection.php b/lib/internal/Magento/Framework/Data/Collection.php index cbaa573aba8b..31e4995e6743 100644 --- a/lib/internal/Magento/Framework/Data/Collection.php +++ b/lib/internal/Magento/Framework/Data/Collection.php @@ -825,7 +825,7 @@ protected function _toOptionHash($valueField = 'id', $labelField = 'name') * Retrieve item by id * * @param string|int $idValue - * @return DataObject + * @return DataObject|null */ public function getItemById($idValue) { From 1351e67bec1f63a5db7de6714bef0abdf0ddea08 Mon Sep 17 00:00:00 2001 From: Dmytro Yushkin Date: Mon, 19 Feb 2024 16:38:27 -0600 Subject: [PATCH 15/38] ACP2E-2630: Issues saving advanced pricing on bundle products --- lib/internal/Magento/Framework/Data/Collection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Data/Collection.php b/lib/internal/Magento/Framework/Data/Collection.php index 31e4995e6743..cbaa573aba8b 100644 --- a/lib/internal/Magento/Framework/Data/Collection.php +++ b/lib/internal/Magento/Framework/Data/Collection.php @@ -825,7 +825,7 @@ protected function _toOptionHash($valueField = 'id', $labelField = 'name') * Retrieve item by id * * @param string|int $idValue - * @return DataObject|null + * @return DataObject */ public function getItemById($idValue) { From 4c7a62b29b8a3269421625801debef4a2c75c4ca Mon Sep 17 00:00:00 2001 From: Dmytro Yushkin Date: Mon, 19 Feb 2024 19:26:29 -0600 Subject: [PATCH 16/38] ACP2E-2630: Issues saving advanced pricing on bundle products --- app/code/Magento/Bundle/Model/Option/SaveAction.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Bundle/Model/Option/SaveAction.php b/app/code/Magento/Bundle/Model/Option/SaveAction.php index 7904a00f244d..2471f932a754 100644 --- a/app/code/Magento/Bundle/Model/Option/SaveAction.php +++ b/app/code/Magento/Bundle/Model/Option/SaveAction.php @@ -351,6 +351,7 @@ private function retrieveExistingOption( ); } + // @phpstan-ignore-next-line if (empty($existingOption)) { $existingOption = $optionCollection->getNewEmptyItem(); } From bc03a5dffb1e01ba21eca57e95efcb0f0534ac5e Mon Sep 17 00:00:00 2001 From: flowers Date: Tue, 20 Feb 2024 08:52:30 -0600 Subject: [PATCH 17/38] ACP2E-2734: Emails are failing to send --- app/code/Magento/Sales/Model/EmailSenderHandler.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Sales/Model/EmailSenderHandler.php b/app/code/Magento/Sales/Model/EmailSenderHandler.php index 748a12dff340..c59e3ddf0dc6 100644 --- a/app/code/Magento/Sales/Model/EmailSenderHandler.php +++ b/app/code/Magento/Sales/Model/EmailSenderHandler.php @@ -23,6 +23,11 @@ */ class EmailSenderHandler { + /** + * Configuration path for defining asynchronous email sending attempts + */ + public const XML_PATH_ASYNC_SENDING_ATTEMPTS = 'sales_email/general/async_sending_attempts'; + /** * Email sender model. * @@ -131,7 +136,7 @@ public function sendEmails() /** @var \Magento\Store\Api\Data\StoreInterface[] $stores */ $stores = $this->getStores(clone $this->entityCollection); - $maxSendAttempts = $this->globalConfig->getValue('sales_email/general/async_sending_attempts'); + $maxSendAttempts = $this->globalConfig->getValue(self::XML_PATH_ASYNC_SENDING_ATTEMPTS); /** @var \Magento\Store\Model\Store $store */ foreach ($stores as $store) { From 7855197f098d28bbb569decbc75802d9b4c644d5 Mon Sep 17 00:00:00 2001 From: Olga Moyseyenko Date: Wed, 21 Feb 2024 15:41:43 -0600 Subject: [PATCH 18/38] ACP2E-2763: Table Rates Still Showing Even Though Free Shipping Is Applied --- .../Test/Mftf/Data/SalesRuleData.xml | 19 +++ ...hFreeShippingAfterApplyingCartRuleTest.xml | 132 ++++++++++++++++++ .../tests/_data/price_tablerates.csv | 5 + 3 files changed, 156 insertions(+) create mode 100644 app/code/Magento/Shipping/Test/Mftf/Test/StorefrontAssertShippingPricesPresentWithFreeShippingAfterApplyingCartRuleTest.xml create mode 100644 dev/tests/acceptance/tests/_data/price_tablerates.csv diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml index c378c58008a2..a7f2da506e8b 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml @@ -379,6 +379,25 @@ Free Shipping in conditions Free Shipping in conditions + + Cart Price Rule For Tree Shipping With Coupon + Description for Cart Price Rule + Yes + Main Website + NOT LOGGED IN + Specific Coupon + FreeShipping + Fixed amount discount + 50 + 0 + 0 + 0 + For shipment with matching items + false + Percent of product price discount + Free Shipping Rule + Free Shipping Rule + Cart Price Rule For Rule Condition Description for Cart Price Rule diff --git a/app/code/Magento/Shipping/Test/Mftf/Test/StorefrontAssertShippingPricesPresentWithFreeShippingAfterApplyingCartRuleTest.xml b/app/code/Magento/Shipping/Test/Mftf/Test/StorefrontAssertShippingPricesPresentWithFreeShippingAfterApplyingCartRuleTest.xml new file mode 100644 index 000000000000..d9443f79474a --- /dev/null +++ b/app/code/Magento/Shipping/Test/Mftf/Test/StorefrontAssertShippingPricesPresentWithFreeShippingAfterApplyingCartRuleTest.xml @@ -0,0 +1,132 @@ + + + + + + + + + + <description value="Table rate price should be displayed correctly on checkout after applied free shipment cart price rule"/> + <severity value="AVERAGE"/> + <testCaseId value="ACP2E-2763"/> + <useCaseId value="ACP2E-2763"/> + <group value="shipping"/> + <group value="SalesRule"/> + <group value="cloud"/> + </annotations> + <before> + <createData entity="ApiSimpleProductWithCustomPrice" stepKey="createProduct"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <!-- Enable free shipping method --> + <createData entity="FreeShippinMethodConfig" stepKey="enableFreeShippingMethod"/> + <createData entity="setFreeShippingSubtotal" stepKey="setFreeShippingSubtotal"/> + + <!-- Turn on and configure table rates shipping method --> + <actionGroup ref="AdminOpenShippingMethodsConfigPageActionGroup" stepKey="openShippingMethodConfigPage"/> + <actionGroup ref="AdminSwitchWebsiteActionGroup" stepKey="switchDefaultWebsite"> + <argument name="website" value="_defaultWebsite"/> + </actionGroup> + <actionGroup ref="AdminChangeTableRatesShippingMethodStatusActionGroup" stepKey="enableTableRatesShippingMethodForDefaultWebsite"> + <argument name="status" value="1"/> + </actionGroup> + <actionGroup ref="AdminImportFileTableRatesShippingMethodActionGroup" stepKey="importCSVFile"> + <argument name="file" value="price_tablerates.csv"/> + </actionGroup> + <actionGroup ref="AdminSaveConfigActionGroup" stepKey="saveConfig"/> + + <!-- Create cart price rule for free shipping --> + <actionGroup ref="AdminCartPriceRuleDeleteAllActionGroup" stepKey="deleteAllExistingCartPriceRules"/> + + <actionGroup ref="AdminOpenNewCartPriceRuleFormPageActionGroup" stepKey="createCartPriceRule"/> + <actionGroup ref="AdminCartPriceRuleFillMainInfoActionGroup" stepKey="fillCartPriceRuleMainInfo"> + <argument name="name" value="{{CartPriceRuleFreeShippingWithCouponAppliedOnly.name}}"/> + <argument name="description" value="{{CartPriceRuleFreeShippingWithCouponAppliedOnly.description}}"/> + </actionGroup> + <actionGroup ref="AdminCartPriceRuleFillCouponInfoActionGroup" stepKey="fillCartPriceRuleCouponInfo"/> + <actionGroup ref="AdminCreateCartPriceRuleActionsSectionDiscountFieldsActionGroup" stepKey="fillCartPriceRuleActionsSection"> + <argument name="rule" value="CartPriceRuleFreeShippingWithCouponAppliedOnly"/> + </actionGroup> + <actionGroup ref="AdminCreateCartPriceRuleActionsSectionFreeShippingActionGroup" stepKey="fillCartPriceRuleFreeShippingActionsSection"> + <argument name="freeShippingOption" value="{{CartPriceRuleFreeShippingWithCouponAppliedOnly.simple_free_shipping}}"/> + </actionGroup> + <actionGroup ref="AdminCartPriceRuleSaveActionGroup" stepKey="saveCartPriceRule"/> + </before> + <after> + + <!-- Delete product --> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + + <!-- Delete cart price rule for free shipping --> + <actionGroup ref="AdminCartPriceRuleDeleteAllActionGroup" stepKey="deleteAllCartPriceRules"/> + <actionGroup ref="AdminOpenShippingMethodsConfigPageActionGroup" stepKey="openShippingMethodConfigPage2"/> + <actionGroup ref="AdminSwitchWebsiteActionGroup" stepKey="switchDefaultWebsite2"> + <argument name="website" value="_defaultWebsite"/> + </actionGroup> + + <!-- Turn off table rates shipping method --> + <actionGroup ref="AdminChangeTableRatesShippingMethodStatusActionGroup" stepKey="disableTableRatesShippingMethodForDefaultWebsite"> + <argument name="status" value="0"/> + </actionGroup> + <actionGroup ref="AdminSaveConfigActionGroup" stepKey="saveConfig2"/> + + <!-- Disable free shipping method --> + <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShippingMethod"/> + <createData entity="setFreeShippingSubtotalToDefault" stepKey="setFreeShippingSubtotalToDefault"/> + + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + + <!-- Add product to cart and check shipping prices --> + <actionGroup ref="OpenStoreFrontProductPageActionGroup" stepKey="navigateToProductPage"> + <argument name="productUrlKey" value="$createProduct.custom_attributes[url_key]$"/> + </actionGroup> + + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="addProductToCart"> + <argument name="product" value="$createProduct$" /> + <argument name="productCount" value="1" /> + </actionGroup> + + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + + <actionGroup ref="GuestCheckoutFillNewShippingAddressActionGroup" stepKey="guestCheckoutFillingShippingSection"> + <argument name="customer" value="CustomerEntityOne" /> + <argument name="address" value="CustomerAddressSimple" /> + </actionGroup> + <see selector="{{CheckoutShippingMethodsSection.shippingRatePriceByName('Fixed')}}" userInput="$5.00" stepKey="assertFlatRatedMethodPrice"/> + <see selector="{{CheckoutShippingMethodsSection.shippingRatePriceByName('Table Rate')}}" userInput="$0.00" stepKey="assertTableRatedMethodPrice"/> + <dontSee selector="{{CheckoutShippingMethodsSection.shippingRatePriceByName('Free Shipping')}}" userInput="$0.00" stepKey="assertFreeShippingMethodNotAvailable"/> + <waitForElementClickable selector="{{CheckoutShippingMethodsSection.shippingMethodFlatRate}}" stepKey="waitForFlatRateShippingMethod"/> + + <!-- Apply cart price rule for free shipping --> + <click selector="{{CheckoutShippingMethodsSection.checkShippingMethodByName('Flat Rate')}}" stepKey="selectFlatRateShippingMethod"/> + <actionGroup ref="StorefrontCheckoutClickNextButtonActionGroup" stepKey="goToPaymentStep"/> + <actionGroup ref="StorefrontApplyDiscountCodeActionGroup" stepKey="applyCoupon"> + <argument name="discountCode" value="{{_defaultCoupon.code}}"/> + </actionGroup> + + <!-- Check shipping prices after applying cart price rule --> + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="amOnHomePageAfterCartRuleApplied"/> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart2"/> + <see selector="{{CheckoutShippingMethodsSection.shippingRatePriceByName('Fixed')}}" userInput="$5.00" stepKey="assertFlatRatedMethodPriceAfterCartRule"/> + <see selector="{{CheckoutShippingMethodsSection.shippingRatePriceByName('Table Rate')}}" userInput="$15.95" stepKey="assertTableRatedMethodPriceAfterCartRule"/> + <see selector="{{CheckoutShippingMethodsSection.shippingRatePriceByName('Free Shipping')}}" userInput="$0.00" stepKey="assertFreeShippingMethodAvailable"/> + </test> +</tests> diff --git a/dev/tests/acceptance/tests/_data/price_tablerates.csv b/dev/tests/acceptance/tests/_data/price_tablerates.csv new file mode 100644 index 000000000000..51977f498761 --- /dev/null +++ b/dev/tests/acceptance/tests/_data/price_tablerates.csv @@ -0,0 +1,5 @@ +Country,Region/State,Zip/Postal Code,Order Subtotal (and above),Shipping Price +USA,*,*,0,15.9500 +USA,*,*,100.0000,0.0000 +USA,AK,*,0.0000,0.0000 +USA,HI,*,0.0000,0.0000 \ No newline at end of file From eef550166260e957dcc95581e0a66023f0e0126a Mon Sep 17 00:00:00 2001 From: arnsaha <arnsaha@adobe.com> Date: Thu, 22 Feb 2024 12:31:35 -0600 Subject: [PATCH 19/38] ACP2E-2799: [Cloud] Wrong bundle product price when used with tier prices - With test --- .../Pricing/Price/DiscountCalculator.php | 16 ++++- .../Pricing/Price/DiscountCalculatorTest.php | 68 +++++++++++++++---- 2 files changed, 66 insertions(+), 18 deletions(-) diff --git a/app/code/Magento/Bundle/Pricing/Price/DiscountCalculator.php b/app/code/Magento/Bundle/Pricing/Price/DiscountCalculator.php index 6990149efc37..253e7e06fec4 100644 --- a/app/code/Magento/Bundle/Pricing/Price/DiscountCalculator.php +++ b/app/code/Magento/Bundle/Pricing/Price/DiscountCalculator.php @@ -7,12 +7,21 @@ namespace Magento\Bundle\Pricing\Price; use Magento\Catalog\Model\Product; +use Magento\Framework\Pricing\PriceCurrencyInterface; /** - * Class DiscountCalculator + * Check the product available discount and apply the correct discount to the price */ class DiscountCalculator { + + /** + * @param PriceCurrencyInterface $priceCurrency + */ + public function __construct(private readonly PriceCurrencyInterface $priceCurrency) + { + } + /** * Apply percentage discount * @@ -20,7 +29,7 @@ class DiscountCalculator * @param float|null $value * @return float|null */ - public function calculateDiscount(Product $product, $value = null) + public function calculateDiscount(Product $product, float $value = null): ?float { if ($value === null) { $value = $product->getPriceInfo()->getPrice(FinalPrice::PRICE_CODE)->getValue(); @@ -32,6 +41,7 @@ public function calculateDiscount(Product $product, $value = null) $discount = min($price->getDiscountPercent(), $discount ?: $price->getDiscountPercent()); } } - return (null !== $discount) ? $discount/100 * $value : $value; + return (null !== $discount) ? + $this->priceCurrency->roundPrice($discount/100 * $value, 2) : $value; } } diff --git a/app/code/Magento/Bundle/Test/Unit/Pricing/Price/DiscountCalculatorTest.php b/app/code/Magento/Bundle/Test/Unit/Pricing/Price/DiscountCalculatorTest.php index 0962a931761e..010c6149b50f 100644 --- a/app/code/Magento/Bundle/Test/Unit/Pricing/Price/DiscountCalculatorTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Pricing/Price/DiscountCalculatorTest.php @@ -11,7 +11,9 @@ use Magento\Bundle\Pricing\Price\DiscountProviderInterface; use Magento\Catalog\Model\Product; use Magento\Catalog\Pricing\Price\FinalPrice; +use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\Framework\Pricing\PriceInfo\Base; +use Magento\Framework\Pricing\Price\PriceInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -42,21 +44,31 @@ class DiscountCalculatorTest extends TestCase */ protected $priceMock; + /** + * @var PriceCurrencyInterface|MockObject + */ + private $priceCurrencyMock; + /** * Test setUp */ protected function setUp(): void { $this->productMock = $this->createMock(Product::class); - $this->priceInfoMock = $this->createPartialMock( - Base::class, - ['getPrice', 'getPrices'] - ); + $this->priceInfoMock = $this->getMockBuilder(Base::class) + ->disableOriginalConstructor() + ->onlyMethods(['getPrice', 'getPrices']) + ->addMethods(['getValue']) + ->getMock(); $this->finalPriceMock = $this->createMock(FinalPrice::class); $this->priceMock = $this->getMockForAbstractClass( DiscountProviderInterface::class ); - $this->calculator = new DiscountCalculator(); + $this->priceCurrencyMock = $this->getMockBuilder(PriceCurrencyInterface::class) + ->disableOriginalConstructor() + ->addMethods(['roundPrice']) + ->getMockForAbstractClass(); + $this->calculator = new DiscountCalculator($this->priceCurrencyMock); } /** @@ -98,26 +110,52 @@ public function testCalculateDiscountWithDefaultAmount() $this->getPriceMock(40), ] ); + $this->priceCurrencyMock->expects($this->once()) + ->method('roundPrice') + ->willReturn(20); $this->assertEquals(20, $this->calculator->calculateDiscount($this->productMock)); } /** * test method calculateDiscount with custom price amount + * + * @dataProvider providerForWithDifferentAmount */ - public function testCalculateDiscountWithCustomAmount() + public function testCalculateDiscountWithCustomAmount(mixed $discount, mixed $value, float $expectedResult) { - $this->productMock->expects($this->once()) + $this->productMock->expects($this->any()) ->method('getPriceInfo') ->willReturn($this->priceInfoMock); - $this->priceInfoMock->expects($this->once()) + $this->priceInfoMock->expects($this->any()) ->method('getPrices') - ->willReturn( - [ - $this->getPriceMock(30), - $this->getPriceMock(20), - $this->getPriceMock(40), - ] + ->willReturn([$this->getPriceMock($discount)]); + if ($value === null) { + $abstractPriceMock = $this->getMockForAbstractClass( + PriceInterface::class ); - $this->assertEquals(10, $this->calculator->calculateDiscount($this->productMock, 50)); + $this->priceInfoMock->expects($this->any()) + ->method('getPrice') + ->willReturn($abstractPriceMock); + $abstractPriceMock->expects($this->any()) + ->method('getValue') + ->willReturn($expectedResult); + } + $this->priceCurrencyMock->expects($this->any()) + ->method('roundPrice') + ->willReturn($expectedResult); + $this->assertEquals($expectedResult, $this->calculator->calculateDiscount($this->productMock, $value)); + } + + /** + * @return array + */ + public function providerForWithDifferentAmount() + { + return [ + 'test case 1 with discount amount' => [20, 50, 10], + 'test case 2 for null discount amount' => [null, 30, 30], + 'test case 3 with discount amount' => [99, 5.5, 5.45], + 'test case 4 with null value' => [50, null, 50] + ]; } } From 1a2560f6011ceec8b0e424a44cc9b82713e0e499 Mon Sep 17 00:00:00 2001 From: Olga Moyseyenko <moyseyen@adobe.com> Date: Thu, 22 Feb 2024 16:09:36 -0600 Subject: [PATCH 20/38] ACP2E-2763: Table Rates Still Showing Even Though Free Shipping Is Applied This value was updated as now the whole subtotal is taken into account for Table Rate tier, not only products excluded from the rule --- .../Magento/Quote/Model/ShippingMethodManagementTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php index 8c1904acc39e..988ca6b8a2ed 100644 --- a/dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php @@ -168,12 +168,12 @@ public function testTableRateWithCartRuleForFreeShipping() $rate = reset($result); $expectedResult = [ 'method_code' => 'bestway', - 'amount' => 10 + 'amount' => 5 ]; $this->assertEquals($expectedResult['method_code'], $rate->getMethodCode()); $this->assertEquals($expectedResult['amount'], $rate->getAmount()); } - + /** * @magentoDataFixture Magento/SalesRule/_files/cart_rule_100_percent_off.php * @magentoDataFixture Magento/Sales/_files/quote_with_customer.php From cec419fdaa3532f4852af518a564155f3beaac44 Mon Sep 17 00:00:00 2001 From: Olga Moyseyenko <moyseyen@adobe.com> Date: Thu, 22 Feb 2024 18:19:56 -0600 Subject: [PATCH 21/38] ACP2E-2763: Table Rates Still Showing Even Though Free Shipping Is Applied --- ...atesShippingMethodConditionActionGroup.xml | 31 +++++++++++++++++++ ...hFreeShippingAfterApplyingCartRuleTest.xml | 6 +++- .../tests/_data/price_tablerates.csv | 2 +- 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 app/code/Magento/Shipping/Test/Mftf/ActionGroup/AdminChangeTableRatesShippingMethodConditionActionGroup.xml diff --git a/app/code/Magento/Shipping/Test/Mftf/ActionGroup/AdminChangeTableRatesShippingMethodConditionActionGroup.xml b/app/code/Magento/Shipping/Test/Mftf/ActionGroup/AdminChangeTableRatesShippingMethodConditionActionGroup.xml new file mode 100644 index 000000000000..95b8f75998c8 --- /dev/null +++ b/app/code/Magento/Shipping/Test/Mftf/ActionGroup/AdminChangeTableRatesShippingMethodConditionActionGroup.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /************************************************************************ + * + * Copyright 2024 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + * ************************************************************************ + */ + --> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Enable/Disable Table Rates shipping method --> + <actionGroup name="AdminChangeTableRatesShippingMethodConditionActionGroup"> + <arguments> + <argument name="condition" type="string" defaultValue="{{TableRateShippingMethodConfig.package_value_with_discount}}"/> + </arguments> + <conditionalClick selector="{{AdminShippingMethodTableRatesSection.carriersTableRateTab}}" dependentSelector="{{AdminShippingMethodTableRatesSection.carriersTableRateActive}}" visible="false" stepKey="expandTab"/> + <uncheckOption selector="{{AdminShippingMethodTableRatesSection.carriersTableRateConditionName}}" stepKey="disableUseDefaultCondition"/> + <selectOption selector="{{AdminShippingMethodTableRatesSection.condition}}" userInput="{{condition}}" stepKey="setCondition"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Shipping/Test/Mftf/Test/StorefrontAssertShippingPricesPresentWithFreeShippingAfterApplyingCartRuleTest.xml b/app/code/Magento/Shipping/Test/Mftf/Test/StorefrontAssertShippingPricesPresentWithFreeShippingAfterApplyingCartRuleTest.xml index d9443f79474a..da57675ed6a4 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Test/StorefrontAssertShippingPricesPresentWithFreeShippingAfterApplyingCartRuleTest.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Test/StorefrontAssertShippingPricesPresentWithFreeShippingAfterApplyingCartRuleTest.xml @@ -26,7 +26,7 @@ <title value="Assert that table rate shipping price will be correct after cart price rule for free shipping applied"/> <description value="Table rate price should be displayed correctly on checkout after applied free shipment cart price rule"/> <severity value="AVERAGE"/> - <testCaseId value="ACP2E-2763"/> + <testCaseId value="AC-11402"/> <useCaseId value="ACP2E-2763"/> <group value="shipping"/> <group value="SalesRule"/> @@ -47,6 +47,9 @@ <actionGroup ref="AdminChangeTableRatesShippingMethodStatusActionGroup" stepKey="enableTableRatesShippingMethodForDefaultWebsite"> <argument name="status" value="1"/> </actionGroup> + <actionGroup ref="AdminChangeTableRatesShippingMethodConditionActionGroup" stepKey="selectPriceVsDestinationCondition"> + <argument name="condition" value="{{TableRateShippingMethodConfig.package_value_with_discount}}"/> + </actionGroup> <actionGroup ref="AdminImportFileTableRatesShippingMethodActionGroup" stepKey="importCSVFile"> <argument name="file" value="price_tablerates.csv"/> </actionGroup> @@ -85,6 +88,7 @@ <actionGroup ref="AdminChangeTableRatesShippingMethodStatusActionGroup" stepKey="disableTableRatesShippingMethodForDefaultWebsite"> <argument name="status" value="0"/> </actionGroup> + <checkOption selector="{{AdminShippingMethodTableRatesSection.carriersTableRateConditionName}}" stepKey="disableUseDefaultCondition"/> <actionGroup ref="AdminSaveConfigActionGroup" stepKey="saveConfig2"/> <!-- Disable free shipping method --> diff --git a/dev/tests/acceptance/tests/_data/price_tablerates.csv b/dev/tests/acceptance/tests/_data/price_tablerates.csv index 51977f498761..509d544585c6 100644 --- a/dev/tests/acceptance/tests/_data/price_tablerates.csv +++ b/dev/tests/acceptance/tests/_data/price_tablerates.csv @@ -2,4 +2,4 @@ Country,Region/State,Zip/Postal Code,Order Subtotal (and above),Shipping Price USA,*,*,0,15.9500 USA,*,*,100.0000,0.0000 USA,AK,*,0.0000,0.0000 -USA,HI,*,0.0000,0.0000 \ No newline at end of file +USA,HI,*,0.0000,0.0000 From 0a772398ff12a7881a2645ea858fbaf708e6e0cd Mon Sep 17 00:00:00 2001 From: Olga Moyseyenko <moyseyen@adobe.com> Date: Thu, 22 Feb 2024 22:08:50 -0600 Subject: [PATCH 22/38] ACP2E-2763: Table Rates Still Showing Even Though Free Shipping Is Applied --- ...WithFreeShippingAfterApplyingCartRuleTest.xml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/Shipping/Test/Mftf/Test/StorefrontAssertShippingPricesPresentWithFreeShippingAfterApplyingCartRuleTest.xml b/app/code/Magento/Shipping/Test/Mftf/Test/StorefrontAssertShippingPricesPresentWithFreeShippingAfterApplyingCartRuleTest.xml index da57675ed6a4..39f0b0db669c 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Test/StorefrontAssertShippingPricesPresentWithFreeShippingAfterApplyingCartRuleTest.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Test/StorefrontAssertShippingPricesPresentWithFreeShippingAfterApplyingCartRuleTest.xml @@ -71,31 +71,33 @@ <argument name="freeShippingOption" value="{{CartPriceRuleFreeShippingWithCouponAppliedOnly.simple_free_shipping}}"/> </actionGroup> <actionGroup ref="AdminCartPriceRuleSaveActionGroup" stepKey="saveCartPriceRule"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutAsAdmin"/> </before> <after> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin2"/> <!-- Delete product --> <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> <!-- Delete cart price rule for free shipping --> <actionGroup ref="AdminCartPriceRuleDeleteAllActionGroup" stepKey="deleteAllCartPriceRules"/> + + <!-- Disable free shipping method --> <actionGroup ref="AdminOpenShippingMethodsConfigPageActionGroup" stepKey="openShippingMethodConfigPage2"/> + <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShippingMethod"/> + <createData entity="setFreeShippingSubtotalToDefault" stepKey="setFreeShippingSubtotalToDefault"/> + + <!-- Turn off table rates shipping method --> <actionGroup ref="AdminSwitchWebsiteActionGroup" stepKey="switchDefaultWebsite2"> <argument name="website" value="_defaultWebsite"/> </actionGroup> - - <!-- Turn off table rates shipping method --> <actionGroup ref="AdminChangeTableRatesShippingMethodStatusActionGroup" stepKey="disableTableRatesShippingMethodForDefaultWebsite"> <argument name="status" value="0"/> </actionGroup> <checkOption selector="{{AdminShippingMethodTableRatesSection.carriersTableRateConditionName}}" stepKey="disableUseDefaultCondition"/> <actionGroup ref="AdminSaveConfigActionGroup" stepKey="saveConfig2"/> - <!-- Disable free shipping method --> - <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShippingMethod"/> - <createData entity="setFreeShippingSubtotalToDefault" stepKey="setFreeShippingSubtotalToDefault"/> - - <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout2"/> </after> <!-- Add product to cart and check shipping prices --> From 62512838806e427bc5514926ee8841b7b2690fe5 Mon Sep 17 00:00:00 2001 From: arnsaha <arnsaha@adobe.com> Date: Fri, 23 Feb 2024 08:33:07 -0600 Subject: [PATCH 23/38] ACP2E-2799: [Cloud] Wrong bundle product price when used with tier prices - With test --- app/code/Magento/Bundle/Pricing/Price/DiscountCalculator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Bundle/Pricing/Price/DiscountCalculator.php b/app/code/Magento/Bundle/Pricing/Price/DiscountCalculator.php index 253e7e06fec4..1cef9f71eff0 100644 --- a/app/code/Magento/Bundle/Pricing/Price/DiscountCalculator.php +++ b/app/code/Magento/Bundle/Pricing/Price/DiscountCalculator.php @@ -29,7 +29,7 @@ public function __construct(private readonly PriceCurrencyInterface $priceCurren * @param float|null $value * @return float|null */ - public function calculateDiscount(Product $product, float $value = null): ?float + public function calculateDiscount(Product $product, float $value = null) { if ($value === null) { $value = $product->getPriceInfo()->getPrice(FinalPrice::PRICE_CODE)->getValue(); From 124997d7365962cffbafecf6192197b13981edca Mon Sep 17 00:00:00 2001 From: arnsaha <arnsaha@adobe.com> Date: Fri, 23 Feb 2024 08:34:52 -0600 Subject: [PATCH 24/38] ACP2E-2799: [Cloud] Wrong bundle product price when used with tier prices - With test --- app/code/Magento/Bundle/Pricing/Price/DiscountCalculator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Bundle/Pricing/Price/DiscountCalculator.php b/app/code/Magento/Bundle/Pricing/Price/DiscountCalculator.php index 1cef9f71eff0..3dcce3db04a8 100644 --- a/app/code/Magento/Bundle/Pricing/Price/DiscountCalculator.php +++ b/app/code/Magento/Bundle/Pricing/Price/DiscountCalculator.php @@ -29,7 +29,7 @@ public function __construct(private readonly PriceCurrencyInterface $priceCurren * @param float|null $value * @return float|null */ - public function calculateDiscount(Product $product, float $value = null) + public function calculateDiscount(Product $product, $value = null) { if ($value === null) { $value = $product->getPriceInfo()->getPrice(FinalPrice::PRICE_CODE)->getValue(); From 9e667db4ac233c9d39861f925f68f08eea4fd9f8 Mon Sep 17 00:00:00 2001 From: Anna Bukatar <abukatar@magento.com> Date: Fri, 23 Feb 2024 14:06:33 -0800 Subject: [PATCH 25/38] ACP2E-2841: Payflow creates new transaction each time we click on fetch button on the view transaction screen --- .../Paypal/Model/Payflow/Transparent.php | 14 ---------- .../Unit/Model/Payflow/TransparentTest.php | 28 ++++++++++++++++++- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/Paypal/Model/Payflow/Transparent.php b/app/code/Magento/Paypal/Model/Payflow/Transparent.php index f9233c15a4a7..ff715c271509 100644 --- a/app/code/Magento/Paypal/Model/Payflow/Transparent.php +++ b/app/code/Magento/Paypal/Model/Payflow/Transparent.php @@ -389,20 +389,6 @@ public function denyPayment(InfoInterface $payment) return true; } - /** - * @inheritDoc - */ - public function fetchTransactionInfo(InfoInterface $payment, $transactionId) - { - $result = parent::fetchTransactionInfo($payment, $transactionId); - $this->_canFetchTransactionInfo = false; - if ($payment->getIsTransactionApproved()) { - $this->acceptPayment($payment); - } - - return $result; - } - /** * Marks payment as fraudulent. * diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php index 0e327e5d1854..c3916172aa37 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php @@ -235,7 +235,7 @@ private function getPaymentConfigInterfaceFactory() ->disableOriginalConstructor() ->getMock(); $this->paymentConfig = $this->getMockBuilder(PaymentConfigInterface::class) - ->setMethods(['setStoreId', 'setMethodInstance', 'setMethod', 'getBuildNotationCode']) + ->addMethods(['setStoreId', 'setMethodInstance', 'setMethod', 'getBuildNotationCode', 'getPaymentAction']) ->getMockForAbstractClass(); $paymentConfigInterfaceFactory->method('create')->willReturn($this->paymentConfig); @@ -345,6 +345,23 @@ private function initPayment() { $this->payment = $this->getMockBuilder(Payment::class) ->disableOriginalConstructor() + ->addMethods(['getIsTransactionApproved']) + ->onlyMethods( + [ + 'setTransactionId', + 'setIsTransactionClosed', + 'getCcExpYear', + 'getCcExpMonth', + 'getExtensionAttributes', + 'getOrder', + 'authorize', + 'canFetchTransactionInfo', + 'getParentTransactionId', + 'setParentTransactionId', + 'setAdditionalInformation', + 'getAdditionalInformation' + ] + ) ->getMock(); $this->order = $this->getMockBuilder(Order::class) ->disableOriginalConstructor() @@ -360,7 +377,16 @@ private function initPayment() $this->payment->method('getCcExpYear')->willReturn('2019'); $this->payment->method('getCcExpMonth')->willReturn('05'); $this->payment->method('getExtensionAttributes')->willReturn($this->paymentExtensionAttributes); + $this->payment->method('getIsTransactionApproved')->willReturn(true); return $this->payment; } + + public function testFetchTransactionInfo() + { + $this->payment->method('canFetchTransactionInfo')->willReturn(false); + $this->paymentConfig->method('getPaymentAction')->willReturn('authorize'); + $this->payment->expects($this->never())->method('authorize'); + $this->subject->fetchTransactionInfo($this->payment, '123'); + } } From 296e9d14aaa9a5cdcbe2fb364f651999ad9de142 Mon Sep 17 00:00:00 2001 From: Anna Bukatar <abukatar@magento.com> Date: Fri, 23 Feb 2024 15:55:19 -0800 Subject: [PATCH 26/38] ACP2E-2841: Payflow creates new transaction each time we click on fetch button on the view transaction screen --- .../Test/Unit/Model/Payflow/TransparentTest.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php index c3916172aa37..9ee08135aad8 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php @@ -125,15 +125,21 @@ public function testCaptureCorrectId(string $parentTransactionId) $gatewayToken = 'gateway_token'; $this->payment->expects($this->once())->method('getParentTransactionId')->willReturn($parentTransactionId); $this->payment->expects($this->exactly($setParentTransactionIdCalls))->method('setParentTransactionId'); - $this->payment->expects($this->exactly($setAdditionalInformationCalls))->method('setAdditionalInformation')->with(Payflowpro::PNREF, $gatewayToken); + $this->payment->expects($this->exactly($setAdditionalInformationCalls)) + ->method('setAdditionalInformation') + ->with(Payflowpro::PNREF, $gatewayToken); $this->payment->expects($this->exactly(4))->method('getAdditionalInformation')->withConsecutive( ['result_code'], [Payflowpro::PNREF], [Payflowpro::PNREF], [Payflowpro::PNREF], )->willReturn(0, '', Payflowpro::PNREF, Payflowpro::PNREF); - $this->paymentExtensionAttributes->expects($this->once())->method('getVaultPaymentToken')->willReturn($this->paymentToken); - $this->paymentToken->expects($this->exactly($getGatewayTokenCalls))->method('getGatewayToken')->willReturn($gatewayToken); + $this->paymentExtensionAttributes->expects($this->once()) + ->method('getVaultPaymentToken') + ->willReturn($this->paymentToken); + $this->paymentToken->expects($this->exactly($getGatewayTokenCalls)) + ->method('getGatewayToken') + ->willReturn($gatewayToken); $this->subject->capture($this->payment, 100); } From 9fad9e539e3877803b64436c7ecde5508742ae27 Mon Sep 17 00:00:00 2001 From: Anna Bukatar <abukatar@magento.com> Date: Tue, 20 Feb 2024 19:01:57 -0800 Subject: [PATCH 27/38] ACP2E-2811: Reindexing Catalog Rule Product Indexer throws SQLSTATE[HY000]: General error: 2006 MySQL server has gone away --- .../Model/Indexer/ReindexRuleProduct.php | 2 +- .../Model/Indexer/ReindexRuleProductTest.php | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php index 320eb8a38ba6..295484ca25f3 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php @@ -146,7 +146,7 @@ public function execute(Rule $rule, $batchCount, $useAdditionalTable = false) 'sort_order' => $sortOrder, ]; - if (count($rows) === $batchCount) { + if (count($rows) === (int) $batchCount) { $connection->insertMultiple($indexTable, $rows); $rows = []; } diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php index a2f4ccfad3b5..eb1c9f330fa7 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php @@ -186,6 +186,28 @@ public function testExecute(): void self::assertTrue($this->model->execute($this->ruleMock, 2, true)); } + public function testExecuteWithCustomBatchSize() + { + $websiteId = 3; + $productIds = [ + 4 => [$websiteId => 1], + 5 => [$websiteId => 1], + 6 => [$websiteId => 1] + ]; + + $this->prepareResourceMock(); + $this->prepareRuleMock([3], $productIds, [10]); + + $this->localeDateMock->method('getConfigTimezone') + ->willReturnMap([ + [ScopeInterface::SCOPE_WEBSITE, self::ADMIN_WEBSITE_ID, $this->adminTimeZone], + [ScopeInterface::SCOPE_WEBSITE, $websiteId, $this->websiteTz] + ]); + + $this->connectionMock->expects($this->exactly(2))->method('insertMultiple'); + self::assertTrue($this->model->execute($this->ruleMock, '2', true)); + } + /** * @param array $websitesIds * @param array $productIds From f463183860beea44f62e3c7e27cf9d92973af83d Mon Sep 17 00:00:00 2001 From: Sergio Vera <sergio.vera@gmail.com> Date: Wed, 28 Feb 2024 10:38:17 +0100 Subject: [PATCH 28/38] LYNX-361: Implemented GraphQL email confirmation --- .../Model/Resolver/ConfirmEmail.php | 78 +++++++++ .../CustomerGraphQl/etc/schema.graphqls | 6 + .../GraphQl/Customer/ConfirmEmailTest.php | 158 ++++++++++++++++++ 3 files changed, 242 insertions(+) create mode 100644 app/code/Magento/CustomerGraphQl/Model/Resolver/ConfirmEmail.php create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ConfirmEmailTest.php diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/ConfirmEmail.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/ConfirmEmail.php new file mode 100644 index 000000000000..635a9576d0e5 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/ConfirmEmail.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright 2024 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained from + * Adobe. + */ +declare(strict_types=1); + +namespace Magento\CustomerGraphQl\Model\Resolver; + +use Magento\Customer\Api\AccountManagementInterface; +use Magento\CustomerGraphQl\Model\Customer\ExtractCustomerData; +use Magento\Framework\Exception\State\InputMismatchException; +use Magento\Framework\Exception\State\InvalidTransitionException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Query\Resolver\Value; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Framework\Validator\EmailAddress as EmailValidator; + +/** + * Customer email confirmation, used for GraphQL request processing + */ +class ConfirmEmail implements ResolverInterface +{ + /** + * @param AccountManagementInterface $accountManagement + * @param EmailValidator $emailValidator + * @param ExtractCustomerData $extractCustomerData + */ + public function __construct( + private readonly AccountManagementInterface $accountManagement, + private readonly EmailValidator $emailValidator, + private readonly ExtractCustomerData $extractCustomerData + ) { + } + + /** + * Confirm customer email mutation + * + * @param \Magento\Framework\GraphQl\Config\Element\Field $field + * @param \Magento\Framework\GraphQl\Query\Resolver\ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * @return array|Value + * @throws \Exception + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!$this->emailValidator->isValid($args['input']['email'])) { + throw new GraphQlInputException(__('Email is invalid')); + } + + try { + $customer = $this->accountManagement->activate($args['input']['email'], $args['input']['confirmation_key']); + } catch (InvalidTransitionException | InputMismatchException $e) { + throw new GraphQlInputException(__($e->getMessage())); + } + + return ['customer' => $this->extractCustomerData->execute($customer)]; + } +} diff --git a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls index fd148e12e70a..fb7b3d4dd6c2 100644 --- a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls @@ -29,6 +29,12 @@ type Mutation { requestPasswordResetEmail(email: String! @doc(description: "The customer's email address.")): Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\RequestPasswordResetEmail") @doc(description: "Request an email with a reset password token for the registered customer identified by the specified email.") resetPassword(email: String! @doc(description: "The customer's email address."), resetPasswordToken: String! @doc(description: "A runtime token generated by the `requestPasswordResetEmail` mutation."), newPassword: String! @doc(description: "The customer's new password.")): Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ResetPassword") @doc(description: "Reset a customer's password using the reset password token that the customer received in an email after requesting it using `requestPasswordResetEmail`.") updateCustomerEmail(email: String! @doc(description: "The customer's email address."), password: String! @doc(description: "The customer's password.")): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomerEmail") @doc(description: "Change the email address for the logged-in customer.") + confirmEmail(input: ConfirmEmailInput! @doc(description: "An input object to identify the customer to confirm the email.")): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ConfirmEmail") @doc(description: "Confirms the email address for a customer.") +} + +input ConfirmEmailInput @doc(description: "Contains details about a customer email address to confirm.") { + email: String! @doc(description: "The email address to be confirmed.") + confirmation_key: String! @doc(description: "The key to confirm the email address.") } input CustomerAddressInput @doc(description: "Contains details about a billing or shipping address."){ diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ConfirmEmailTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ConfirmEmailTest.php new file mode 100644 index 000000000000..49fb3bc933b2 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ConfirmEmailTest.php @@ -0,0 +1,158 @@ +<?php +/** + * Copyright 2024 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained from + * Adobe. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Customer; + +use Magento\Customer\Test\Fixture\Customer as CustomerFixture; +use Magento\Framework\Exception\AuthenticationException; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\Fixture\DataFixture; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Tests for confirm customer email + */ +#[ + DataFixture( + CustomerFixture::class, + [ + 'email' => 'customer@example.com', + 'confirmation' => 'abcde', + ], + 'customer' + ) +] +class ConfirmEmailTest extends GraphQlAbstract +{ + private const QUERY = <<<QUERY +mutation { + confirmEmail(input: { + email: "%s" + confirmation_key: "%s" + }) { + customer { + email + } + } +} +QUERY; + + /** + * @var string + */ + private const PASSWORD = 'password'; + + /** + * @return void + * @throws AuthenticationException + */ + public function testConfirmEmail() + { + $response = $this->graphQlMutation( + sprintf( + self::QUERY, + 'customer@example.com', + 'abcde', + ), + [], + '', + $this->getCustomerAuthHeaders('customer@example.com', self::PASSWORD) + ); + + $this->assertEquals( + [ + 'confirmEmail' => [ + 'customer' => [ + 'email' => 'customer@example.com' + ] + ] + ], + $response + ); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('The account is already active.'); + + $this->graphQlMutation( + sprintf( + self::QUERY, + 'customer@example.com', + 'abcde', + ), + [], + '', + $this->getCustomerAuthHeaders('customer@example.com', self::PASSWORD) + ); + } + + /** + * @return void + * @throws AuthenticationException + */ + public function testConfirmEmailWrongEmail() + { + $this->expectException(\Exception::class); + + $this->graphQlMutation( + sprintf( + self::QUERY, + 'bad-email', + 'abcde', + ), + [], + '', + $this->getCustomerAuthHeaders('customer@example.com', self::PASSWORD) + ); + } + + /** + * @return void + * @throws AuthenticationException + */ + public function testConfirmEmailWrongConfirmation() + { + $this->expectException(\Exception::class); + + $this->graphQlMutation( + sprintf( + self::QUERY, + 'customer@example.com', + 'wrong-confirmation', + ), + [], + '', + $this->getCustomerAuthHeaders('customer@example.com', self::PASSWORD) + ); + } + + /** + * @param string $email + * @param string $password + * @return array + * @throws AuthenticationException + */ + private function getCustomerAuthHeaders(string $email, string $password): array + { + $customerToken = Bootstrap::getObjectManager()->get( + CustomerTokenServiceInterface::class + )->createCustomerAccessToken( + $email, + $password + ); + return ['Authorization' => 'Bearer ' . $customerToken]; + } +} From b4989c4067f91c52d5f5e18d44dfa925c8d27665 Mon Sep 17 00:00:00 2001 From: Rafal Janicki <rjanicki@adobe.com> Date: Mon, 4 Mar 2024 14:58:02 +0000 Subject: [PATCH 29/38] LYNX-364: Extend GraphQL StoreConfig for Customer Token Lifetime Configuration (#213) * LYNX-364: Extend GraphQL StoreConfig for Customer Token Lifetime Configuration * LYNX-364: WebAPI tests * LYNX-364: WebAPI tests fix * LYNX-364: WebAPI test for unauthorized customer; refactoring * LYNX-364: update comment * LYNX-364: update comment * LYNX-364Create new IntegrationGraphQl module * LYNX-364: Restore deleted file * LYNX-364: Remove unnecessary authorization check * LYNX-364: Fix static tests * LYNX-364: Fix Static Tests * LYNX-364: CR changes * LYNX-364: CR refactoring * LYNX-364: CR refactoring --- app/code/Magento/IntegrationGraphQl/README.md | 22 +++++ .../Magento/IntegrationGraphQl/composer.json | 22 +++++ .../IntegrationGraphQl/etc/graphql/di.xml | 27 ++++++ .../Magento/IntegrationGraphQl/etc/module.xml | 22 +++++ .../IntegrationGraphQl/etc/schema.graphqls | 18 ++++ .../IntegrationGraphQl/registration.php | 19 +++++ composer.json | 1 + ...ustomerAccessTokenLifetimeResolverTest.php | 85 +++++++++++++++++++ 8 files changed, 216 insertions(+) create mode 100644 app/code/Magento/IntegrationGraphQl/README.md create mode 100644 app/code/Magento/IntegrationGraphQl/composer.json create mode 100644 app/code/Magento/IntegrationGraphQl/etc/graphql/di.xml create mode 100644 app/code/Magento/IntegrationGraphQl/etc/module.xml create mode 100644 app/code/Magento/IntegrationGraphQl/etc/schema.graphqls create mode 100644 app/code/Magento/IntegrationGraphQl/registration.php create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/IntegrationGraphQl/CustomerAccessTokenLifetimeResolverTest.php diff --git a/app/code/Magento/IntegrationGraphQl/README.md b/app/code/Magento/IntegrationGraphQl/README.md new file mode 100644 index 000000000000..d6414fad7f70 --- /dev/null +++ b/app/code/Magento/IntegrationGraphQl/README.md @@ -0,0 +1,22 @@ +# Magento_IntegrationGraphQl module + +This module provides GraphQl resolvers for Integartion module. + +## Installation + +Before installing this module, note that the Magento_IntegrationGraphQl is dependent on the following modules: + +- `Magento_GraphQl` +- `Magento_Integration` + +For information about a module installation in Magento 2, see [Enable or disable modules](https://experienceleague.adobe.com/docs/commerce-operations/installation-guide/tutorials/manage-modules.html). + +## Extensibility + +Extension developers can interact with the Magento_IntegrationGraphQl module. For more information about the Magento extension mechanism, see [Magento plugins](https://developer.adobe.com/commerce/php/development/components/plugins/). + +[The Magento dependency injection mechanism](https://developer.adobe.com/commerce/php/development/components/dependency-injection/) enables you to override the functionality of the Magento_IntegrationGraphQl module. + +## Additional information + +You can get more information about [GraphQl In Magento 2](https://developer.adobe.com/commerce/webapi/graphql/). diff --git a/app/code/Magento/IntegrationGraphQl/composer.json b/app/code/Magento/IntegrationGraphQl/composer.json new file mode 100644 index 000000000000..942f629c5e76 --- /dev/null +++ b/app/code/Magento/IntegrationGraphQl/composer.json @@ -0,0 +1,22 @@ +{ + "name": "magento/module-integration-graph-ql", + "description": "N/A", + "type": "magento2-module", + "require": { + "php": "~8.1.0||~8.2.0||~8.3.0", + "magento/framework": "*", + "magento/module-integration": "*" + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\IntegrationGraphQl\\": "" + } + } +} diff --git a/app/code/Magento/IntegrationGraphQl/etc/graphql/di.xml b/app/code/Magento/IntegrationGraphQl/etc/graphql/di.xml new file mode 100644 index 000000000000..9e0541e944c4 --- /dev/null +++ b/app/code/Magento/IntegrationGraphQl/etc/graphql/di.xml @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<!-- +/************************************************************************ + * + * Copyright 2024 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + * ************************************************************************ + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\StoreGraphQl\Model\Resolver\Store\StoreConfigDataProvider"> + <arguments> + <argument name="extendedConfigData" xsi:type="array"> + <item name="customer_access_token_lifetime" xsi:type="string">oauth/access_token_lifetime/customer</item> + </argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/IntegrationGraphQl/etc/module.xml b/app/code/Magento/IntegrationGraphQl/etc/module.xml new file mode 100644 index 000000000000..ca4c956afc46 --- /dev/null +++ b/app/code/Magento/IntegrationGraphQl/etc/module.xml @@ -0,0 +1,22 @@ +<?xml version="1.0"?> +<!-- +/************************************************************************ + * + * Copyright 2024 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + * ************************************************************************ + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_IntegrationGraphQl" > + </module> +</config> diff --git a/app/code/Magento/IntegrationGraphQl/etc/schema.graphqls b/app/code/Magento/IntegrationGraphQl/etc/schema.graphqls new file mode 100644 index 000000000000..1241f54dca1d --- /dev/null +++ b/app/code/Magento/IntegrationGraphQl/etc/schema.graphqls @@ -0,0 +1,18 @@ +# ADOBE CONFIDENTIAL +# ___________________ +# +# Copyright 2024 Adobe +# All Rights Reserved. +# +# NOTICE: All information contained herein is, and remains +# the property of Adobe and its suppliers, if any. The intellectual +# and technical concepts contained herein are proprietary to Adobe +# and its suppliers and are protected by all applicable intellectual +# property laws, including trade secret and copyright laws. +# Dissemination of this information or reproduction of this material +# is strictly forbidden unless prior written permission is obtained +# from Adobe. + +type StoreConfig { + customer_access_token_lifetime: Float @doc(description: "Customer access token lifetime.") +} diff --git a/app/code/Magento/IntegrationGraphQl/registration.php b/app/code/Magento/IntegrationGraphQl/registration.php new file mode 100644 index 000000000000..bb35a050bf7c --- /dev/null +++ b/app/code/Magento/IntegrationGraphQl/registration.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright 2024 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained from + * Adobe. + */ +declare(strict_types=1); + +use Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_IntegrationGraphQl', __DIR__); diff --git a/composer.json b/composer.json index c792b32c1ef3..ccbc0fa17700 100644 --- a/composer.json +++ b/composer.json @@ -206,6 +206,7 @@ "magento/module-indexer": "*", "magento/module-instant-purchase": "*", "magento/module-integration": "*", + "magento/module-integration-graph-ql": "*", "magento/module-layered-navigation": "*", "magento/module-login-as-customer": "*", "magento/module-login-as-customer-admin-ui": "*", diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/IntegrationGraphQl/CustomerAccessTokenLifetimeResolverTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/IntegrationGraphQl/CustomerAccessTokenLifetimeResolverTest.php new file mode 100644 index 000000000000..10bc66fc4a4b --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/IntegrationGraphQl/CustomerAccessTokenLifetimeResolverTest.php @@ -0,0 +1,85 @@ +<?php +/** + * Copyright 2024 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained from + * Adobe. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\IntegrationGraphQl; + +use Magento\TestFramework\TestCase\GraphQlAbstract; +use Magento\TestFramework\Fixture\Config; + +/** + * Class for Store Config Customer Access Token Lifetime settings + */ +class CustomerAccessTokenLifetimeResolverTest extends GraphQlAbstract +{ + #[ + Config('oauth/access_token_lifetime/customer', '2') + ] + public function testGetCustomerAccessTokenLifetimeAsString() + { + $this->assertEquals( + $this->graphQlQuery($this->getQuery()), + [ + 'storeConfig' => [ + "customer_access_token_lifetime" => 2 + ] + ] + ); + } + + #[ + Config('oauth/access_token_lifetime/customer', 2.5) + ] + public function testGetCustomerAccessTokenLifetimeAsFloat() + { + $this->assertEquals( + $this->graphQlQuery($this->getQuery()), + [ + 'storeConfig' => [ + "customer_access_token_lifetime" => 2.5 + ] + ] + ); + } + + #[ + Config('oauth/access_token_lifetime/customer', null) + ] + public function testGetCustomerAccessTokenLifetimeNull() + { + $this->assertEquals( + $this->graphQlQuery($this->getQuery()), + [ + 'storeConfig' => [ + "customer_access_token_lifetime" => null + ] + ] + ); + } + + /** + * @return string + */ + private function getQuery(): string + { + return <<<QUERY + { + storeConfig { + customer_access_token_lifetime + } + } +QUERY; + } +} From 881c2861f209e78ae724f236bbe5df47a774f14c Mon Sep 17 00:00:00 2001 From: Sergii Ivashchenko <serg.ivashchenko@gmail.com> Date: Tue, 5 Mar 2024 13:15:48 +0000 Subject: [PATCH 30/38] LYNX-359: Added email field to CustomerOrder and fixed orderV2 error handling --- .../Magento/QuoteGraphQl/etc/schema.graphqls | 2 +- .../SalesGraphQl/Model/Formatter/Order.php | 17 +++-------------- .../Magento/SalesGraphQl/etc/schema.graphqls | 1 + .../GraphQl/Quote/Guest/PlaceOrderTest.php | 9 +++++++++ .../GraphQl/Sales/GuestOrderByTokenTest.php | 4 ++++ .../Magento/GraphQl/Sales/GuestOrderTest.php | 2 ++ 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index 9f44a24b1974..ace304dcb6ee 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -260,7 +260,7 @@ type ApplyCouponToCartOutput @doc(description: "Contains details about the cart type PlaceOrderOutput @doc(description: "Contains the results of the request to place an order.") { order: Order @deprecated(reason: "Use `orderV2` instead.") @doc(description: "The ID of the order.") - orderV2: CustomerOrder! @doc(description: "Full order information.") + orderV2: CustomerOrder @doc(description: "Full order information.") errors: [PlaceOrderError!]! @doc(description:"An array of place order errors.") } diff --git a/app/code/Magento/SalesGraphQl/Model/Formatter/Order.php b/app/code/Magento/SalesGraphQl/Model/Formatter/Order.php index ec41c15e0fc1..a1ad36d4dfbd 100644 --- a/app/code/Magento/SalesGraphQl/Model/Formatter/Order.php +++ b/app/code/Magento/SalesGraphQl/Model/Formatter/Order.php @@ -17,26 +17,14 @@ */ class Order { - /** - * @var OrderAddress - */ - private $orderAddress; - - /** - * @var OrderPayments - */ - private $orderPayments; - /** * @param OrderAddress $orderAddress * @param OrderPayments $orderPayments */ public function __construct( - OrderAddress $orderAddress, - OrderPayments $orderPayments + private readonly OrderAddress $orderAddress, + private readonly OrderPayments $orderPayments ) { - $this->orderAddress = $orderAddress; - $this->orderPayments = $orderPayments; } /** @@ -57,6 +45,7 @@ public function format(OrderInterface $orderModel): array 'order_date' => $orderModel->getCreatedAt(), 'order_number' => $orderModel->getIncrementId(), 'status' => $orderModel->getStatusLabel(), + 'email' => $orderModel->getCustomerEmail(), 'shipping_method' => $orderModel->getShippingDescription(), 'shipping_address' => $this->orderAddress->getOrderShippingAddress($orderModel), 'billing_address' => $this->orderAddress->getOrderBillingAddress($orderModel), diff --git a/app/code/Magento/SalesGraphQl/etc/schema.graphqls b/app/code/Magento/SalesGraphQl/etc/schema.graphqls index b1d1739694d5..1e2991d7fede 100644 --- a/app/code/Magento/SalesGraphQl/etc/schema.graphqls +++ b/app/code/Magento/SalesGraphQl/etc/schema.graphqls @@ -74,6 +74,7 @@ type CustomerOrder @doc(description: "Contains details about each of the custome grand_total: Float @deprecated(reason: "Use the `totals.grand_total` field instead.") token: String! @doc(description: "The token that can be used to retrieve the order using order query.") @resolver(class: "Magento\\SalesGraphQl\\Model\\Resolver\\Token") applied_coupons: [AppliedCoupon!]! @doc(description: "Coupons applied to the order.") + email: String @doc(description: "Order customer email.") } type OrderAddress @doc(description: "Contains detailed information about an order's billing and shipping addresses."){ diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/PlaceOrderTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/PlaceOrderTest.php index fdc96ba81d16..06fa8567795b 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/PlaceOrderTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/PlaceOrderTest.php @@ -109,7 +109,9 @@ public function testPlaceOrder() self::assertArrayHasKey('placeOrder', $response); self::assertArrayHasKey('order_number', $response['placeOrder']['order']); + self::assertArrayHasKey('number', $response['placeOrder']['orderV2']); self::assertEquals($reservedOrderId, $response['placeOrder']['order']['order_number']); + self::assertEquals($reservedOrderId, $response['placeOrder']['orderV2']['number']); self::assertEmpty(count($response['placeOrder']['errors'])); $orderIncrementId = $response['placeOrder']['order']['order_number']; $order = $this->orderFactory->create(); @@ -147,7 +149,9 @@ public function testPlaceOrderWithAutoGroup() self::assertArrayHasKey('placeOrder', $response); self::assertArrayHasKey('order_number', $response['placeOrder']['order']); + self::assertArrayHasKey('number', $response['placeOrder']['orderV2']); self::assertEquals($reservedOrderId, $response['placeOrder']['order']['order_number']); + self::assertEquals($reservedOrderId, $response['placeOrder']['orderV2']['number']); self::assertEmpty(count($response['placeOrder']['errors'])); $orderIncrementId = $response['placeOrder']['order']['order_number']; $order = $this->orderFactory->create(); @@ -458,7 +462,9 @@ public function testPlaceOrderWithGiftMessage() self::assertArrayHasKey('placeOrder', $response); self::assertArrayHasKey('order_number', $response['placeOrder']['order']); + self::assertArrayHasKey('number', $response['placeOrder']['orderV2']); self::assertEquals($reservedOrderId, $response['placeOrder']['order']['order_number']); + self::assertEquals($reservedOrderId, $response['placeOrder']['orderV2']['number']); $orderIncrementId = $response['placeOrder']['order']['order_number']; $order = $this->orderFactory->create(); $order->loadByIncrementId($orderIncrementId); @@ -477,6 +483,9 @@ private function getQuery(string $maskedQuoteId): string order { order_number } + orderV2 { + number + } errors { message code diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/GuestOrderByTokenTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/GuestOrderByTokenTest.php index f25c54d57a1e..959d21b3e5db 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/GuestOrderByTokenTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/GuestOrderByTokenTest.php @@ -48,6 +48,7 @@ class GuestOrderByTokenTest extends GraphQlAbstract token: "%token" }) { number + email billing_address { firstname lastname @@ -63,6 +64,7 @@ class GuestOrderByTokenTest extends GraphQlAbstract }) { orderV2 { number + email billing_address { firstname lastname @@ -97,6 +99,7 @@ public function testGuestOrder(): void $this->assertNotEmpty($placeOrderResponse['placeOrder']['orderV2']['number']); $this->assertNotEmpty($placeOrderResponse['placeOrder']['orderV2']['token']); + $this->assertNotEmpty($placeOrderResponse['placeOrder']['orderV2']['email']); $this->assertNotEmpty($placeOrderResponse['placeOrder']['orderV2']['billing_address']['firstname']); $this->assertNotEmpty($placeOrderResponse['placeOrder']['orderV2']['billing_address']['lastname']); @@ -111,6 +114,7 @@ public function testGuestOrder(): void [ 'guestOrderByToken' => [ 'number' => $placeOrderResponse['placeOrder']['orderV2']['number'], + 'email' => $placeOrderResponse['placeOrder']['orderV2']['email'], 'billing_address' => [ 'firstname' => $placeOrderResponse['placeOrder']['orderV2']['billing_address']['firstname'], 'lastname' => $placeOrderResponse['placeOrder']['orderV2']['billing_address']['lastname'] diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/GuestOrderTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/GuestOrderTest.php index fe8a57d19a2e..856defb108fc 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/GuestOrderTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/GuestOrderTest.php @@ -47,6 +47,7 @@ class GuestOrderTest extends GraphQlAbstract postcode: "%postcode" }) { number + email billing_address { firstname lastname @@ -83,6 +84,7 @@ public function testGuestOrder(): void [ 'guestOrder' => [ 'number' => $order->getIncrementId(), + 'email' => $order->getBillingAddress()->getEmail(), 'billing_address' => [ 'firstname' => $order->getBillingAddress()->getFirstname(), 'lastname' => $order->getBillingAddress()->getLastname() From 60503d788e7cdcf7f4affb5f8eca3cd2879c2c8a Mon Sep 17 00:00:00 2001 From: Sergio Vera <sergio.vera@gmail.com> Date: Wed, 6 Mar 2024 14:46:40 +0100 Subject: [PATCH 31/38] LYNX-358: Added shopping_cart_display_tax_gift_wrapping and cart_summary_display_total to GraphQl --- .../Magento/QuoteGraphQl/etc/graphql/di.xml | 4 ++ .../Magento/QuoteGraphQl/etc/schema.graphqls | 4 ++ .../Model/Resolver/DisplayGiftWrapping.php | 53 +++++++++++++++++++ .../Magento/TaxGraphQl/etc/graphql/di.xml | 17 +++++- .../Magento/TaxGraphQl/etc/schema.graphqls | 7 +++ .../GraphQl/Quote/StoreConfigResolverTest.php | 29 +++++++++- .../GraphQl/Tax/StoreConfigResolverTest.php | 3 ++ 7 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 app/code/Magento/TaxGraphQl/Model/Resolver/DisplayGiftWrapping.php diff --git a/app/code/Magento/QuoteGraphQl/etc/graphql/di.xml b/app/code/Magento/QuoteGraphQl/etc/graphql/di.xml index a7339b82fccd..83942f1daebb 100644 --- a/app/code/Magento/QuoteGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/QuoteGraphQl/etc/graphql/di.xml @@ -66,6 +66,10 @@ <item name="is_guest_checkout_enabled" xsi:type="string">checkout/options/guest_checkout</item> <item name="is_one_page_checkout_enabled" xsi:type="string">checkout/options/onepage_checkout_enabled</item> <item name="max_items_in_order_summary" xsi:type="string">checkout/options/max_items_display_count</item> + <item name="cart_summary_display_quantity" xsi:type="string">checkout/cart_link/use_qty</item> + <item name="minicart_display" xsi:type="string">checkout/sidebar/display</item> + <item name="minicart_max_items" xsi:type="string">checkout/sidebar/max_items_display_count</item> + <item name="cart_expires_in_days" xsi:type="string">checkout/cart/delete_quote_after</item> </argument> </arguments> </type> diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index ace304dcb6ee..53da2d875112 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -511,6 +511,10 @@ type StoreConfig { is_guest_checkout_enabled: Boolean @doc(description: "Extended Config Data - checkout/options/guest_checkout") is_one_page_checkout_enabled: Boolean @doc(description: "Extended Config Data - checkout/options/onepage_checkout_enabled") max_items_in_order_summary: Int @doc(description: "Extended Config Data - checkout/options/max_items_display_count") + cart_summary_display_quantity: Int @doc(description: "Extended Config Data - checkout/cart_link/use_qty") + minicart_display: Boolean @doc(description: "Extended Config Data - checkout/sidebar/display") + minicart_max_items: Int @doc(description: "Extended Config Data - checkout/sidebar/count") + cart_expires_in_days: Int @doc(description: "Extended Config Data - checkout/cart/delete_quote_after") } input EstimateTotalsInput { diff --git a/app/code/Magento/TaxGraphQl/Model/Resolver/DisplayGiftWrapping.php b/app/code/Magento/TaxGraphQl/Model/Resolver/DisplayGiftWrapping.php new file mode 100644 index 000000000000..3eefbdf2205e --- /dev/null +++ b/app/code/Magento/TaxGraphQl/Model/Resolver/DisplayGiftWrapping.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright 2024 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained from + * Adobe. + */ +declare(strict_types=1); + +namespace Magento\TaxGraphQl\Model\Resolver; + +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\EnumLookup; +use Magento\Framework\GraphQl\Query\ResolverInterface; + +/** + * @inheritdoc + */ +class DisplayGiftWrapping implements ResolverInterface +{ + /** + * @param EnumLookup $enumLookup + */ + public function __construct(private readonly EnumLookup $enumLookup) { + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (isset($value['shopping_cart_display_tax_gift_wrapping'])) { + return $this->enumLookup->getEnumValueFromField( + 'TaxGiftWrappingEnum', + $value['shopping_cart_display_tax_gift_wrapping'] + ); + } + return null; + } +} diff --git a/app/code/Magento/TaxGraphQl/etc/graphql/di.xml b/app/code/Magento/TaxGraphQl/etc/graphql/di.xml index a0316135e460..b81686edf2f6 100644 --- a/app/code/Magento/TaxGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/TaxGraphQl/etc/graphql/di.xml @@ -16,7 +16,8 @@ * ************************************************************************ */ --> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <type name="Magento\StoreGraphQl\Model\Resolver\Store\StoreConfigDataProvider"> <arguments> <argument name="extendedConfigData" xsi:type="array"> @@ -26,7 +27,21 @@ <item name="shopping_cart_display_grand_total" xsi:type="string">tax/cart_display/grandtotal</item> <item name="shopping_cart_display_full_summary" xsi:type="string">tax/cart_display/full_summary</item> <item name="shopping_cart_display_zero_tax" xsi:type="string">tax/cart_display/zero_tax</item> + <item name="shopping_cart_display_tax_gift_wrapping" xsi:type="string">tax/cart_display/gift_wrapping</item> + </argument> + </arguments> + </type> + <type name="Magento\Framework\GraphQl\Schema\Type\Enum\DefaultDataMapper"> + <arguments> + <argument name="map" xsi:type="array"> + <item name="TaxGiftWrappingEnum" xsi:type="array"> + <item name="display_excluding_tax" xsi:type="string">1</item> + <item name="display_including_tax" xsi:type="string">2</item> + <item name="display_type_both" xsi:type="string">3</item> + </item> </argument> </arguments> </type> </config> + + diff --git a/app/code/Magento/TaxGraphQl/etc/schema.graphqls b/app/code/Magento/TaxGraphQl/etc/schema.graphqls index e6234b83539d..aae1f234e0c6 100644 --- a/app/code/Magento/TaxGraphQl/etc/schema.graphqls +++ b/app/code/Magento/TaxGraphQl/etc/schema.graphqls @@ -12,4 +12,11 @@ type StoreConfig { shopping_cart_display_grand_total: Boolean @doc(description: "Extended Config Data - tax/cart_display/grandtotal") shopping_cart_display_full_summary: Boolean @doc(description: "Extended Config Data - tax/cart_display/full_summary") shopping_cart_display_zero_tax: Boolean @doc(description: "Extended Config Data - tax/cart_display/zero_tax") + shopping_cart_display_tax_gift_wrapping: TaxGiftWrappingEnum @doc(description: "Extended Config Data - tax/cart_display/gift_wrapping") @resolver(class: "Magento\\TaxGraphQl\\Model\\Resolver\\DisplayGiftWrapping") +} + +enum TaxGiftWrappingEnum { + DISPLAY_EXCLUDING_TAX + DISPLAY_INCLUDING_TAX + DISPLAY_TYPE_BOTH } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/StoreConfigResolverTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/StoreConfigResolverTest.php index 0dbf16957ffe..9e8bf05b3be8 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/StoreConfigResolverTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/StoreConfigResolverTest.php @@ -18,11 +18,28 @@ class StoreConfigResolverTest extends GraphQlAbstract { private const MAX_ITEMS_TO_DISPLAY = 5; + private const CART_SUMMARY_DISPLAY_TOTAL = 1; + private const MINICART_MAX_ITEMS = 10; + private const CART_EXPIRES_IN_DAYS = 5; #[ ConfigFixture(Data::XML_PATH_GUEST_CHECKOUT, true, ScopeInterface::SCOPE_STORE, 'default'), ConfigFixture('checkout/options/onepage_checkout_enabled', true, ScopeInterface::SCOPE_STORE, 'default'), - ConfigFixture('checkout/options/max_items_display_count', self::MAX_ITEMS_TO_DISPLAY) + ConfigFixture('checkout/options/max_items_display_count', self::MAX_ITEMS_TO_DISPLAY), + ConfigFixture('checkout/cart_link/use_qty', 1, ScopeInterface::SCOPE_STORE, 'default'), + ConfigFixture('checkout/sidebar/display', true, ScopeInterface::SCOPE_STORE, 'default'), + ConfigFixture( + 'checkout/sidebar/max_items_display_count', + self::MINICART_MAX_ITEMS, + ScopeInterface::SCOPE_STORE, + 'default' + ), + ConfigFixture( + 'checkout/cart/delete_quote_after', + self::CART_EXPIRES_IN_DAYS, + ScopeInterface::SCOPE_STORE, + 'default' + ), ] public function testGetStoreConfig(): void { @@ -32,7 +49,11 @@ public function testGetStoreConfig(): void storeConfig { is_guest_checkout_enabled, is_one_page_checkout_enabled, - max_items_in_order_summary + max_items_in_order_summary, + cart_summary_display_quantity, + minicart_display, + minicart_max_items, + cart_expires_in_days } } QUERY; @@ -52,5 +73,9 @@ private function validateStoreConfig( $this->assertTrue($responseConfig['is_guest_checkout_enabled']); $this->assertTrue($responseConfig['is_one_page_checkout_enabled']); $this->assertEquals(self::MAX_ITEMS_TO_DISPLAY, $responseConfig['max_items_in_order_summary']); + $this->assertEquals(self::CART_SUMMARY_DISPLAY_TOTAL, $responseConfig['cart_summary_display_quantity']); + $this->assertTrue($responseConfig['minicart_display']); + $this->assertEquals(self::MINICART_MAX_ITEMS, $responseConfig['minicart_max_items']); + $this->assertEquals(self::CART_EXPIRES_IN_DAYS, $responseConfig['cart_expires_in_days']); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Tax/StoreConfigResolverTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Tax/StoreConfigResolverTest.php index 9b8f21231986..0f2af31f7861 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Tax/StoreConfigResolverTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Tax/StoreConfigResolverTest.php @@ -25,6 +25,7 @@ class StoreConfigResolverTest extends GraphQlAbstract ConfigFixture(Config::XML_PATH_DISPLAY_CART_GRANDTOTAL, 1, ScopeInterface::SCOPE_STORE, 'default'), ConfigFixture(Config::XML_PATH_DISPLAY_CART_FULL_SUMMARY, 1, ScopeInterface::SCOPE_STORE, 'default'), ConfigFixture(Config::XML_PATH_DISPLAY_CART_ZERO_TAX, 1, ScopeInterface::SCOPE_STORE, 'default'), + ConfigFixture('tax/cart_display/gift_wrapping', 3, ScopeInterface::SCOPE_STORE, 'default'), ] public function testGetStoreConfig(): void { @@ -38,6 +39,7 @@ public function testGetStoreConfig(): void shopping_cart_display_grand_total, shopping_cart_display_full_summary, shopping_cart_display_zero_tax, + shopping_cart_display_tax_gift_wrapping, } } QUERY; @@ -60,5 +62,6 @@ private function validateStoreConfig( $this->assertEquals(1, $responseConfig['shopping_cart_display_grand_total']); $this->assertEquals(1, $responseConfig['shopping_cart_display_full_summary']); $this->assertEquals(1, $responseConfig['shopping_cart_display_zero_tax']); + $this->assertEquals('DISPLAY_TYPE_BOTH', $responseConfig['shopping_cart_display_tax_gift_wrapping']); } } From b4b00e73fae276dc54075d7d55c13d135fe0f12c Mon Sep 17 00:00:00 2001 From: Sergio Vera <sergio.vera@gmail.com> Date: Wed, 6 Mar 2024 14:47:28 +0100 Subject: [PATCH 32/38] LYNX-361: Implemented GraphQL email confirmation --- .../CustomerGraphQl/Model/Resolver/ConfirmEmail.php | 9 ++++++--- .../Magento/GraphQl/Customer/ConfirmEmailTest.php | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/ConfirmEmail.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/ConfirmEmail.php index 635a9576d0e5..4d6287c73c22 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/ConfirmEmail.php +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/ConfirmEmail.php @@ -20,6 +20,7 @@ use Magento\CustomerGraphQl\Model\Customer\ExtractCustomerData; use Magento\Framework\Exception\State\InputMismatchException; use Magento\Framework\Exception\State\InvalidTransitionException; +use Magento\Framework\Exception\StateException; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Query\Resolver\Value; @@ -66,13 +67,15 @@ public function resolve( if (!$this->emailValidator->isValid($args['input']['email'])) { throw new GraphQlInputException(__('Email is invalid')); } - try { $customer = $this->accountManagement->activate($args['input']['email'], $args['input']['confirmation_key']); } catch (InvalidTransitionException | InputMismatchException $e) { - throw new GraphQlInputException(__($e->getMessage())); + throw new GraphQlInputException(__($e->getRawMessage())); + } catch (StateException) { + throw new GraphQlInputException(__('This confirmation key is invalid or has expired.')); + } catch (\Exception) { + throw new GraphQlInputException(__('There was an error confirming the account')); } - return ['customer' => $this->extractCustomerData->execute($customer)]; } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ConfirmEmailTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ConfirmEmailTest.php index 49fb3bc933b2..4b6fa08cd27d 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ConfirmEmailTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ConfirmEmailTest.php @@ -106,6 +106,7 @@ public function testConfirmEmail() public function testConfirmEmailWrongEmail() { $this->expectException(\Exception::class); + $this->expectExceptionMessage('Email is invalid'); $this->graphQlMutation( sprintf( @@ -126,6 +127,7 @@ public function testConfirmEmailWrongEmail() public function testConfirmEmailWrongConfirmation() { $this->expectException(\Exception::class); + $this->expectExceptionMessage('The confirmation token is invalid. Verify the token and try again.'); $this->graphQlMutation( sprintf( From a3a9f56f695e3554cd80edcc62e4976ccd0224d8 Mon Sep 17 00:00:00 2001 From: eliseacornejo <ecornejo@adobe.com> Date: Wed, 6 Mar 2024 14:54:09 +0100 Subject: [PATCH 33/38] LYNX-362: Add confirmation status (#216) --- .../Model/Resolver/ConfirmationStatus.php | 58 ++++++ .../CustomerGraphQl/etc/graphql/di.xml | 1 + .../CustomerGraphQl/etc/schema.graphqls | 7 + .../Customer/ConfirmationStatusTest.php | 188 ++++++++++++++++++ .../GraphQl/Customer/StoreConfigTest.php | 35 ++++ 5 files changed, 289 insertions(+) create mode 100644 app/code/Magento/CustomerGraphQl/Model/Resolver/ConfirmationStatus.php create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ConfirmationStatusTest.php diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/ConfirmationStatus.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/ConfirmationStatus.php new file mode 100644 index 000000000000..70ef16bc5cb7 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/ConfirmationStatus.php @@ -0,0 +1,58 @@ +<?php +/************************************************************************ + * + * Copyright 2024 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + * ************************************************************************ + */ +declare(strict_types=1); + +namespace Magento\CustomerGraphQl\Model\Resolver; + +use Magento\Customer\Api\AccountManagementInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; + +/** + * Confirmation status resolver + */ +class ConfirmationStatus implements ResolverInterface +{ + /** + * @param AccountManagementInterface $accountManagement + */ + public function __construct( + private readonly AccountManagementInterface $accountManagement + ) { + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + try { + $confirmationStatus = $this->accountManagement->getConfirmationStatus($context->getUserId()); + } catch (LocalizedException $e) { + throw new GraphQlInputException(__($e->getMessage()), $e); + } + return strtoupper($confirmationStatus); + } +} diff --git a/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml b/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml index 0636098cd6d2..305e9cd12d67 100644 --- a/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml @@ -37,6 +37,7 @@ <item name="required_character_classes_number" xsi:type="string">customer/password/required_character_classes_number</item> <item name="minimum_password_length" xsi:type="string">customer/password/minimum_password_length</item> <item name="autocomplete_on_storefront" xsi:type="string">customer/password/autocomplete_on_storefront</item> + <item name="create_account_confirmation" xsi:type="string">customer/create_account/confirm</item> </argument> </arguments> </type> diff --git a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls index fb7b3d4dd6c2..848d0a9be8d0 100644 --- a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls @@ -5,6 +5,7 @@ type StoreConfig { required_character_classes_number : String @doc(description: "The number of different character classes (lowercase, uppercase, digits, special characters) required in a password.") minimum_password_length : String @doc(description: "The minimum number of characters required for a valid password.") autocomplete_on_storefront : Boolean @doc(description: "Indicates whether to enable autocomplete on login and forgot password forms.") + create_account_confirmation: Boolean @doc(description: "Indicates if the new accounts need confirmation.") } type Query { @@ -146,6 +147,7 @@ type Customer @doc(description: "Defines the customer name, addresses, and other addresses: [CustomerAddress] @doc(description: "An array containing the customer's shipping and billing addresses.") @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\CustomerAddresses") gender: Int @doc(description: "The customer's gender (Male - 1, Female - 2).") custom_attributes(attributeCodes: [ID!]): [AttributeValueInterface] @doc(description: "Customer's custom attributes.") @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\CustomAttributeFilter") + confirmation_status: ConfirmationStatusEnum! @doc(description: "The customer's confirmation status.") @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\ConfirmationStatus") } type CustomerAddress @doc(description: "Contains detailed information about a customer's billing or shipping address."){ @@ -473,3 +475,8 @@ enum ValidationRuleEnum @doc(description: "List of validation rule names applied MAX_IMAGE_HEIGHT MAX_IMAGE_WIDTH } + +enum ConfirmationStatusEnum @doc(description: "List of account confirmation statuses.") { + ACCOUNT_CONFIRMED @doc(description: "Account confirmed") + ACCOUNT_CONFIRMATION_NOT_REQUIRED @doc(description: "Account confirmation not required") +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ConfirmationStatusTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ConfirmationStatusTest.php new file mode 100644 index 000000000000..e2e05392917e --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ConfirmationStatusTest.php @@ -0,0 +1,188 @@ +<?php +/************************************************************************ + * + * Copyright 2024 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + * ************************************************************************ + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Customer; + +use Magento\Customer\Api\AccountManagementInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Test\Fixture\Customer; +use Magento\Framework\Exception\AuthenticationException; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\Fixture\Config; +use Magento\TestFramework\Fixture\DataFixtureStorageManager; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; +use Magento\TestFramework\Fixture\DataFixture; + +/** + * Tests for confirmation status + */ +class ConfirmationStatusTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + protected function setUp(): void + { + parent::setUp(); + $this->customerTokenService = Bootstrap::getObjectManager()->get(CustomerTokenServiceInterface::class); + $this->customerRepository = Bootstrap::getObjectManager()->get(CustomerRepositoryInterface::class); + } + + #[ + Config('customer/create_account/confirm', 0), + DataFixture( + Customer::class, + [ + 'email' => 'customer@example.com', + ], + 'customer' + ) + ] + public function testGetConfirmationStatusConfirmationNotRequiredTest() + { + $customer = DataFixtureStorageManager::getStorage()->get('customer'); + $query = <<<QUERY +query { + customer { + confirmation_status + } +} +QUERY; + $response = $this->graphQlQuery( + $query, + [], + '', + $this->getHeaderMap($customer->getEmail(), 'password') + ); + $this->assertEquals( + strtoupper(AccountManagementInterface::ACCOUNT_CONFIRMATION_NOT_REQUIRED), + $response['customer']['confirmation_status'] + ); + } + + #[ + Config('customer/create_account/confirm', 1), + DataFixture( + Customer::class, + [ + 'email' => 'customer@example.com', + ], + 'customer' + ) + ] + public function testGetConfirmationStatusConfirmedTest() + { + $customer = DataFixtureStorageManager::getStorage()->get('customer'); + $query = <<<QUERY +query { + customer { + confirmation_status + } +} +QUERY; + $response = $this->graphQlQuery( + $query, + [], + '', + $this->getHeaderMap($customer->getEmail(), 'password') + ); + $this->assertEquals( + strtoupper(AccountManagementInterface::ACCOUNT_CONFIRMED), + $response['customer']['confirmation_status'] + ); + } + + #[ + Config('customer/create_account/confirm', 1), + DataFixture( + Customer::class, + [ + 'email' => 'another@example.com', + ], + 'customer' + ) + ] + public function testGetConfirmationStatusConfirmationRequiredTest() + { + $this->expectExceptionMessage("This account isn't confirmed. Verify and try again."); + /** @var CustomerInterface $customer */ + $customer = DataFixtureStorageManager::getStorage()->get('customer'); + $headersMap = $this->getHeaderMap($customer->getEmail(), 'password'); + $customer->setConfirmation(AccountManagementInterface::ACCOUNT_CONFIRMATION_REQUIRED); + $this->customerRepository->save($this->customerRepository->get($customer->getEmail())); + $query = <<<QUERY +query { + customer { + confirmation_status + } +} +QUERY; + $this->graphQlQuery( + $query, + [], + '', + $headersMap + ); + } + + #[ + DataFixture( + Customer::class, + [ + 'email' => 'test@example.com', + ], + 'customer' + ) + ] + public function testGetConfirmationStatusIfUserIsNotAuthorizedTest() + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('The current customer isn\'t authorized.'); + + $query = <<<QUERY +query { + customer { + confirmation_status + } +} +QUERY; + $this->graphQlQuery($query); + } + + /** + * @param string $email + * @param string $password + * + * @return array + * @throws AuthenticationException + */ + private function getHeaderMap(string $email, string $password): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($email, $password); + return ['Authorization' => 'Bearer ' . $customerToken]; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/StoreConfigTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/StoreConfigTest.php index 473105c2c46b..99b9e95e8d1f 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/StoreConfigTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/StoreConfigTest.php @@ -8,6 +8,7 @@ namespace Magento\GraphQl\Customer; use Exception; +use Magento\TestFramework\Fixture\Config; use Magento\TestFramework\TestCase\GraphQlAbstract; /** @@ -35,4 +36,38 @@ public function testReturnTypeAutocompleteOnStorefrontConfig() self::assertArrayHasKey('autocomplete_on_storefront', $response['storeConfig']); self::assertTrue($response['storeConfig']['autocomplete_on_storefront']); } + + #[ + Config('customer/create_account/confirm', 1) + ] + public function testCreateAccountConfirmationEnabledStorefrontConfig() + { + $query = <<<QUERY +{ + storeConfig { + create_account_confirmation + } +} +QUERY; + $response = $this->graphQlQuery($query); + self::assertArrayHasKey('create_account_confirmation', $response['storeConfig']); + self::assertTrue($response['storeConfig']['create_account_confirmation']); + } + + #[ + Config('customer/create_account/confirm', 0) + ] + public function testCreateAccountConfirmationDisabledStorefrontConfig() + { + $query = <<<QUERY +{ + storeConfig { + create_account_confirmation + } +} +QUERY; + $response = $this->graphQlQuery($query); + self::assertArrayHasKey('create_account_confirmation', $response['storeConfig']); + self::assertFalse($response['storeConfig']['create_account_confirmation']); + } } From ab8d439f1c294594c849084177723aea3e12533b Mon Sep 17 00:00:00 2001 From: Sergio Vera <svera@adobe.com> Date: Wed, 6 Mar 2024 15:54:32 +0100 Subject: [PATCH 34/38] Fixed static test failure on gift wrapping resolver --- .../Magento/TaxGraphQl/Model/Resolver/DisplayGiftWrapping.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/TaxGraphQl/Model/Resolver/DisplayGiftWrapping.php b/app/code/Magento/TaxGraphQl/Model/Resolver/DisplayGiftWrapping.php index 3eefbdf2205e..886650b835c8 100644 --- a/app/code/Magento/TaxGraphQl/Model/Resolver/DisplayGiftWrapping.php +++ b/app/code/Magento/TaxGraphQl/Model/Resolver/DisplayGiftWrapping.php @@ -29,7 +29,8 @@ class DisplayGiftWrapping implements ResolverInterface /** * @param EnumLookup $enumLookup */ - public function __construct(private readonly EnumLookup $enumLookup) { + public function __construct(private readonly EnumLookup $enumLookup) + { } /** From 9da42814a4bc7342fd7ba6ae3e48a319fdc2f776 Mon Sep 17 00:00:00 2001 From: Rafal Janicki <rjanicki@adobe.com> Date: Wed, 6 Mar 2024 17:24:20 +0000 Subject: [PATCH 35/38] Deleted redundant dependency --- app/code/Magento/IntegrationGraphQl/composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/code/Magento/IntegrationGraphQl/composer.json b/app/code/Magento/IntegrationGraphQl/composer.json index 942f629c5e76..bb26e5f2af1f 100644 --- a/app/code/Magento/IntegrationGraphQl/composer.json +++ b/app/code/Magento/IntegrationGraphQl/composer.json @@ -4,8 +4,7 @@ "type": "magento2-module", "require": { "php": "~8.1.0||~8.2.0||~8.3.0", - "magento/framework": "*", - "magento/module-integration": "*" + "magento/framework": "*" }, "license": [ "OSL-3.0", From 6bc1e4ca2add0479ba1c737cfbd8fe3062282564 Mon Sep 17 00:00:00 2001 From: Sergio Vera <svera@adobe.com> Date: Wed, 6 Mar 2024 19:20:05 +0100 Subject: [PATCH 36/38] LYNX-358: Avoid banned giftwrapping --- .../Resolver/{DisplayGiftWrapping.php => DisplayWrapping.php} | 4 ++-- app/code/Magento/TaxGraphQl/etc/graphql/di.xml | 2 +- app/code/Magento/TaxGraphQl/etc/schema.graphqls | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename app/code/Magento/TaxGraphQl/Model/Resolver/{DisplayGiftWrapping.php => DisplayWrapping.php} (93%) diff --git a/app/code/Magento/TaxGraphQl/Model/Resolver/DisplayGiftWrapping.php b/app/code/Magento/TaxGraphQl/Model/Resolver/DisplayWrapping.php similarity index 93% rename from app/code/Magento/TaxGraphQl/Model/Resolver/DisplayGiftWrapping.php rename to app/code/Magento/TaxGraphQl/Model/Resolver/DisplayWrapping.php index 886650b835c8..bc85bcd6249f 100644 --- a/app/code/Magento/TaxGraphQl/Model/Resolver/DisplayGiftWrapping.php +++ b/app/code/Magento/TaxGraphQl/Model/Resolver/DisplayWrapping.php @@ -24,7 +24,7 @@ /** * @inheritdoc */ -class DisplayGiftWrapping implements ResolverInterface +class DisplayWrapping implements ResolverInterface { /** * @param EnumLookup $enumLookup @@ -45,7 +45,7 @@ public function resolve( ) { if (isset($value['shopping_cart_display_tax_gift_wrapping'])) { return $this->enumLookup->getEnumValueFromField( - 'TaxGiftWrappingEnum', + 'TaxWrappingEnum', $value['shopping_cart_display_tax_gift_wrapping'] ); } diff --git a/app/code/Magento/TaxGraphQl/etc/graphql/di.xml b/app/code/Magento/TaxGraphQl/etc/graphql/di.xml index b81686edf2f6..601a81b7476d 100644 --- a/app/code/Magento/TaxGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/TaxGraphQl/etc/graphql/di.xml @@ -34,7 +34,7 @@ <type name="Magento\Framework\GraphQl\Schema\Type\Enum\DefaultDataMapper"> <arguments> <argument name="map" xsi:type="array"> - <item name="TaxGiftWrappingEnum" xsi:type="array"> + <item name="TaxWrappingEnum" xsi:type="array"> <item name="display_excluding_tax" xsi:type="string">1</item> <item name="display_including_tax" xsi:type="string">2</item> <item name="display_type_both" xsi:type="string">3</item> diff --git a/app/code/Magento/TaxGraphQl/etc/schema.graphqls b/app/code/Magento/TaxGraphQl/etc/schema.graphqls index aae1f234e0c6..2696098dbaad 100644 --- a/app/code/Magento/TaxGraphQl/etc/schema.graphqls +++ b/app/code/Magento/TaxGraphQl/etc/schema.graphqls @@ -12,10 +12,10 @@ type StoreConfig { shopping_cart_display_grand_total: Boolean @doc(description: "Extended Config Data - tax/cart_display/grandtotal") shopping_cart_display_full_summary: Boolean @doc(description: "Extended Config Data - tax/cart_display/full_summary") shopping_cart_display_zero_tax: Boolean @doc(description: "Extended Config Data - tax/cart_display/zero_tax") - shopping_cart_display_tax_gift_wrapping: TaxGiftWrappingEnum @doc(description: "Extended Config Data - tax/cart_display/gift_wrapping") @resolver(class: "Magento\\TaxGraphQl\\Model\\Resolver\\DisplayGiftWrapping") + shopping_cart_display_tax_gift_wrapping: TaxWrappingEnum @doc(description: "Extended Config Data - tax/cart_display/gift_wrapping") @resolver(class: "Magento\\TaxGraphQl\\Model\\Resolver\\DisplayWrapping") } -enum TaxGiftWrappingEnum { +enum TaxWrappingEnum { DISPLAY_EXCLUDING_TAX DISPLAY_INCLUDING_TAX DISPLAY_TYPE_BOTH From 8ceb28d353a56cff9f5ccab87a116db6bbd0a1d8 Mon Sep 17 00:00:00 2001 From: eliseacornejo <ecornejo@adobe.com> Date: Wed, 6 Mar 2024 10:28:01 +0100 Subject: [PATCH 37/38] LYNX-367: MFTF admin tests for create order (#219) --- .../Section/AdminOrderFormItemsSection.xml | 1 + .../Test/Mftf/Data/SalesRuleData.xml | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml index 0497bdbeb26f..b6829b97bd10 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml @@ -36,6 +36,7 @@ <element name="couponCode" type="input" selector="#order-coupons input" timeout="30"/> <element name="applyCoupon" type="button" selector="#order-coupons button"/> <element name="removeCoupon" type="button" selector=".added-coupon-code .action-remove"/> + <element name="removeSpecificCoupon" type="button" selector=".added-coupon-code.coupon-code-{{couponCode}} .action-remove" parameterized="true"/> <element name="totalRecords" type="text" selector="#sales_order_create_search_grid-total-count"/> <element name="numberOfPages" type="text" selector="div.admin__data-grid-pager-wrap div.admin__data-grid-pager > label"/> <element name="productName" type="button" selector="(.//*[@class='col-product'])[2]/span"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml index c378c58008a2..3fee17da8211 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml @@ -585,4 +585,39 @@ <data key="apply">Percent of product price discount</data> <data key="discountAmount">10</data> </entity> + + <entity name="SalesRuleSpecificCouponWithFixedAmountDiscount" type="SalesRule"> + <data key="name" unique="suffix">SimpleSalesRule</data> + <data key="description">Sales Rule Description</data> + <array key="website_ids"> + <item>1</item> + </array> + <array key="customer_group_ids"> + <item>1</item> + </array> + <data key="uses_per_customer">10</data> + <data key="is_active">true</data> + <data key="stop_rules_processing">false</data> + <data key="is_advanced">true</data> + <data key="sort_order">1</data> + <data key="simple_action">by_fixed</data> + <data key="discount_amount">10</data> + <data key="discount_qty">10</data> + <data key="discount_step">0</data> + <data key="apply_to_shipping">false</data> + <data key="times_used">0</data> + <data key="is_rss">false</data> + <data key="coupon_type">SPECIFIC_COUPON</data> + <data key="use_auto_generation">false</data> + <data key="uses_per_coupon">10</data> + <data key="simple_free_shipping">1</data> + </entity> + + <entity name="InactiveSalesRuleSpecificCouponWithFixedAmountDiscount" extends="SalesRuleSpecificCouponWithFixedAmountDiscount"> + <data key="is_active">false</data> + </entity> + <entity name="SalesRuleSpecificCouponAndByPercentLessPriority" extends="SalesRuleSpecificCouponAndByPercent"> + <data key="sort_order">3</data> + <data key="stop_rules_processing">true</data> + </entity> </entities> From 06d086ebe9837d505c26379649e7c772ddae8cc3 Mon Sep 17 00:00:00 2001 From: eliseacornejo <ecornejo@adobe.com> Date: Thu, 7 Mar 2024 12:33:37 +0100 Subject: [PATCH 38/38] LYNX-369: add element for mftf test (#223) --- .../Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml index b6829b97bd10..ef359ce11ead 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml @@ -37,6 +37,7 @@ <element name="applyCoupon" type="button" selector="#order-coupons button"/> <element name="removeCoupon" type="button" selector=".added-coupon-code .action-remove"/> <element name="removeSpecificCoupon" type="button" selector=".added-coupon-code.coupon-code-{{couponCode}} .action-remove" parameterized="true"/> + <element name="couponCodeApplied" type="text" selector=".added-coupon-code.coupon-code-{{couponCode}} span" parameterized="true"/> <element name="totalRecords" type="text" selector="#sales_order_create_search_grid-total-count"/> <element name="numberOfPages" type="text" selector="div.admin__data-grid-pager-wrap div.admin__data-grid-pager > label"/> <element name="productName" type="button" selector="(.//*[@class='col-product'])[2]/span"/>