Skip to content

Commit f3998a2

Browse files
Merge branch '2.4-develop' into spartans_pr_04112025
2 parents 819384e + 43a5442 commit f3998a2

File tree

40 files changed

+3744
-1756
lines changed

40 files changed

+3744
-1756
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright 2025 Adobe
5+
* All Rights Reserved.
6+
*/
7+
-->
8+
<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
9+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
10+
<test name="StorefrontValidatePriceTest">
11+
<annotations>
12+
<features value="Catalog"/>
13+
<stories value="Validate Price in frontend product search and product details pages"/>
14+
<title value="Product price validation"/>
15+
<description value="This test case verifies price in frontend product search and product details pages based on catalog price scope"/>
16+
<severity value="MAJOR"/>
17+
<testCaseId value="AC-3183"/>
18+
<group value="catalog"/>
19+
</annotations>
20+
<before>
21+
<!-- Precondition 2:Create Customer with allowed remote shopping assistance-->
22+
<createData entity="Simple_US_Customer_Assistance_Allowed" stepKey="createCustomer"/>
23+
<!-- Precondition 4:Create Simple Product -->
24+
<createData entity="_defaultProduct" stepKey="createProduct">
25+
<field key="price">100</field>
26+
</createData>
27+
<actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin1"/>
28+
<!-- Precondition 1:Create website-->
29+
<actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createWebsite">
30+
<argument name="newWebsiteName" value="{{customWebsite.name}}"/>
31+
<argument name="websiteCode" value="{{customWebsite.code}}"/>
32+
</actionGroup>
33+
<!-- Create second store -->
34+
<actionGroup ref="CreateCustomStoreActionGroup" stepKey="createCustomStore">
35+
<argument name="website" value="{{customWebsite.name}}"/>
36+
<argument name="store" value="{{customStoreGroup.name}}"/>
37+
<argument name="rootCategory" value="Default Category"/>
38+
</actionGroup>
39+
<!-- Create second store view -->
40+
<actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createCustomStoreView">
41+
<argument name="StoreGroup" value="customStoreGroup"/>
42+
<argument name="customStore" value="customStoreEN"/>
43+
</actionGroup>
44+
<!--Open customer edit page-->
45+
<actionGroup ref="AdminOpenCustomerEditPageActionGroup" stepKey="openCustomerEditPage">
46+
<argument name="customerId" value="$createCustomer.id$"/>
47+
</actionGroup>
48+
<!--Navigate to "Account Information" tab-->
49+
<actionGroup ref="AdminOpenAccountInformationTabFromCustomerEditPageActionGroup" stepKey="openAccountInformationEditPage"/>
50+
<!-- Precondition 3:Assign customer to custom website-->
51+
<actionGroup ref="AdminUpdateCustomerWebsiteInCustomerInformationPageActionGroup" stepKey="updateCustomerWebsite">
52+
<argument name="websiteName" value="{{customWebsite.name}}"/>
53+
</actionGroup>
54+
<!--Verify that changes are saved successfully-->
55+
<actionGroup ref="AdminSaveCustomerAndAssertSuccessMessage" stepKey="assertThatChangesAreSavedSuccessfully"/>
56+
</before>
57+
<after>
58+
<!-- Logout from customer, delete product, customer, website -->
59+
<actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/>
60+
<deleteData createDataKey="createProduct" stepKey="deleteProduct"/>
61+
<deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/>
62+
<actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite">
63+
<argument name="websiteName" value="{{customWebsite.name}}"/>
64+
</actionGroup>
65+
</after>
66+
<!-- Step-1: Set catalog price scope to website -->
67+
<actionGroup ref="AdminSetCatalogPriceToWebsiteActionGroup" stepKey="setPriceScopeWebsite"/>
68+
<!-- Step 2: Reindex and cache clear -->
69+
<actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex">
70+
<argument name="indices" value=""/>
71+
</actionGroup>
72+
<actionGroup ref="CliCacheFlushActionGroup" stepKey="cleanCache">
73+
<argument name="tags" value=""/>
74+
</actionGroup>
75+
<!-- Step 3: Open created product and assign to website-->
76+
<actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="openProductEdit">
77+
<argument name="productId" value="$createProduct.id$"/>
78+
</actionGroup>
79+
<actionGroup ref="AdminAssignProductInWebsiteActionGroup" stepKey="assignProductToSecondWebsite">
80+
<argument name="website" value="{{customWebsite.name}}"/>
81+
</actionGroup>
82+
<actionGroup ref="SaveProductFormActionGroup" stepKey="saveTheProduct"/>
83+
<!-- Step 4: Edit product and change the price in default scope-->
84+
<actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="openTheProductEdit">
85+
<argument name="productId" value="$createProduct.id$"/>
86+
</actionGroup>
87+
<actionGroup ref="SwitchToTheNewStoreViewActionGroup" stepKey="switchDefaultStoreView">
88+
<argument name="storeViewName" value="'Default Store View'"/>
89+
</actionGroup>
90+
<waitForElementVisible selector="{{AdminProductFormSection.productPrice}}" stepKey="waitForProductPriceField"/>
91+
<uncheckOption selector="{{AdminProductFormSection.productPriceUseDefault}}" stepKey="uncheckPriceDefaultValue"/>
92+
<fillField selector="{{AdminProductFormSection.productPrice}}" userInput="150" stepKey="fillSimpleProductPrice"/>
93+
<actionGroup ref="SaveProductFormActionGroup" stepKey="saveProductPrice"/>
94+
<!-- Step 5: Edit product and change the price in custom website-->
95+
<actionGroup ref="SwitchToTheNewStoreViewActionGroup" stepKey="changeScopeToStoreView2">
96+
<argument name="storeViewName" value="{{customStoreEN.name}}"/>
97+
</actionGroup>
98+
<waitForElementVisible selector="{{AdminProductFormSection.productPrice}}" stepKey="waitForTheProductPriceField"/>
99+
<uncheckOption selector="{{AdminProductFormSection.productPriceUseDefault}}" stepKey="uncheckThePriceDefaultValue"/>
100+
<fillField selector="{{AdminProductFormSection.productPrice}}" userInput="200" stepKey="fillTheSimpleProductPrice"/>
101+
<!--Save product-->
102+
<actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/>
103+
<!--Step6: From admin login as customer to storefront and verify website scope product price is displayed in catalog search results page and product details page -->
104+
<actionGroup ref="AdminLoginAsCustomerLoginFromCustomerPageActionGroup" stepKey="loginAsCustomerFromCustomerPage">
105+
<argument name="customerId" value="$$createCustomer.id$$"/>
106+
</actionGroup>
107+
<actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByProductName">
108+
<argument name="phrase" value="$createProduct.name$"/>
109+
</actionGroup>
110+
<waitForPageLoad stepKey="waitForSearchResultsPage" />
111+
<waitForText selector="{{StorefrontCategoryMainSection.productPrice}}" userInput="$200.00" stepKey="seeWebsiteScopeProductPriceInSearchResultsPage"/>
112+
<actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage">
113+
<argument name="productUrl" value="$createProduct.custom_attributes[url_key]$"/>
114+
</actionGroup>
115+
<waitForText userInput="$200.00" selector="{{StorefrontProductInfoMainSection.price}}" stepKey="seeWebsiteScopeProductPrice"/>
116+
<!-- Step 7: Set catalog price scope to website -->
117+
<actionGroup ref="AdminSetDefaultCatalogPriceActionGroup" stepKey="setScopeGlobal"/>
118+
<!-- Step 8: Reindex and cache clear -->
119+
<actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindexing">
120+
<argument name="indices" value=""/>
121+
</actionGroup>
122+
<actionGroup ref="CliCacheFlushActionGroup" stepKey="cacheClean">
123+
<argument name="tags" value=""/>
124+
</actionGroup>
125+
<!--Step 9: Login as customer to storefront and verify global scope product price is displayed in catalog search results page and product details page -->
126+
<actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="searchByProductName">
127+
<argument name="phrase" value="$createProduct.name$"/>
128+
</actionGroup>
129+
<waitForPageLoad stepKey="waitSearchResult"/>
130+
<waitForText selector="{{StorefrontCategoryMainSection.productPrice}}" userInput="$100.00" stepKey="seeGlobalPrice"/>
131+
<actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openTheProductPage">
132+
<argument name="productUrl" value="$createProduct.custom_attributes[url_key]$"/>
133+
</actionGroup>
134+
<waitForPageLoad stepKey="waitForTheProductPageToOpen"/>
135+
<waitForText userInput="$100.00" selector="{{StorefrontProductInfoMainSection.price}}" stepKey="seeTheGlobalPrice"/>
136+
</test>
137+
</tests>

