Skip to content

Commit 6dd3fa9

Browse files
authored
Merge pull request #9988 from adobe-commerce-tier-4/PR_2025_08_04_chittima
[Support Tier-4 chittima] 08-04-2025 Regular delivery of bugfixes and improvements
2 parents b209408 + 275404e commit 6dd3fa9

File tree

26 files changed

+992
-89
lines changed

26 files changed

+992
-89
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright 2024 Adobe
5+
* All Rights Reserved.
6+
*/
7+
-->
8+
9+
<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
11+
<actionGroup name="AssertElementVisibleOnScreenActionGroup">
12+
<annotations>
13+
<description>Asserts that an element is fully visible on screen within viewport bounds, not just present in DOM</description>
14+
</annotations>
15+
<arguments>
16+
<argument name="selector" type="string"/>
17+
</arguments>
18+
19+
<executeJS function="
20+
var element = document.querySelector('{{selector}}');
21+
if (!element) {
22+
return 'Element not found in DOM';
23+
}
24+
25+
var rect = element.getBoundingClientRect();
26+
var windowHeight = window.innerHeight;
27+
var windowWidth = window.innerWidth;
28+
29+
var isVisible = rect.top &gt;= 0 &amp;&amp;
30+
rect.left &gt;= 0 &amp;&amp;
31+
rect.bottom &lt;= windowHeight &amp;&amp;
32+
rect.right &lt;= windowWidth;
33+
34+
return isVisible ? 'Element is visible on screen' : 'Element is positioned off-screen';
35+
" stepKey="checkElementVisibility"/>
36+
37+
<assertStringContainsString stepKey="assertElementIsVisible">
38+
<expectedResult type="string">Element is visible on screen</expectedResult>
39+
<actualResult type="variable">$checkElementVisibility</actualResult>
40+
</assertStringContainsString>
41+
</actionGroup>
42+
</actionGroups>

app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection/AdminProductFormSection.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,5 +93,6 @@
9393
<element name="customSwitcherAttribute" type="checkbox" selector="//input[@name='product[{{attribute_code}}]' and @value='{{checked_value}}']/parent::div[@data-role='switcher']" parameterized="true"/>
9494
<element name="attributeOptionUncheckDefaultValue" type="checkbox" selector="input[name='use_default[{{attribute_code}}]']" parameterized="true"/>
9595
<element name="customAttributeUnderMassUpdate" type="select" selector="//select[@name='attributes[{{attribute_code}}][]']" parameterized="true"/>
96+
<element name="datePickerCalendar" type="button" selector="//div[{{row}}]//button[contains(@class, 'ui-datepicker-trigger')]" parameterized="true"/>
9697
</section>
9798
</sections>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright 2024 Adobe
5+
* All Rights Reserved.
6+
*/
7+
-->
8+
9+
<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
11+
<test name="AdminDateAttributeCalendarPositioningTest">
12+
<annotations>
13+
<features value="Catalog"/>
14+
<stories value="ACP2E-4060: Date Attribute in custom Attribute Group Fails to Show Datepicker in Admin"/>
15+
<title value="Date attribute calendar should appear in correct position when assigned to attribute group"/>
16+
<description value="Verify that date attributes assigned to custom attribute groups display the calendar widget in the correct position relative to the input field"/>
17+
<severity value="MAJOR"/>
18+
<testCaseId value="AC-15180"/>
19+
<group value="Catalog"/>
20+
</annotations>
21+
22+
<before>
23+
<actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/>
24+
25+
<!-- Create a new date attribute -->
26+
<createData entity="dateProductAttribute" stepKey="attribute"/>
27+
28+
<!-- Go to default attribute set edit page -->
29+
<amOnPage url="{{AdminProductAttributeSetEditPage.url}}/{{AddToDefaultSet.attributeSetId}}/" stepKey="onAttributeSetEdit"/>
30+
31+
<!-- Assert created attribute in unassigned section -->
32+
<see userInput="$$attribute.attribute_code$$" selector="{{AdminProductAttributeSetEditSection.unassignedAttributesTree}}" stepKey="seeAttributeInUnassigned"/>
33+
34+
<!-- Assign the date attribute to the new group -->
35+
<actionGroup ref="AssignAttributeToGroupActionGroup" stepKey="assignAttributeToGroup">
36+
<argument name="group" value="Design"/>
37+
<argument name="attribute" value="$$attribute.attribute_code$$"/>
38+
</actionGroup>
39+
40+
<actionGroup ref="SaveAttributeSetActionGroup" stepKey="saveAttributeSet"/>
41+
42+
<!-- Create a simple product for testing -->
43+
<createData entity="SimpleProduct" stepKey="createSimpleProduct"/>
44+
</before>
45+
46+
<after>
47+
<deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/>
48+
<deleteData createDataKey="attribute" stepKey="deleteAttribute"/>
49+
<actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/>
50+
</after>
51+
52+
<!-- Navigate to the product edit page -->
53+
<actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndex"/>
54+
<actionGroup ref="FilterProductGridBySkuActionGroup" stepKey="filterProductGridBySku">
55+
<argument name="product" value="$$createSimpleProduct$$"/>
56+
</actionGroup>
57+
<click selector="{{AdminProductGridSection.productRowBySku($$createSimpleProduct.sku$$)}}" stepKey="openProduct"/>
58+
<waitForPageLoad stepKey="waitForProductToLoad"/>
59+
60+
<!-- Verify the date attribute is visible in the custom group -->
61+
<conditionalClick selector="{{AdminProductGridSection.productCollapsibleColumns('closed','Design')}}" dependentSelector="{{AdminProductGridSection.productCollapsibleColumns('closed','Design')}}" visible="true" stepKey="expandDesign"/>
62+
63+
<!-- Click on the date input field to trigger the calendar -->
64+
<click selector="{{AdminProductFormSection.datePickerCalendar('1')}}" stepKey="clickDatePickerTrigger"/>
65+
66+
<!-- Assert that the calendar is fully visible on screen -->
67+
<actionGroup ref="AssertElementVisibleOnScreenActionGroup" stepKey="assertCalendarVisibleOnScreen">
68+
<argument name="selector" value=".ui-datepicker"/>
69+
</actionGroup>
70+
</test>
71+
</tests>

