Skip to content

Commit 34d5647

Browse files
Merge branch 'ACQE-8573' into ACQE-functional-deployment-v4-1
2 parents fd7190e + 739c141 commit 34d5647

File tree

1 file changed

+283
-0
lines changed

1 file changed

+283
-0
lines changed
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Checkout\Model;
10+
11+
use Magento\Checkout\Model\Session as CheckoutSession;
12+
use Magento\Checkout\Test\Fixture\SetBillingAddress as SetBillingAddressFixture;
13+
use Magento\Checkout\Test\Fixture\SetGuestEmail as SetGuestEmailFixture;
14+
use Magento\Checkout\Test\Fixture\SetPaymentMethod as SetPaymentMethodFixture;
15+
use Magento\Downloadable\Model\Link\Purchased\Item;
16+
use Magento\Downloadable\Model\ResourceModel\Link\Purchased\Item\CollectionFactory as DownloadableLinkItemCollection;
17+
use Magento\Downloadable\Test\Fixture\DownloadableProduct as DownloadableProductFixture;
18+
use Magento\Framework\Exception\CouldNotSaveException;
19+
use Magento\Framework\Exception\InputException;
20+
use Magento\Framework\Exception\LocalizedException;
21+
use Magento\Framework\Exception\NoSuchEntityException;
22+
use Magento\Framework\ObjectManagerInterface;
23+
use Magento\Quote\Api\GuestCartManagementInterface;
24+
use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface;
25+
use Magento\Quote\Test\Fixture\AddProductToCart as AddProductToCartFixture;
26+
use Magento\Quote\Test\Fixture\GuestCart;
27+
use Magento\Sales\Api\Data\OrderInterface;
28+
use Magento\Sales\Api\InvoiceOrderInterface;
29+
use Magento\Sales\Model\OrderRepository;
30+
use Magento\TestFramework\Fixture\DataFixture;
31+
use Magento\TestFramework\Fixture\DataFixtureStorage;
32+
use Magento\TestFramework\Fixture\DataFixtureStorageManager;
33+
use Magento\TestFramework\Helper\Bootstrap;
34+
use Magento\TestFramework\Mail\Template\TransportBuilderMock;
35+
use PHPUnit\Framework\Constraint\StringContains;
36+
use PHPUnit\Framework\TestCase;
37+
38+
/**
39+
* Integration test for purchasing downloadable products through checkout
40+
*
41+
* @magentoDbIsolation disabled
42+
* @magentoAppIsolation enabled
43+
* @magentoAppArea frontend
44+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
45+
*/
46+
class DownloadableProductPurchaseTest extends TestCase
47+
{
48+
/**
49+
* @var TransportBuilderMock
50+
*/
51+
private TransportBuilderMock $transportBuilder;
52+
53+
/**
54+
* @var DataFixtureStorage
55+
*/
56+
private DataFixtureStorage $fixtures;
57+
58+
/**
59+
* @var DownloadableLinkItemCollection
60+
*/
61+
private DownloadableLinkItemCollection $linkCollection;
62+
63+
/**
64+
* @var InvoiceOrderInterface
65+
*/
66+
private InvoiceOrderInterface $invoiceOrder;
67+
68+
/**
69+
* @var GuestCartManagementInterface
70+
*/
71+
private GuestCartManagementInterface $cartManagement;
72+
73+
/**
74+
* @var OrderRepository
75+
*/
76+
private OrderRepository $orderRepository;
77+
78+
/**
79+
* @var QuoteIdToMaskedQuoteIdInterface
80+
*/
81+
private QuoteIdToMaskedQuoteIdInterface $quoteIdToMaskedId;
82+
83+
/**
84+
* @var ObjectManagerInterface
85+
*/
86+
private ObjectManagerInterface $objectManager;
87+
88+
/**
89+
* @return void
90+
* @throws LocalizedException
91+
*/
92+
protected function setUp(): void
93+
{
94+
parent::setUp();
95+
$this->objectManager = Bootstrap::getObjectManager();
96+
$this->fixtures = $this->objectManager->get(DataFixtureStorageManager::class)->getStorage();
97+
$this->transportBuilder = $this->objectManager->get(TransportBuilderMock::class);
98+
$this->linkCollection = $this->objectManager
99+
->get(DownloadableLinkItemCollection::class);
100+
$this->invoiceOrder = $this->objectManager->get(InvoiceOrderInterface::class);
101+
$this->cartManagement = $this->objectManager->get(GuestCartManagementInterface::class);
102+
$this->orderRepository = $this->objectManager->get(OrderRepository::class);
103+
$this->quoteIdToMaskedId = $this->objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
104+
$this->transportBuilder->clean();
105+
}
106+
107+
/**
108+
* Clean up after test
109+
*
110+
* @return void
111+
*/
112+
protected function tearDown(): void
113+
{
114+
parent::tearDown();
115+
$this->transportBuilder->clean();
116+
}
117+
118+
/**
119+
* Test that after placing an order for a downloadable product, the order email contains the correct links
120+
*
121+
* @return void
122+
* @throws CouldNotSaveException
123+
* @throws InputException
124+
* @throws NoSuchEntityException
125+
*/
126+
#[
127+
DataFixture(DownloadableProductFixture::class, [
128+
'price' => 100,
129+
'type_id' => 'downloadable',
130+
'links_purchased_separately' => 0,
131+
'downloadable_product_links' => [
132+
[
133+
'title' => 'Example 1',
134+
'price' => 0.00,
135+
'link_type' => 'url'
136+
],
137+
[
138+
'title' => 'Example 2',
139+
'price' => 0.00,
140+
'link_type' => 'url'
141+
]
142+
]
143+
], as: 'product'),
144+
DataFixture(GuestCart::class, [], as: 'quote'),
145+
DataFixture(SetGuestEmailFixture::class, ['cart_id' => '$quote.id$']),
146+
DataFixture(
147+
AddProductToCartFixture::class,
148+
[
149+
'cart_id' => '$quote.id$',
150+
'product_id' => '$product.id$'
151+
]
152+
),
153+
DataFixture(SetBillingAddressFixture::class, ['cart_id' => '$quote.id$'], as: 'billingAddress'),
154+
DataFixture(SetPaymentMethodFixture::class, ['cart_id' => '$quote.id$']),
155+
]
156+
public function testDownloadableProductLinkAfterOrderPlaced(): void
157+
{
158+
$quoteId = (int) $this->fixtures->get('quote')->getEntityId();
159+
$checkoutSession = $this->objectManager->get(CheckoutSession::class);
160+
$checkoutSession->setQuoteId($quoteId);
161+
$order = $this->placeOrderWithDownloadableProduct();
162+
$this->verifyOrderConfirmationEmailSent($order);
163+
$this->verifyDownloadableLinksStatus($order);
164+
}
165+
166+
/**
167+
* Verifies that order confirmation email is sent and contains expected content
168+
*
169+
* @param OrderInterface $order The order to verify email for
170+
* @throws NoSuchEntityException
171+
*/
172+
private function verifyOrderConfirmationEmailSent(OrderInterface $order): void
173+
{
174+
$message = $this->transportBuilder->getSentMessage();
175+
$this->assertNotNull($message, 'Order confirmation email should be sent for downloadable product order');
176+
$assert = $this->logicalAnd(
177+
new StringContains($order->getBillingAddress()->getName()),
178+
new StringContains(
179+
'Thank you for your order from ' . $order->getStore()->getFrontendName()
180+
),
181+
new StringContains(
182+
"Your Order <span class=\"no-link\">#{$order->getIncrementId()}</span>"
183+
)
184+
);
185+
$emailBody = quoted_printable_decode($message->getBody()->bodyToString());
186+
$this->assertThat($emailBody, $assert);
187+
$this->assertStringContainsString('download', $emailBody);
188+
$this->assertStringContainsString('Downloadable Links', $emailBody);
189+
$this->assertStringContainsString('/downloadable/download/link/', $emailBody);
190+
}
191+
192+
/**
193+
* Verifies downloadable link status for guest order items
194+
*
195+
* @param OrderInterface $order
196+
*/
197+
private function verifyDownloadableLinksStatus(OrderInterface $order): void
198+
{
199+
foreach ($order->getItems() as $item) {
200+
if ($item->getData('item_id')) {
201+
$this->checkDownloadableLinkStatusForGuestOrder(
202+
(int)$item->getData('item_id'),
203+
(int)$order->getData('entity_id')
204+
);
205+
}
206+
}
207+
}
208+
209+
/**
210+
* Check downloadable link status before and after invoice for guest order
211+
*
212+
* @param int $orderItemId
213+
* @param int $orderId
214+
* @return void
215+
*/
216+
private function checkDownloadableLinkStatusForGuestOrder(int $orderItemId, int $orderId): void
217+
{
218+
$purchasedLinks = $this->getDownloadablePurchasedLinks($orderItemId);
219+
foreach ($purchasedLinks as $link) {
220+
$this->assertEquals(
221+
Item::LINK_STATUS_PENDING,
222+
$link->getStatus(),
223+
'Download link should be pending before invoice for guest order'
224+
);
225+
}
226+
$this->createInvoiceForOrder($orderId);
227+
$linksAfterInvoice = $this->getDownloadablePurchasedLinks($orderItemId);
228+
foreach ($linksAfterInvoice as $link) {
229+
$this->assertEquals(
230+
Item::LINK_STATUS_AVAILABLE,
231+
$link->getStatus(),
232+
'Download link should be available after invoice for guest order'
233+
);
234+
}
235+
}
236+
237+
/**
238+
* Retrieve purchased downloadable links by order item id
239+
*
240+
* @param int $orderItemId
241+
* @return array
242+
*/
243+
private function getDownloadablePurchasedLinks(int $orderItemId): array
244+
{
245+
/** @var DownloadableLinkItemCollection $collection */
246+
$collection = $this->linkCollection->create()
247+
->addFieldToFilter('order_item_id', $orderItemId);
248+
249+
return $collection->getItems();
250+
}
251+
252+
/**
253+
* Create invoice for order
254+
*
255+
* @param int $orderId
256+
* @return void
257+
*/
258+
private function createInvoiceForOrder(int $orderId): void
259+
{
260+
$this->invoiceOrder->execute(
261+
$orderId
262+
);
263+
}
264+
265+
/**
266+
* Place order with downloadable product in quote
267+
*
268+
* @return OrderInterface
269+
* @throws CouldNotSaveException
270+
* @throws InputException
271+
* @throws NoSuchEntityException
272+
*/
273+
private function placeOrderWithDownloadableProduct(): OrderInterface
274+
{
275+
$quote = $this->fixtures->get('quote');
276+
$maskedId = $this->quoteIdToMaskedId->execute(
277+
(int) $quote->getEntityId()
278+
);
279+
$orderId = $this->cartManagement->placeOrder($maskedId);
280+
281+
return $this->orderRepository->get($orderId);
282+
}
283+
}

0 commit comments

Comments
 (0)