app/code/Magento/Cms/Test/Mftf/ActionGroup/CreateNewPageWithWidgetActionGroup.xml

Lines changed: 0 additions & 45 deletions
This file was deleted.

app/code/Magento/Cms/Test/Mftf/class-file-naming-allowlist

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ AdminAddImageToCMSBlockContent
22
AssertBlockContent
33
AssignBlockToCMSPage
44
CreateNewPageWithBasicValues
5-
CreateNewPageWithWidget
65
FillOutBlockContent
76
FillOutCMSPageContent
87
deleteBlock
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CustomerGraphQl\Model\Customer\Address;
9+
10+
use Magento\Customer\Api\AddressRepositoryInterface;
11+
use Magento\Customer\Api\Data\AddressInterface;
12+
use Magento\Framework\Exception\LocalizedException;
13+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
14+
15+
class DeleteCustomerAddressV2
16+
{
17+
/**
18+
* DeleteCustomerAddressV2 Constructor
19+
*
20+
* @param AddressRepositoryInterface $addressRepository
21+
*/
22+
public function __construct(
23+
private readonly AddressRepositoryInterface $addressRepository
24+
) {
25+
}
26+
27+
/**
28+
* Delete customer address
29+
*
30+
* @param AddressInterface $address
31+
* @return void
32+
* @throws GraphQlInputException
33+
*/
34+
public function execute(AddressInterface $address): void
35+
{
36+
if ($address->isDefaultBilling()) {
37+
throw new GraphQlInputException(
38+
__('Customer Address with the specified ID is set as default billing address and can not be deleted')
39+
);
40+
}
41+
42+
if ($address->isDefaultShipping()) {
43+
throw new GraphQlInputException(
44+
__('Customer Address with the specified ID is set as default shipping address and can not be deleted')
45+
);
46+
}
47+
48+
try {
49+
$this->addressRepository->delete($address);
50+
} catch (LocalizedException $e) {
51+
throw new GraphQlInputException(__($e->getMessage()), $e);
52+
}
53+
}
54+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CustomerGraphQl\Model\Customer\Address;
9+
10+
use Magento\Customer\Api\AddressRepositoryInterface;
11+
use Magento\Customer\Api\Data\AddressInterface;
12+
use Magento\Framework\Exception\LocalizedException;
13+
use Magento\Framework\Exception\NoSuchEntityException;
14+
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
15+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
16+
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
17+
18+
class GetCustomerAddressV2
19+
{
20+
/**
21+
* GetCustomerAddressV2 Constructor
22+
*
23+
* @param AddressRepositoryInterface $addressRepository
24+
*/
25+
public function __construct(
26+
private readonly AddressRepositoryInterface $addressRepository
27+
) {
28+
}
29+
30+
/**
31+
* Get customer address
32+
*
33+
* @param int $addressId
34+
* @param int $customerId
35+
* @return AddressInterface
36+
* @throws GraphQlInputException
37+
* @throws GraphQlNoSuchEntityException
38+
* @throws GraphQlAuthorizationException
39+
*/
40+
public function execute(int $addressId, int $customerId): AddressInterface
41+
{
42+
try {
43+
$customerAddress = $this->addressRepository->getById($addressId);
44+
} catch (NoSuchEntityException $e) {
45+
throw new GraphQlNoSuchEntityException(
46+
__('Could not find an address with the specified ID')
47+
);
48+
} catch (LocalizedException $e) {
49+
throw new GraphQlInputException(__($e->getMessage()), $e);
50+
}
51+
52+
if ((int)$customerAddress->getCustomerId() !== $customerId) {
53+
throw new GraphQlAuthorizationException(
54+
__('Current customer does not have permission to get address with the specified ID')
55+
);
56+
}
57+
58+
return $customerAddress;
59+
}
60+
}

app/code/Magento/CustomerGraphQl/Model/Customer/ExtractCustomerData.php

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Magento\Customer\Api\Data\CustomerInterface;
1313
use Magento\EavGraphQl\Model\GetAttributeValueComposite;
1414
use Magento\Framework\Exception\LocalizedException;
15+
use Magento\Framework\GraphQl\Query\Uid;
1516
use Magento\Framework\Webapi\ServiceOutputProcessor;
1617

1718
/**
@@ -20,25 +21,17 @@
2021
class ExtractCustomerData
2122
{
2223
/**
23-
* @var ServiceOutputProcessor
24-
*/
25-
private $serviceOutputProcessor;
26-
27-
/**
28-
* @var GetAttributeValueComposite
29-
*/
30-
private GetAttributeValueComposite $getAttributeValueComposite;
31-
32-
/**
24+
* ExtractCustomerData Constructor.
25+
*
3326
* @param ServiceOutputProcessor $serviceOutputProcessor
3427
* @param GetAttributeValueComposite $getAttributeValueComposite
28+
* @param Uid $idEncoder
3529
*/
3630
public function __construct(
37-
ServiceOutputProcessor $serviceOutputProcessor,
38-
GetAttributeValueComposite $getAttributeValueComposite
31+
private readonly ServiceOutputProcessor $serviceOutputProcessor,
32+
private readonly GetAttributeValueComposite $getAttributeValueComposite,
33+
private readonly Uid $idEncoder
3934
) {
40-
$this->serviceOutputProcessor = $serviceOutputProcessor;
41-
$this->getAttributeValueComposite = $getAttributeValueComposite;
4235
}
4336

4437
/**
@@ -57,6 +50,7 @@ private function curateAddressData(array $arrayAddress): array
5750
$arrayAddress[$key]['default_billing'] = false;
5851
}
5952
}
53+
6054
return $arrayAddress;
6155
}
6256

@@ -98,14 +92,15 @@ function (array $customAttribute) {
9892
}
9993
//Fields are deprecated and should not be exposed on storefront.
10094
$customerData['group_id'] = null;
101-
$customerData['id'] = null;
102-
10395
$customerData['model'] = $customer;
10496

10597
//'dob' is deprecated, 'date_of_birth' is used instead.
10698
if (!empty($customerData['dob'])) {
10799
$customerData['date_of_birth'] = $customerData['dob'];
108100
}
101+
102+
$customerData['id'] = $this->idEncoder->encode((string) $customerData['id']);
103+
109104
return $customerData;
110105
}
111106
}

app/code/Magento/CustomerGraphQl/Model/Resolver/CustomerAddressUid.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,7 @@ public function resolve(
3434
ResolveInfo $info,
3535
?array $value = null,
3636
?array $args = null
37-
): string {
38-
if (!isset($value['id'])) {
39-
throw new LocalizedException(__('Missing required address ID.'));
40-
}
41-
42-
return $this->idEncoder->encode((string) $value['id']);
37+
): ?string {
38+
return isset($value['id']) ? $this->idEncoder->encode((string) $value['id']): null;
4339
}
4440
}

0 commit comments

Comments
 (0)