app/code/Magento/Paypal/Controller/Ipn/Index.php

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
<?php
22
/**
3-
*
4-
* Copyright © Magento, Inc. All rights reserved.
5-
* See COPYING.txt for license details.
3+
* Copyright 2011 Adobe
4+
* All Rights Reserved.
65
*/
76
declare(strict_types=1);
87

@@ -13,6 +12,7 @@
1312
use Magento\Framework\App\Request\InvalidRequestException;
1413
use Magento\Framework\App\RequestInterface;
1514
use Magento\Framework\Exception\RemoteServiceUnavailableException;
15+
use Magento\Paypal\Model\Exception\UnknownIpnException;
1616
use Magento\Sales\Model\OrderFactory;
1717

1818
/**
@@ -86,19 +86,23 @@ public function execute()
8686
try {
8787
$data = $this->getRequest()->getPostValue();
8888
$this->_ipnFactory->create(['data' => $data])->processIpnRequest();
89-
$incrementId = $this->getRequest()->getPostValue()['invoice'];
90-
$this->_eventManager->dispatch(
91-
'paypal_checkout_success',
92-
[
93-
'order' => $this->orderFactory->create()->loadByIncrementId($incrementId)
94-
]
95-
);
89+
$incrementId = $data['invoice'] ?? null;
90+
if ($incrementId) {
91+
$this->_eventManager->dispatch(
92+
'paypal_checkout_success',
93+
[
94+
'order' => $this->orderFactory->create()->loadByIncrementId($incrementId)
95+
]
96+
);
97+
}
9698
} catch (RemoteServiceUnavailableException $e) {
9799
$this->_logger->critical($e);
98100
$this->getResponse()->setStatusHeader(503, '1.1', 'Service Unavailable')->sendResponse();
99101
/** @todo eliminate usage of exit statement */
100102
// phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage
101103
exit;
104+
} catch (UnknownIpnException $e) {
105+
$this->_logger->warning($e);
102106
} catch (\Exception $e) {
103107
$this->_logger->critical($e);
104108
$this->getResponse()->setHttpResponseCode(500);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Paypal\Model\Exception;
9+
10+
use Magento\Framework\Exception\LocalizedException;
11+
12+
/**
13+
* Exception for unknown or invalid PayPal IPN requests
14+
*/
15+
class UnknownIpnException extends LocalizedException
16+
{
17+
}

app/code/Magento/Paypal/Model/Ipn.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2011 Adobe
4+
* All Rights Reserved.
55
*/
66

77
namespace Magento\Paypal\Model;
88

99
use Exception;
1010
use Magento\Framework\App\ObjectManager;
1111
use Magento\Framework\Exception\LocalizedException;
12+
use Magento\Paypal\Model\Exception\UnknownIpnException;
1213
use Magento\Sales\Model\Order;
1314
use Magento\Sales\Model\Order\Email\Sender\CreditmemoSender;
1415
use Magento\Sales\Model\Order\Email\Sender\OrderSender;
@@ -151,6 +152,11 @@ protected function _getConfig()
151152
protected function _getOrder()
152153
{
153154
$incrementId = $this->getRequestData('invoice');
155+
if (!$incrementId) {
156+
throw new UnknownIpnException(
157+
__('Missing invoice field in IPN request.')
158+
);
159+
}
154160
$this->_order = $this->_orderFactory->create()->loadByIncrementId($incrementId);
155161
if (!$this->_order->getId()) {
156162
// phpcs:ignore Magento2.Exceptions.DirectThrow

app/code/Magento/Paypal/Test/Unit/Controller/Ipn/IndexTest.php

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2011 Adobe
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

@@ -20,6 +20,7 @@
2020
use PHPUnit\Framework\MockObject\MockObject;
2121
use PHPUnit\Framework\TestCase;
2222
use Psr\Log\LoggerInterface;
23+
use Magento\Paypal\Model\Exception\UnknownIpnException;
2324

2425
/**
2526
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -86,6 +87,15 @@ public function testIndexActionException()
8687
$this->model->execute();
8788
}
8889

90+
public function testIndexActionUnknownIpnException()
91+
{
92+
$this->requestMock->expects($this->once())->method('isPost')->willReturn(true);
93+
$exception = new UnknownIpnException(__('Missing invoice field in IPN request.'));
94+
$this->requestMock->expects($this->once())->method('getPostValue')->willThrowException($exception);
95+
$this->loggerMock->expects($this->once())->method('warning')->with($this->identicalTo($exception));
96+
$this->model->execute();
97+
}
98+
8999
public function testIndexAction()
90100
{
91101
$this->requestMock->expects($this->once())->method('isPost')->willReturn(true);
@@ -94,7 +104,7 @@ public function testIndexAction()
94104
'invoice' => $incrementId,
95105
'other' => 'other data'
96106
];
97-
$this->requestMock->expects($this->exactly(2))->method('getPostValue')->willReturn($data);
107+
$this->requestMock->expects($this->once())->method('getPostValue')->willReturn($data);
98108
$ipnMock = $this->getMockForAbstractClass(IpnInterface::class);
99109
$this->ipnFactoryMock->expects($this->once())
100110
->method('create')

app/code/Magento/Paypal/Test/Unit/Model/IpnTest.php

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2015 Adobe
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

@@ -21,7 +21,15 @@
2121
use Magento\Sales\Model\OrderFactory;
2222
use PHPUnit\Framework\MockObject\MockObject;
2323
use PHPUnit\Framework\TestCase;
24+
use Magento\Paypal\Model\Exception\UnknownIpnException;
25+
use Magento\Sales\Model\Order\Email\Sender\CreditmemoSender;
26+
use Magento\Sales\Model\Order\Email\Sender\OrderSender;
27+
use Magento\Sales\Model\OrderMutexInterface;
28+
use \Psr\Log\LoggerInterface;
2429

30+
/**
31+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
32+
*/
2533
class IpnTest extends TestCase
2634
{
2735
/**
@@ -115,7 +123,7 @@ protected function setUp(): void
115123
'curlFactory' => $this->curlFactory,
116124
'orderFactory' => $this->_orderMock,
117125
'paypalInfo' => $this->_paypalInfo,
118-
'data' => ['payment_status' => 'Pending', 'pending_reason' => 'authorization']
126+
'data' => ['invoice' => '00000001', 'payment_status' => 'Pending', 'pending_reason' => 'authorization']
119127
]
120128
);
121129
}
@@ -157,6 +165,36 @@ public function testPaymentReviewRegisterPaymentAuthorization()
157165
$this->_ipn->processIpnRequest();
158166
}
159167

168+
public function testProcessIpnRequestThrowsException()
169+
{
170+
$creditmemoSenderMock = $this->getMockBuilder(CreditmemoSender::class)
171+
->disableOriginalConstructor()
172+
->getMock();
173+
$orderSenderMock = $this->getMockBuilder(OrderSender::class)
174+
->disableOriginalConstructor()
175+
->getMock();
176+
$orderMutexMock = $this->getMockForAbstractClass(orderMutexInterface::class);
177+
$loggerMock = $this->getMockForAbstractClass(LoggerInterface::class);
178+
$data = [
179+
'payment_status' => 'Pending',
180+
'pending_reason' => 'fraud',
181+
'fraud_management_pending_filters_1' => 'Maximum Transaction Amount',
182+
];
183+
$this->_ipn = new Ipn(
184+
$this->configFactory,
185+
$loggerMock,
186+
$this->curlFactory,
187+
$this->_orderMock,
188+
$this->_paypalInfo,
189+
$orderSenderMock,
190+
$creditmemoSenderMock,
191+
$orderMutexMock,
192+
$data
193+
);
194+
$this->expectException(UnknownIpnException::class);
195+
$this->_ipn->processIpnRequest();
196+
}
197+
160198
public function testPaymentReviewRegisterPaymentFraud()
161199
{
162200
$paymentMock = $this->createPartialMock(
@@ -196,6 +234,7 @@ public function testPaymentReviewRegisterPaymentFraud()
196234
'orderFactory' => $this->_orderMock,
197235
'paypalInfo' => $this->_paypalInfo,
198236
'data' => [
237+
'invoice' => '00000001',
199238
'payment_status' => 'Pending',
200239
'pending_reason' => 'fraud',
201240
'fraud_management_pending_filters_1' => 'Maximum Transaction Amount',
@@ -241,6 +280,7 @@ public function testRegisterPaymentDenial()
241280
'orderFactory' => $this->_orderMock,
242281
'paypalInfo' => $this->_paypalInfo,
243282
'data' => [
283+
'invoice' => '00000001',
244284
'payment_status' => 'Denied',
245285
]
246286
]

0 commit comments

Comments
 (0)