<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ declare(strict_types=1); namespace Magento\Quote\Model; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\ProductRepository; use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Customer\Api\Data\CustomerInterface; use Magento\Customer\Api\Data\CustomerInterfaceFactory; use Magento\Customer\Model\Data\Customer; use Magento\Framework\Exception\LocalizedException; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\ObjectManager; use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Quote\Api\CartRepositoryInterface; use Magento\Framework\Api\ExtensibleDataInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class QuoteTest extends \PHPUnit\Framework\TestCase { /** * @var ObjectManager */ private $objectManager; /** * @inheritdoc */ protected function setUp() { $this->objectManager = Bootstrap::getObjectManager(); } /** * @param ExtensibleDataInterface $entity * @return array */ private function convertToArray(ExtensibleDataInterface $entity): array { return $this->objectManager ->create(\Magento\Framework\Api\ExtensibleDataObjectConverter::class) ->toFlatArray($entity); } /** * @magentoDataFixture Magento/Catalog/_files/product_virtual.php * @magentoDataFixture Magento/Sales/_files/quote.php * @return void */ public function testCollectTotalsWithVirtual(): void { $quote = $this->objectManager->create(Quote::class); $quote->load('test01', 'reserved_order_id'); $productRepository = $this->objectManager->create( ProductRepositoryInterface::class ); $product = $productRepository->get('virtual-product', false, null, true); $quote->addProduct($product); $quote->collectTotals(); $this->assertEquals(2, $quote->getItemsQty()); $this->assertEquals(1, $quote->getVirtualItemsQty()); $this->assertEquals(20, $quote->getGrandTotal()); $this->assertEquals(20, $quote->getBaseGrandTotal()); } /** * @return void */ public function testSetCustomerData(): void { /** @var Quote $quote */ $quote = $this->objectManager->create(Quote::class); /** @var CustomerInterfaceFactory $customerFactory */ $customerFactory = $this->objectManager->create( CustomerInterfaceFactory::class ); /** @var \Magento\Framework\Api\DataObjectHelper $dataObjectHelper */ $dataObjectHelper = $this->objectManager->create(\Magento\Framework\Api\DataObjectHelper::class); $expected = $this->_getCustomerDataArray(); $customer = $customerFactory->create(); $dataObjectHelper->populateWithArray( $customer, $expected, \Magento\Customer\Api\Data\CustomerInterface::class ); $this->assertEquals($expected, $this->convertToArray($customer)); $quote->setCustomer($customer); $customer = $quote->getCustomer(); $this->assertEquals($expected, $this->convertToArray($customer)); $this->assertEquals('qa@example.com', $quote->getCustomerEmail()); } /** * @return void */ public function testUpdateCustomerData(): void { /** @var Quote $quote */ $quote = $this->objectManager->create(Quote::class); $customerFactory = $this->objectManager->create( CustomerInterfaceFactory::class ); /** @var \Magento\Framework\Api\DataObjectHelper $dataObjectHelper */ $dataObjectHelper = $this->objectManager->create(\Magento\Framework\Api\DataObjectHelper::class); $expected = $this->_getCustomerDataArray(); //For save in repository $expected = $this->removeIdFromCustomerData($expected); $customerDataSet = $customerFactory->create(); $dataObjectHelper->populateWithArray( $customerDataSet, $expected, \Magento\Customer\Api\Data\CustomerInterface::class ); $this->assertEquals($expected, $this->convertToArray($customerDataSet)); /** * @var \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository */ $customerRepository = $this->objectManager ->create(\Magento\Customer\Api\CustomerRepositoryInterface::class); $customerRepository->save($customerDataSet); $quote->setCustomer($customerDataSet); $expected = $this->_getCustomerDataArray(); $expected = $this->changeEmailInCustomerData('test@example.com', $expected); $customerDataUpdated = $customerFactory->create(); $dataObjectHelper->populateWithArray( $customerDataUpdated, $expected, \Magento\Customer\Api\Data\CustomerInterface::class ); $quote->updateCustomerData($customerDataUpdated); $customer = $quote->getCustomer(); $expected = $this->changeEmailInCustomerData('test@example.com', $expected); $actual = $this->convertToArray($customer); foreach ($expected as $item) { $this->assertContains($item, $actual); } $this->assertEquals('test@example.com', $quote->getCustomerEmail()); } /** * Customer data is set to quote (which contains valid group ID). * * @return void */ public function testGetCustomerGroupFromCustomer(): void { /** Preconditions */ /** @var CustomerInterfaceFactory $customerFactory */ $customerFactory = $this->objectManager->create( CustomerInterfaceFactory::class ); $customerGroupId = 3; $customerData = $customerFactory->create()->setId(1)->setGroupId($customerGroupId); /** @var Quote $quote */ $quote = $this->objectManager->create(Quote::class); $quote->setCustomer($customerData); $quote->unsetData('customer_group_id'); /** Execute SUT */ $this->assertEquals($customerGroupId, $quote->getCustomerGroupId(), "Customer group ID is invalid"); } /** * @magentoDataFixture Magento/Customer/_files/customer_group.php * @return void */ public function testGetCustomerTaxClassId(): void { /** * Preconditions: create quote and assign ID of customer group created in fixture to it. */ $fixtureGroupCode = 'custom_group'; $fixtureTaxClassId = 3; /** @var \Magento\Customer\Model\Group $group */ $group = $this->objectManager->create(\Magento\Customer\Model\Group::class); $fixtureGroupId = $group->load($fixtureGroupCode, 'customer_group_code')->getId(); /** @var Quote $quote */ $quote = $this->objectManager->create(Quote::class); $quote->setCustomerGroupId($fixtureGroupId); /** Execute SUT */ $this->assertEquals($fixtureTaxClassId, $quote->getCustomerTaxClassId(), 'Customer tax class ID is invalid.'); } /** * Billing and shipping address arguments are not passed, customer has default billing and shipping addresses. * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php * @return void */ public function testAssignCustomerWithAddressChangeAddressesNotSpecified(): void { /** Preconditions: * Customer with two addresses created * First address is default billing, second is default shipping. */ /** @var Quote $quote */ $quote = $this->objectManager->create(Quote::class); $customerData = $this->_prepareQuoteForTestAssignCustomerWithAddressChange($quote); /** Execute SUT */ $quote->assignCustomerWithAddressChange($customerData); /** Check if SUT caused expected effects */ $fixtureCustomerId = 1; $this->assertEquals($fixtureCustomerId, $quote->getCustomerId(), 'Customer ID in quote is invalid.'); $expectedBillingAddressData = [ 'street' => 'Green str, 67', 'telephone' => 3468676, 'postcode' => 75477, 'country_id' => 'US', 'city' => 'CityM', 'lastname' => 'Smith', 'firstname' => 'John', 'customer_id' => 1, 'customer_address_id' => 1, 'region_id' => 1 ]; $billingAddress = $quote->getBillingAddress(); foreach ($expectedBillingAddressData as $field => $value) { $this->assertEquals( $value, $billingAddress->getData($field), "'{$field}' value in quote billing address is invalid." ); } $expectedShippingAddressData = [ 'customer_address_id' => 2, 'telephone' => 3234676, 'postcode' => 47676, 'country_id' => 'US', 'city' => 'CityX', 'street' => 'Black str, 48', 'lastname' => 'Smith', 'firstname' => 'John', 'customer_id' => 1, 'region_id' => 1 ]; $shippingAddress = $quote->getShippingAddress(); foreach ($expectedShippingAddressData as $field => $value) { $this->assertEquals( $value, $shippingAddress->getData($field), "'{$field}' value in quote shipping address is invalid." ); } } /** * Billing and shipping address arguments are passed, customer has default billing and shipping addresses. * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php * @return void */ public function testAssignCustomerWithAddressChange(): void { /** Preconditions: * Customer with two addresses created * First address is default billing, second is default shipping. */ /** @var Quote $quote */ $quote = $this->objectManager->create(Quote::class); $customerData = $this->_prepareQuoteForTestAssignCustomerWithAddressChange($quote); /** @var \Magento\Quote\Model\Quote\Address $quoteBillingAddress */ $expectedBillingAddressData = [ 'street' => 'Billing str, 67', 'telephone' => 16546757, 'postcode' => 2425457, 'country_id' => 'US', 'city' => 'CityBilling', 'lastname' => 'LastBilling', 'firstname' => 'FirstBilling', 'region_id' => 1 ]; $quoteBillingAddress = $this->objectManager->create(\Magento\Quote\Model\Quote\Address::class); $quoteBillingAddress->setData($expectedBillingAddressData); $expectedShippingAddressData = [ 'telephone' => 787878787, 'postcode' => 117785, 'country_id' => 'US', 'city' => 'CityShipping', 'street' => 'Shipping str, 48', 'lastname' => 'LastShipping', 'firstname' => 'FirstShipping', 'region_id' => 1 ]; $quoteShippingAddress = $this->objectManager->create(\Magento\Quote\Model\Quote\Address::class); $quoteShippingAddress->setData($expectedShippingAddressData); /** Execute SUT */ $quote->assignCustomerWithAddressChange($customerData, $quoteBillingAddress, $quoteShippingAddress); /** Check if SUT caused expected effects */ $fixtureCustomerId = 1; $this->assertEquals($fixtureCustomerId, $quote->getCustomerId(), 'Customer ID in quote is invalid.'); $billingAddress = $quote->getBillingAddress(); foreach ($expectedBillingAddressData as $field => $value) { $this->assertEquals( $value, $billingAddress->getData($field), "'{$field}' value in quote billing address is invalid." ); } $shippingAddress = $quote->getShippingAddress(); foreach ($expectedShippingAddressData as $field => $value) { $this->assertEquals( $value, $shippingAddress->getData($field), "'{$field}' value in quote shipping address is invalid." ); } } /** * @magentoDataFixture Magento/Catalog/_files/product_simple_duplicated.php * @return void */ public function testAddProductUpdateItem(): void { /** @var Quote $quote */ $quote = $this->objectManager->create(Quote::class); $quote->load('test01', 'reserved_order_id'); $productStockQty = 100; $productRepository = $this->objectManager->create( ProductRepositoryInterface::class ); $product = $productRepository->get('simple-1', false, null, true); $quote->addProduct($product, 50); $quote->setTotalsCollectedFlag(false)->collectTotals(); $this->assertEquals(50, $quote->getItemsQty()); $quote->addProduct($product, 50); $quote->setTotalsCollectedFlag(false)->collectTotals(); $this->assertEquals(100, $quote->getItemsQty()); $params = [ 'related_product' => '', 'product' => $product->getId(), 'qty' => 1, 'id' => 0 ]; $updateParams = new \Magento\Framework\DataObject($params); $quote->updateItem($updateParams['id'], $updateParams); $quote->setTotalsCollectedFlag(false)->collectTotals(); $this->assertEquals(1, $quote->getItemsQty()); $this->expectException(\Magento\Framework\Exception\LocalizedException::class); // TODO: fix test or implementation as described in https://github.com/magento-engcom/msi/issues/1037 // $this->expectExceptionMessage('The requested qty is not available'); $updateParams['qty'] = $productStockQty + 1; $quote->updateItem($updateParams['id'], $updateParams); } /** * Prepare quote for testing assignCustomerWithAddressChange method. * * Customer with two addresses created. First address is default billing, second is default shipping. * * @param Quote $quote * @return CustomerInterface */ protected function _prepareQuoteForTestAssignCustomerWithAddressChange(Quote $quote): CustomerInterface { $customerRepository = $this->objectManager->create( CustomerRepositoryInterface::class ); $fixtureCustomerId = 1; /** @var \Magento\Customer\Model\Customer $customer */ $customer = $this->objectManager->create(\Magento\Customer\Model\Customer::class); $fixtureSecondAddressId = 2; $customer->load($fixtureCustomerId)->setDefaultShipping($fixtureSecondAddressId)->save(); $customerData = $customerRepository->getById($fixtureCustomerId); $this->assertEmpty( $quote->getBillingAddress()->getId(), "Precondition failed: billing address should be empty." ); $this->assertEmpty( $quote->getShippingAddress()->getId(), "Precondition failed: shipping address should be empty." ); return $customerData; } /** * @param string $email * @param array $customerData * @return array */ protected function changeEmailInCustomerData(string $email, array $customerData): array { $customerData[\Magento\Customer\Model\Data\Customer::EMAIL] = $email; return $customerData; } /** * @param array $customerData * @return array */ protected function removeIdFromCustomerData(array $customerData): array { unset($customerData[\Magento\Customer\Model\Data\Customer::ID]); return $customerData; } /** * @return array */ protected function _getCustomerDataArray(): array { return [ Customer::CONFIRMATION => 'test', Customer::CREATED_AT => '2/3/2014', Customer::CREATED_IN => 'Default', Customer::DEFAULT_BILLING => 'test', Customer::DEFAULT_SHIPPING => 'test', Customer::DOB => '2014-02-03 00:00:00', Customer::EMAIL => 'qa@example.com', Customer::FIRSTNAME => 'Joe', Customer::GENDER => 0, Customer::GROUP_ID => \Magento\Customer\Model\GroupManagement::NOT_LOGGED_IN_ID, Customer::ID => 1, Customer::LASTNAME => 'Dou', Customer::MIDDLENAME => 'Ivan', Customer::PREFIX => 'Dr.', Customer::STORE_ID => 1, Customer::SUFFIX => 'Jr.', Customer::TAXVAT => 1, Customer::WEBSITE_ID => 1 ]; } /** * Test to verify that reserved_order_id will be changed if it already in used * * @magentoDataFixture Magento/Sales/_files/order.php * @magentoDataFixture Magento/Quote/_files/empty_quote.php * @return void */ public function testReserveOrderId(): void { /** @var Quote $quote */ $quote = $this->objectManager->create(Quote::class); $quote->load('reserved_order_id', 'reserved_order_id'); $quote->reserveOrderId(); $this->assertEquals('reserved_order_id', $quote->getReservedOrderId()); $quote->setReservedOrderId('100000001'); $quote->reserveOrderId(); $this->assertNotEquals('100000001', $quote->getReservedOrderId()); } /** * Test to verify that disabled product cannot be added to cart * @magentoDataFixture Magento/Quote/_files/is_not_salable_product.php * @return void */ public function testAddedProductToQuoteIsSalable(): void { $productId = 99; /** @var ProductRepository $productRepository */ $productRepository = $this->objectManager->get(ProductRepository::class); /** @var Quote $quote */ $product = $productRepository->getById($productId, false, null, true); $this->expectException(LocalizedException::class); $this->expectExceptionMessage('Product that you are trying to add is not available.'); $quote = $this->objectManager->create(Quote::class); $quote->addProduct($product); } /** * @magentoDataFixture Magento/Sales/_files/quote.php * @magentoDataFixture Magento/Catalog/_files/product_simple.php * @return void */ public function testGetItemById(): void { $quote = $this->objectManager->create(Quote::class); $quote->load('test01', 'reserved_order_id'); $quoteItem = $this->objectManager->create(\Magento\Quote\Model\Quote\Item::class); $productRepository = $this->objectManager->create(ProductRepositoryInterface::class); $product = $productRepository->get('simple'); $quoteItem->setProduct($product); $quote->addItem($quoteItem); $quote->save(); $this->assertInstanceOf(\Magento\Quote\Model\Quote\Item::class, $quote->getItemById($quoteItem->getId())); $this->assertEquals($quoteItem->getId(), $quote->getItemById($quoteItem->getId())->getId()); } /** * Tests of quotes merging. * * @param int|null $guestItemGiftMessageId * @param int|null $customerItemGiftMessageId * @param int|null $guestOrderGiftMessageId * @param int|null $customerOrderGiftMessageId * @param int|null $expectedItemGiftMessageId * @param int|null $expectedOrderGiftMessageId * * @magentoDataFixture Magento/Sales/_files/quote.php * @dataProvider giftMessageDataProvider * @throws LocalizedException * @return void */ public function testMerge( $guestItemGiftMessageId, $customerItemGiftMessageId, $guestOrderGiftMessageId, $customerOrderGiftMessageId, $expectedItemGiftMessageId, $expectedOrderGiftMessageId ): void { $productRepository = $this->objectManager->create(ProductRepositoryInterface::class); $product = $productRepository->get('simple', false, null, true); /** @var Quote $quote */ $guestQuote = $this->getQuote('test01'); $guestQuote->setGiftMessageId($guestOrderGiftMessageId); /** @var Quote $customerQuote */ $customerQuote = $this->objectManager->create(Quote::class); $customerQuote->setReservedOrderId('test02') ->setStoreId($guestQuote->getStoreId()) ->addProduct($product); $customerQuote->setGiftMessageId($customerOrderGiftMessageId); $guestItem = $guestQuote->getItemByProduct($product); $guestItem->setGiftMessageId($guestItemGiftMessageId); $customerItem = $customerQuote->getItemByProduct($product); $customerItem->setGiftMessageId($customerItemGiftMessageId); $customerQuote->merge($guestQuote); $mergedItemItem = $customerQuote->getItemByProduct($product); self::assertEquals($expectedOrderGiftMessageId, $customerQuote->getGiftMessageId()); self::assertEquals($expectedItemGiftMessageId, $mergedItemItem->getGiftMessageId()); } /** * Provides order- and item-level gift message Id. * * @return array */ public function giftMessageDataProvider(): array { return [ [ 'guestItemId' => null, 'customerItemId' => 1, 'guestOrderId' => null, 'customerOrderId' => 11, 'expectedItemId' => 1, 'expectedOrderId' => 11 ], [ 'guestItemId' => 1, 'customerItemId' => 2, 'guestOrderId' => 11, 'customerOrderId' => 22, 'expectedItemId' => 1, 'expectedOrderId' => 11 ] ]; } /** * Gets quote by reserved order id. * * @param string $reservedOrderId * @return Quote */ private function getQuote(string $reservedOrderId): Quote { /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); $searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId) ->create(); /** @var CartRepositoryInterface $quoteRepository */ $quoteRepository = $this->objectManager->get(CartRepositoryInterface::class); $items = $quoteRepository->getList($searchCriteria)->getItems(); return array_pop($items); } }