diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminSlideOutDialogSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminSlideOutDialogSection.xml index a2645c9cbf96d..a4270ae9ce6c9 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminSlideOutDialogSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminSlideOutDialogSection.xml @@ -9,7 +9,7 @@
- + diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 7db77faaafcaf..a6394ce293a6c 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -1570,26 +1570,9 @@ public function addAttributeToFilter($attribute, $condition = null, $joinType = $this->_allIdsCache = null; if (is_string($attribute) && $attribute == 'is_saleable') { - $columns = $this->getSelect()->getPart(\Magento\Framework\DB\Select::COLUMNS); - foreach ($columns as $columnEntry) { - list($correlationName, $column, $alias) = $columnEntry; - if ($alias == 'is_saleable') { - if ($column instanceof \Zend_Db_Expr) { - $field = $column; - } else { - $connection = $this->getSelect()->getConnection(); - if (empty($correlationName)) { - $field = $connection->quoteColumnAs($column, $alias, true); - } else { - $field = $connection->quoteColumnAs([$correlationName, $column], $alias, true); - } - } - $this->getSelect()->where("{$field} = ?", $condition); - break; - } - } - - return $this; + $this->addIsSaleableAttributeToFilter($condition); + } elseif (is_string($attribute) && $attribute == 'tier_price') { + $this->addTierPriceAttributeToFilter($attribute, $condition); } else { return parent::addAttributeToFilter($attribute, $condition, $joinType); } @@ -2469,4 +2452,71 @@ public function getPricesCount() return $this->_pricesCount; } + + /** + * Add is_saleable attribute to filter + * + * @param array|null $condition + * @return $this + */ + private function addIsSaleableAttributeToFilter($condition) + { + $columns = $this->getSelect()->getPart(Select::COLUMNS); + foreach ($columns as $columnEntry) { + list($correlationName, $column, $alias) = $columnEntry; + if ($alias == 'is_saleable') { + if ($column instanceof \Zend_Db_Expr) { + $field = $column; + } else { + $connection = $this->getSelect()->getConnection(); + if (empty($correlationName)) { + $field = $connection->quoteColumnAs($column, $alias, true); + } else { + $field = $connection->quoteColumnAs([$correlationName, $column], $alias, true); + } + } + $this->getSelect()->where("{$field} = ?", $condition); + break; + } + } + + return $this; + } + + /** + * Add tier price attribute to filter + * + * @param string $attribute + * @param array|null $condition + * @return $this + */ + private function addTierPriceAttributeToFilter($attribute, $condition) + { + $attrCode = $attribute; + $connection = $this->getConnection(); + $attrTable = $this->_getAttributeTableAlias($attrCode); + $entity = $this->getEntity(); + $fKey = 'e.' . $this->getEntityPkName($entity); + $pKey = $attrTable . '.' . $this->getEntityPkName($entity); + $attribute = $entity->getAttribute($attrCode); + $attrFieldName = $attrTable . '.value'; + $fKey = $connection->quoteColumnAs($fKey, null); + $pKey = $connection->quoteColumnAs($pKey, null); + + $condArr = ["{$pKey} = {$fKey}"]; + $this->getSelect()->join( + [$attrTable => $this->getTable('catalog_product_entity_tier_price')], + '(' . implode(') AND (', $condArr) . ')', + [$attrCode => $attrFieldName] + ); + $this->removeAttributeToSelect($attrCode); + $this->_filterAttributes[$attrCode] = $attribute->getId(); + $this->_joinFields[$attrCode] = ['table' => '', 'field' => $attrFieldName]; + $field = $this->_getAttributeTableAlias($attrCode) . '.value'; + $conditionSql = $this->_getConditionSql($field, $condition); + $this->getSelect()->where($conditionSql, null, Select::TYPE_CONDITION); + $this->_totalRecords = null; + + return $this; + } } diff --git a/app/code/Magento/Cms/Test/Mftf/Data/WysiwygConfigData.xml b/app/code/Magento/Cms/Test/Mftf/Data/WysiwygConfigData.xml new file mode 100644 index 0000000000000..bc69c94329ac9 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Data/WysiwygConfigData.xml @@ -0,0 +1,21 @@ + + + + + + cms/wysiwyg/enabled + 0 + enabled + + + cms/wysiwyg/enabled + 0 + hidden + + diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontRegisterCustomerFromOrderSuccessPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontRegisterCustomerFromOrderSuccessPageActionGroup.xml new file mode 100644 index 0000000000000..a97ad2a8b1907 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontRegisterCustomerFromOrderSuccessPageActionGroup.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Downloadable/Observer/UpdateLinkPurchasedObserver.php b/app/code/Magento/Downloadable/Observer/UpdateLinkPurchasedObserver.php new file mode 100644 index 0000000000000..fc6430710aa59 --- /dev/null +++ b/app/code/Magento/Downloadable/Observer/UpdateLinkPurchasedObserver.php @@ -0,0 +1,71 @@ +purchasedCollectionFactory = $purchasedCollectionFactory; + } + + /** + * Link customer_id to downloadable link purchased after update order + * + * @param Observer $observer + * @return $this + */ + public function execute(Observer $observer) + { + $order = $observer->getEvent()->getOrder(); + $orderId = $order->getId(); + $customerId = $order->getCustomerId(); + if (!$orderId || !$customerId) { + return $this; + } + $purchasedLinksCollection = $this->getPurchasedCollection((int)$orderId); + foreach ($purchasedLinksCollection as $linkPurchased) { + $linkPurchased->setCustomerId($customerId)->save(); + } + + return $this; + } + + /** + * Get purchased collection by order id + * + * @param int $orderId + * @return PurchasedCollection + */ + private function getPurchasedCollection(int $orderId): PurchasedCollection + { + return $this->purchasedCollectionFactory->create()->addFieldToFilter( + 'order_id', + ['eq' => $orderId] + ); + } +} diff --git a/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/StorefrontAssertDownloadableProductIsPresentInCustomerAccountActionGroup.xml b/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/StorefrontAssertDownloadableProductIsPresentInCustomerAccountActionGroup.xml new file mode 100644 index 0000000000000..d672e5bd0f95f --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/StorefrontAssertDownloadableProductIsPresentInCustomerAccountActionGroup.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/app/code/Magento/Downloadable/Test/Mftf/Data/CatalogConfigData.xml b/app/code/Magento/Downloadable/Test/Mftf/Data/CatalogConfigData.xml new file mode 100644 index 0000000000000..8bb81f9c7579d --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Data/CatalogConfigData.xml @@ -0,0 +1,21 @@ + + + + + + catalog/downloadable/disable_guest_checkout + 0 + 0 + + + catalog/downloadable/disable_guest_checkout + 0 + 1 + + diff --git a/app/code/Magento/Downloadable/Test/Mftf/Page/StorefrontCustomerDownloadableProductsPage.xml b/app/code/Magento/Downloadable/Test/Mftf/Page/StorefrontCustomerDownloadableProductsPage.xml new file mode 100644 index 0000000000000..eafb37111fda2 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Page/StorefrontCustomerDownloadableProductsPage.xml @@ -0,0 +1,13 @@ + + + + +
+ + diff --git a/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontCustomerDownloadableProductsSection.xml b/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontCustomerDownloadableProductsSection.xml new file mode 100644 index 0000000000000..d45a774077ba0 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontCustomerDownloadableProductsSection.xml @@ -0,0 +1,14 @@ + + + + +
+ +
+
diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/LinkDownloadableProductFromGuestToCustomerTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/LinkDownloadableProductFromGuestToCustomerTest.xml new file mode 100644 index 0000000000000..f9046623a8f7c --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/LinkDownloadableProductFromGuestToCustomerTest.xml @@ -0,0 +1,70 @@ + + + + + + + + + + <description value="Verify that in 'My Downloadable Products' section in customer account user can see products."/> + <severity value="AVERAGE"/> + <useCaseId value="MAGETWO-98655"/> + <testCaseId value="MC-16148"/> + <group value="catalog"/> + <group value="downloadable"/> + </annotations> + <before> + <magentoCLI command="config:set {{EnableGuestCheckoutWithDownloadableItems.path}} {{EnableGuestCheckoutWithDownloadableItems.value}}" stepKey="enableGuestCheckoutWithDownloadableItems" /> + <createData entity="ApiDownloadableProduct" stepKey="createProduct"/> + <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink"> + <requiredEntity createDataKey="createProduct"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <magentoCLI command="config:set {{DisableGuestCheckoutWithDownloadableItems.path}} {{DisableGuestCheckoutWithDownloadableItems.value}}" stepKey="disableGuestCheckoutWithDownloadableItems" /> + <deleteData stepKey="deleteProduct" createDataKey="createProduct"/> + <actionGroup ref="RemoveCustomerFromAdminActionGroup" stepKey="deleteCustomer"> + <argument name="customer" value="Simple_US_NY_Customer"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Step 1: Go to Storefront as Guest--> + <actionGroup ref="CustomerLogoutStorefrontActionGroup" stepKey="logoutCustomer"/> + <!--Step 2: Add downloadable product to shopping cart--> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.custom_attributes[url_key]$$)}}" stepKey="amOnStorefrontProductPage"/> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="addProductToCart"> + <argument name="product" value="$$createProduct$$"/> + <argument name="productCount" value="1"/> + </actionGroup> + <!--Step 3: Go to checkout--> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart" /> + <!--Step 4: Select Check/Money Order payment, fill required fields and click Update and Place Order--> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyOrderPayment"/> + <actionGroup ref="GuestCheckoutFillNewBillingAddressActionGroup" stepKey="changeAddress"> + <argument name="customerVar" value="Simple_US_NY_Customer"/> + <argument name="customerAddressVar" value="US_Address_NY"/> + </actionGroup> + <click selector="{{CheckoutPaymentSection.update}}" stepKey="clickUpdateButton" /> + <waitForPageLoad stepKey="waitUpdateAddress"/> + <actionGroup ref="CheckoutPlaceOrderActionGroup" stepKey="placeOrder"> + <argument name="orderNumberMessage" value="CONST.successGuestCheckoutOrderNumberMessage"/> + <argument name="emailYouMessage" value="CONST.successCheckoutEmailYouMessage" /> + </actionGroup> + <!--Step 5: Create customer account after placing order--> + <actionGroup ref="StorefrontRegisterCustomerFromOrderSuccessPage" stepKey="createCustomerAfterPlaceOrder"> + <argument name="customer" value="CustomerEntityOne"/> + </actionGroup> + <!--Step 6: Go To My Account -> My Downloadable Products and check if downloadable product link exist--> + <actionGroup ref="StorefrontAssertDownloadableProductIsPresentInCustomerAccount" stepKey="seeStorefontMyDownloadableProductsProductName"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Downloadable/etc/events.xml b/app/code/Magento/Downloadable/etc/events.xml index 5a985fc33802e..21cc50ddc9669 100644 --- a/app/code/Magento/Downloadable/etc/events.xml +++ b/app/code/Magento/Downloadable/etc/events.xml @@ -11,6 +11,7 @@ </event> <event name="sales_order_save_after"> <observer name="downloadable_observer" instance="Magento\Downloadable\Observer\SetLinkStatusObserver" /> + <observer name="downloadable_observer_assign_customer" instance="Magento\Downloadable\Observer\UpdateLinkPurchasedObserver" /> </event> <event name="sales_model_service_quote_submit_success"> <observer name="checkout_type_onepage_save_order_after" instance="Magento\Downloadable\Observer\SetHasDownloadableProductsObserver" /> diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/AddressSave.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/AddressSave.php index dc994e554b394..53563ccd70061 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/AddressSave.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/AddressSave.php @@ -9,6 +9,7 @@ use Magento\Backend\App\Action\Context; use Magento\Backend\Model\View\Result\Redirect; use Magento\Directory\Model\RegionFactory; +use Magento\Sales\Api\OrderAddressRepositoryInterface; use Magento\Sales\Api\OrderManagementInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Api\Data\OrderAddressInterface; @@ -24,8 +25,11 @@ use Magento\Framework\Controller\Result\RawFactory; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\Action\HttpPostActionInterface; /** + * Sales address save + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class AddressSave extends Order @@ -54,6 +58,7 @@ class AddressSave extends Order * @param OrderRepositoryInterface $orderRepository * @param LoggerInterface $logger * @param RegionFactory|null $regionFactory + * @param OrderAddressRepositoryInterface|null $orderAddressRepository * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -69,9 +74,12 @@ public function __construct( OrderManagementInterface $orderManagement, OrderRepositoryInterface $orderRepository, LoggerInterface $logger, - RegionFactory $regionFactory = null + RegionFactory $regionFactory = null, + OrderAddressRepositoryInterface $orderAddressRepository = null ) { $this->regionFactory = $regionFactory ?: ObjectManager::getInstance()->get(RegionFactory::class); + $this->orderAddressRepository = $orderAddressRepository ?: ObjectManager::getInstance() + ->get(OrderAddressRepositoryInterface::class); parent::__construct( $context, $coreRegistry, @@ -87,6 +95,11 @@ public function __construct( ); } + /** + * @var OrderAddressRepositoryInterface + */ + private $orderAddressRepository; + /** * Save order address * @@ -105,7 +118,7 @@ public function execute() if ($data && $address->getId()) { $address->addData($data); try { - $address->save(); + $this->orderAddressRepository->save($address); $this->_eventManager->dispatch( 'admin_sales_order_address_update', [ diff --git a/app/code/Magento/Sales/Model/Order/AddressRepository.php b/app/code/Magento/Sales/Model/Order/AddressRepository.php index 96dc531a82bf4..7543a298c3a4a 100644 --- a/app/code/Magento/Sales/Model/Order/AddressRepository.php +++ b/app/code/Magento/Sales/Model/Order/AddressRepository.php @@ -5,7 +5,11 @@ */ namespace Magento\Sales\Model\Order; +use Magento\Customer\Model\AttributeMetadataDataProvider; +use Magento\Customer\Model\ResourceModel\Form\Attribute\Collection as AttributeCollection; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Sales\Api\Data\OrderAddressInterface; use Magento\Sales\Model\ResourceModel\Metadata; use Magento\Sales\Api\Data\OrderAddressSearchResultInterfaceFactory as SearchResultFactory; use Magento\Framework\Exception\CouldNotDeleteException; @@ -40,20 +44,88 @@ class AddressRepository implements \Magento\Sales\Api\OrderAddressRepositoryInte */ private $collectionProcessor; + /** + * @var AttributeMetadataDataProvider + */ + private $attributeMetadataDataProvider; + + /** + * @var AttributeCollection|null + */ + private $attributesList = null; + /** * AddressRepository constructor. * @param Metadata $metadata * @param SearchResultFactory $searchResultFactory * @param CollectionProcessorInterface|null $collectionProcessor + * @param AttributeMetadataDataProvider $attributeMetadataDataProvider */ public function __construct( Metadata $metadata, SearchResultFactory $searchResultFactory, - CollectionProcessorInterface $collectionProcessor = null + CollectionProcessorInterface $collectionProcessor = null, + AttributeMetadataDataProvider $attributeMetadataDataProvider = null ) { $this->metadata = $metadata; $this->searchResultFactory = $searchResultFactory; $this->collectionProcessor = $collectionProcessor ?: $this->getCollectionProcessor(); + $this->attributeMetadataDataProvider = $attributeMetadataDataProvider ?: ObjectManager::getInstance() + ->get(AttributeMetadataDataProvider::class); + } + + /** + * Format multiline and multiselect attributes + * + * @param OrderAddressInterface $orderAddress + * + * @return void + */ + private function formatCustomAddressAttributes(OrderAddressInterface $orderAddress) + { + $attributesList = $this->getAttributesList(); + + foreach ($attributesList as $attribute) { + $attributeCode = $attribute->getAttributeCode(); + if (!$orderAddress->hasData($attributeCode)) { + continue; + } + $attributeValue = $orderAddress->getData($attributeCode); + if (is_array($attributeValue)) { + $glue = $attribute->getFrontendInput() === 'multiline' ? PHP_EOL : ','; + $attributeValue = trim(implode($glue, $attributeValue)); + } + $orderAddress->setData($attributeCode, $attributeValue); + } + } + + /** + * Get list of custom attributes. + * + * @return AttributeCollection|null + */ + private function getAttributesList() + { + if (!$this->attributesList) { + $attributesList = $this->attributeMetadataDataProvider->loadAttributesCollection( + 'customer_address', + 'customer_register_address' + ); + $attributesList->addFieldToFilter('is_user_defined', 1); + $attributesList->addFieldToFilter( + 'frontend_input', + [ + 'in' => [ + 'multiline', + 'multiselect', + ], + ] + ); + + $this->attributesList = $attributesList; + } + + return $this->attributesList; } /** @@ -95,7 +167,7 @@ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCr $searchResult = $this->searchResultFactory->create(); $this->collectionProcessor->process($searchCriteria, $searchResult); $searchResult->setSearchCriteria($searchCriteria); - + return $searchResult; } @@ -141,6 +213,7 @@ public function deleteById($id) */ public function save(\Magento\Sales\Api\Data\OrderAddressInterface $entity) { + $this->formatCustomAddressAttributes($entity); try { $this->metadata->getMapper()->save($entity); $this->registry[$entity->getEntityId()] = $entity; diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/AddressRepositoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/AddressRepositoryTest.php index 87f4a9103be6f..e48fea847a743 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/AddressRepositoryTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/AddressRepositoryTest.php @@ -3,127 +3,180 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Test\Unit\Model\Order; +use Magento\Customer\Model\AttributeMetadataDataProvider; +use Magento\Eav\Model\Entity\Attribute; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Sales\Model\Order\Address as OrderAddress; +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; +use Magento\Sales\Model\Order\AddressRepository; +use Magento\Sales\Model\ResourceModel\Order\Address\Collection as OrderAddressCollection; +use Magento\Customer\Model\ResourceModel\Form\Attribute\Collection as FormAttributeCollection; +use Magento\Framework\Api\SearchCriteria; +use Magento\Sales\Api\Data\OrderAddressSearchResultInterfaceFactory; +use Magento\Sales\Model\ResourceModel\Metadata; +use Magento\Sales\Model\Order\AddressRepository as OrderAddressRepository; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\InputException; /** * Unit test for order address repository class. * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class AddressRepositoryTest extends \PHPUnit\Framework\TestCase +class AddressRepositoryTest extends TestCase { /** * Subject of testing. * - * @var \Magento\Sales\Model\Order\AddressRepository + * @var OrderAddressRepository */ protected $subject; /** * Sales resource metadata. * - * @var \Magento\Sales\Model\ResourceModel\Metadata|\PHPUnit_Framework_MockObject_MockObject + * @var Metadata|MockObject */ protected $metadata; /** - * @var \Magento\Sales\Api\Data\OrderAddressSearchResultInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + * @var OrderAddressSearchResultInterfaceFactory|MockObject */ protected $searchResultFactory; /** - * @var CollectionProcessorInterface |\PHPUnit_Framework_MockObject_MockObject + * @var CollectionProcessorInterface|MockObject */ private $collectionProcessorMock; + /** + * @var Attribute[] + */ + private $attributesList; + + /** + * @var AttributeMetadataDataProvider + */ + private $attributeMetadataDataProvider; + + /** + * @var OrderAddress|MockObject + */ + private $orderAddress; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @inheritdoc + */ protected function setUp() { - $objectManager = new ObjectManager($this); + $this->objectManager = new ObjectManager($this); + $this->orderAddress = $this->createPartialMock(OrderAddress::class, ['getEntityId', 'load']); $this->metadata = $this->createPartialMock( - \Magento\Sales\Model\ResourceModel\Metadata::class, + Metadata::class, ['getNewInstance', 'getMapper'] ); + $this->attributeMetadataDataProvider = $this->getMockBuilder(AttributeMetadataDataProvider::class) + ->disableOriginalConstructor() + ->setMethods(['loadAttributesCollection']) + ->getMock(); + $collectionAttribute = $this->getMockBuilder(FormAttributeCollection::class) + ->setMethods(['addFieldToFilter', 'getIterator']) + ->disableOriginalConstructor() + ->getMock(); + $collectionAttribute->method('getIterator') + ->willReturn(new \ArrayIterator([])); + $this->attributeMetadataDataProvider->method('loadAttributesCollection')->willReturn($collectionAttribute); + $this->searchResultFactory = $this->createPartialMock( - \Magento\Sales\Api\Data\OrderAddressSearchResultInterfaceFactory::class, + OrderAddressSearchResultInterfaceFactory::class, ['create'] ); $this->collectionProcessorMock = $this->getMockBuilder(CollectionProcessorInterface::class) ->getMock(); - $this->subject = $objectManager->getObject( - \Magento\Sales\Model\Order\AddressRepository::class, + $this->subject = $this->objectManager->getObject( + OrderAddressRepository::class, [ 'metadata' => $this->metadata, 'searchResultFactory' => $this->searchResultFactory, 'collectionProcessor' => $this->collectionProcessorMock, + 'attributeMetadataDataProvider' => $this->attributeMetadataDataProvider ] ); } /** + * Test for get order address + * * @param int|null $id * @param int|null $entityId + * + * @return void * @dataProvider getDataProvider */ public function testGet($id, $entityId) { if (!$id) { - $this->expectException( - \Magento\Framework\Exception\InputException::class - ); - + $this->expectException(InputException::class); $this->subject->get($id); } else { - $address = $this->createPartialMock(\Magento\Sales\Model\Order\Address::class, ['load', 'getEntityId']); - $address->expects($this->once()) + $this->orderAddress->expects($this->once()) ->method('load') ->with($id) - ->willReturn($address); - $address->expects($this->once()) + ->willReturn($this->orderAddress); + $this->orderAddress->expects($this->once()) ->method('getEntityId') ->willReturn($entityId); $this->metadata->expects($this->once()) ->method('getNewInstance') - ->willReturn($address); + ->willReturn($this->orderAddress); if (!$entityId) { - $this->expectException( - \Magento\Framework\Exception\NoSuchEntityException::class - ); - + $this->expectException(NoSuchEntityException::class); $this->subject->get($id); } else { - $this->assertEquals($address, $this->subject->get($id)); + $this->assertEquals($this->orderAddress, $this->subject->get($id)); - $address->expects($this->never()) + $this->orderAddress->expects($this->never()) ->method('load') ->with($id) - ->willReturn($address); - $address->expects($this->never()) + ->willReturn($this->orderAddress); + $this->orderAddress->expects($this->never()) ->method('getEntityId') ->willReturn($entityId); $this->metadata->expects($this->never()) ->method('getNewInstance') - ->willReturn($address); + ->willReturn($this->orderAddress); // Retrieve Address from registry. - $this->assertEquals($address, $this->subject->get($id)); + $this->assertEquals($this->orderAddress, $this->subject->get($id)); } } } /** + * Data for testGet + * * @return array */ - public function getDataProvider() + public function getDataProvider(): array { return [ [null, null], @@ -132,10 +185,15 @@ public function getDataProvider() ]; } + /** + * Test for get list order address + * + * @return void + */ public function testGetList() { - $searchCriteria = $this->createMock(\Magento\Framework\Api\SearchCriteria::class); - $collection = $this->createMock(\Magento\Sales\Model\ResourceModel\Order\Address\Collection::class); + $searchCriteria = $this->createMock(SearchCriteria::class); + $collection = $this->createMock(OrderAddressCollection::class); $this->collectionProcessorMock->expects($this->once()) ->method('process') @@ -147,15 +205,19 @@ public function testGetList() $this->assertEquals($collection, $this->subject->getList($searchCriteria)); } + /** + * Test for delete order address + * + * @return void + */ public function testDelete() { - $address = $this->createPartialMock(\Magento\Sales\Model\Order\Address::class, ['getEntityId']); - $address->expects($this->once()) + $this->orderAddress->expects($this->once()) ->method('getEntityId') ->willReturn(1); $mapper = $this->getMockForAbstractClass( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, [], '', false, @@ -165,27 +227,29 @@ public function testDelete() ); $mapper->expects($this->once()) ->method('delete') - ->with($address); + ->with($this->orderAddress); $this->metadata->expects($this->any()) ->method('getMapper') ->willReturn($mapper); - $this->assertTrue($this->subject->delete($address)); + $this->assertTrue($this->subject->delete($this->orderAddress)); } /** + * Test for delete order address with exception + * + * @return void * @expectedException \Magento\Framework\Exception\CouldNotDeleteException * @expectedExceptionMessage Could not delete order address */ public function testDeleteWithException() { - $address = $this->createPartialMock(\Magento\Sales\Model\Order\Address::class, ['getEntityId']); - $address->expects($this->never()) + $this->orderAddress->expects($this->never()) ->method('getEntityId'); $mapper = $this->getMockForAbstractClass( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, [], '', false, @@ -201,18 +265,22 @@ public function testDeleteWithException() ->method('getMapper') ->willReturn($mapper); - $this->subject->delete($address); + $this->subject->delete($this->orderAddress); } + /** + * Test for save order address + * + * @return void + */ public function testSave() { - $address = $this->createPartialMock(\Magento\Sales\Model\Order\Address::class, ['getEntityId']); - $address->expects($this->any()) + $this->orderAddress->expects($this->any()) ->method('getEntityId') ->willReturn(1); $mapper = $this->getMockForAbstractClass( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, [], '', false, @@ -222,27 +290,29 @@ public function testSave() ); $mapper->expects($this->once()) ->method('save') - ->with($address); + ->with($this->orderAddress); $this->metadata->expects($this->any()) ->method('getMapper') ->willReturn($mapper); - $this->assertEquals($address, $this->subject->save($address)); + $this->assertEquals($this->orderAddress, $this->subject->save($this->orderAddress)); } /** + * Test for save order address with exception + * + * @return void * @expectedException \Magento\Framework\Exception\CouldNotSaveException * @expectedExceptionMessage Could not save order address */ public function testSaveWithException() { - $address = $this->createPartialMock(\Magento\Sales\Model\Order\Address::class, ['getEntityId']); - $address->expects($this->never()) + $this->orderAddress->expects($this->never()) ->method('getEntityId'); $mapper = $this->getMockForAbstractClass( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, [], '', false, @@ -258,17 +328,117 @@ public function testSaveWithException() ->method('getMapper') ->willReturn($mapper); - $this->assertEquals($address, $this->subject->save($address)); + $this->assertEquals($this->orderAddress, $this->subject->save($this->orderAddress)); } + /** + * Tets for create order address + * + * @return void + */ public function testCreate() { - $address = $this->createPartialMock(\Magento\Sales\Model\Order\Address::class, ['getEntityId']); - $this->metadata->expects($this->once()) ->method('getNewInstance') - ->willReturn($address); + ->willReturn($this->orderAddress); + + $this->assertEquals($this->orderAddress, $this->subject->create()); + } + + /** + * Test for save sales address with multi-attribute. + * + * @param string $attributeType + * @param string $attributeCode + * @param array $attributeValue + * @param string $expected + * + * @return void + * @dataProvider dataMultiAttribute + */ + public function testSaveWithMultiAttribute( + string $attributeType, + string $attributeCode, + array $attributeValue, + string $expected + ) { + $orderAddress = $this->getMockBuilder(OrderAddress::class) + ->disableOriginalConstructor() + ->setMethods(['getEntityId', 'hasData', 'getData', 'setData']) + ->getMock(); + + $orderAddress->expects($this->any()) + ->method('getEntityId') + ->willReturn(1); + + $mapper = $this->getMockForAbstractClass( + AbstractDb::class, + [], + '', + false, + true, + true, + ['save'] + ); + $mapper->method('save') + ->with($orderAddress); + $this->metadata->method('getMapper') + ->willReturn($mapper); + + $attributeModel = $this->getMockBuilder(Attribute::class) + ->setMethods(['getFrontendInput', 'getAttributeCode']) + ->disableOriginalConstructor() + ->getMock(); + $attributeModel->method('getFrontendInput')->willReturn($attributeType); + $attributeModel->method('getAttributeCode')->willReturn($attributeCode); + $this->attributesList = [$attributeModel]; + + $this->subject = $this->objectManager->getObject( + AddressRepository::class, + [ + 'metadata' => $this->metadata, + 'searchResultFactory' => $this->searchResultFactory, + 'collectionProcessor' => $this->collectionProcessorMock, + 'attributeMetadataDataProvider' => $this->attributeMetadataDataProvider, + 'attributesList' => $this->attributesList, + ] + ); + + $orderAddress->method('hasData')->with($attributeCode)->willReturn(true); + $orderAddress->method('getData')->with($attributeCode)->willReturn($attributeValue); + $orderAddress->expects($this->once())->method('setData')->with($attributeCode, $expected); + + $this->assertEquals($orderAddress, $this->subject->save($orderAddress)); + } + + /** + * Data for testSaveWithMultiAttribute + * + * @return array + */ + public function dataMultiAttribute(): array + { + $data = [ + 'multiselect' => [ + 'multiselect', + 'attr_multiselect', + [ + 'opt1', + 'opt2', + ], + 'opt1,opt2', + ], + 'multiline' => [ + 'multiline', + 'attr_multiline', + [ + 'line1', + 'line2', + ], + 'line1'.PHP_EOL.'line2', + ], + ]; - $this->assertEquals($address, $this->subject->create()); + return $data; } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php index 6ac7a8551fd99..19be025b5e40f 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php @@ -5,6 +5,9 @@ */ namespace Magento\Catalog\Model\ResourceModel\Product; +/** + * Collection test + */ class CollectionTest extends \PHPUnit\Framework\TestCase { /** @@ -228,4 +231,17 @@ public function testAddAttributeToFilterAffectsGetSize() $this->collection->addAttributeToFilter('sku', 'Product1'); $this->assertEquals(1, $this->collection->getSize()); } + + /** + * Add tier price attribute filter to collection + * + * @magentoDataFixture Magento/Catalog/Model/ResourceModel/_files/few_simple_products.php + * @magentoDataFixture Magento/Catalog/Model/ResourceModel/_files/product_simple.php + */ + public function testAddAttributeTierPriceToFilter() + { + $this->assertEquals(11, $this->collection->getSize()); + $this->collection->addAttributeToFilter('tier_price', ['gt' => 0]); + $this->assertEquals(1, $this->collection->getSize()); + } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/Order/AddressRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/AddressRepositoryTest.php index 7a38c14685073..775ddd7274a48 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Model/Order/AddressRepositoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/AddressRepositoryTest.php @@ -3,19 +3,23 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Model\Order; +use Magento\Framework\ObjectManagerInterface; +use Magento\Sales\Api\Data\OrderAddressInterface; +use Magento\Sales\Api\OrderAddressRepositoryInterface; use Magento\TestFramework\Helper\Bootstrap; use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Framework\Api\SortOrderBuilder; +use PHPUnit\Framework\TestCase; /** * Class AddressRepositoryTest - * @package Magento\Sales\Model\Order] - * @magentoDbIsolation enabled */ -class AddressRepositoryTest extends \PHPUnit\Framework\TestCase +class AddressRepositoryTest extends TestCase { /** @var AddressRepository */ protected $repository; @@ -29,22 +33,25 @@ class AddressRepositoryTest extends \PHPUnit\Framework\TestCase /** @var SearchCriteriaBuilder */ private $searchCriteriaBuilder; + /** @var ObjectManagerInterface */ + private $objectManager; + + /** + * @inheritdoc + */ protected function setUp() { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $this->repository = $objectManager->create(AddressRepository::class); - $this->searchCriteriaBuilder = $objectManager->create( - \Magento\Framework\Api\SearchCriteriaBuilder::class - ); - $this->filterBuilder = $objectManager->get( - \Magento\Framework\Api\FilterBuilder::class - ); - $this->sortOrderBuilder = $objectManager->get( - \Magento\Framework\Api\SortOrderBuilder::class - ); + $this->objectManager = Bootstrap::getObjectManager(); + $this->repository = $this->objectManager->get(AddressRepository::class); + $this->searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $this->filterBuilder = $this->objectManager->get(FilterBuilder::class); + $this->sortOrderBuilder = $this->objectManager->get(SortOrderBuilder::class); } /** + * Test for get list with multiple filters and sorting + * + * @return void * @magentoDataFixture Magento/Sales/_files/address_list.php */ public function testGetListWithMultipleFiltersAndSorting() @@ -78,4 +85,23 @@ public function testGetListWithMultipleFiltersAndSorting() $this->assertEquals('ZX0789', array_shift($items)->getPostcode()); $this->assertEquals('47676', array_shift($items)->getPostcode()); } + + /** + * Test for formatting custom sales address multi-attribute + * + * @return void + * @magentoDataFixture Magento/Sales/_files/order_address_with_multi_attribute.php + */ + public function testFormatSalesAddressCustomMultiAttribute() + { + $address = $this->objectManager->get(OrderAddressInterface::class) + ->load('multiattribute@example.com', 'email'); + $address->setData('address_multiselect_attribute', ['dog', 'cat']); + $address->setData('address_multiline_attribute', ['dog', 'cat']); + + $this->objectManager->get(OrderAddressRepositoryInterface::class) + ->save($address); + $this->assertEquals('dog,cat', $address->getData('address_multiselect_attribute')); + $this->assertEquals('dog'.PHP_EOL.'cat', $address->getData('address_multiline_attribute')); + } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_address_with_multi_attribute.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_address_with_multi_attribute.php new file mode 100644 index 0000000000000..c78caa66cfe1e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_address_with_multi_attribute.php @@ -0,0 +1,100 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Eav\Model\Config; +use Magento\Eav\Model\Entity\Attribute\Set; +use Magento\Customer\Model\Attribute; +use Magento\Eav\Model\Entity\Type; +use Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend; +use Magento\Sales\Model\Order\Address; + +$objectManager = Bootstrap::getObjectManager(); +$addressData = [ + 'region' => 'CA', + 'region_id' => '12', + 'postcode' => '11111', + 'lastname' => 'lastname', + 'firstname' => 'firstname', + 'street' => 'street', + 'city' => 'Los Angeles', + 'email' => 'multiattribute@example.com', + 'telephone' => '2222222', + 'country_id' => 'US' +]; + +/** @var $entityType Type */ +$entityType = $objectManager->get(Config::class) + ->getEntityType('customer_address'); +/** @var $attributeSet Set */ +$attributeSet = $objectManager->get(Set::class); + +$attributeMultiselect = $objectManager->create( + Attribute::class, + [ + 'data' => [ + 'frontend_input' => 'multiselect', + 'frontend_label' => ['Multiselect Attribute'], + 'sort_order' => '0', + 'backend_type' => 'varchar', + 'is_user_defined' => 1, + 'is_system' => 0, + 'is_required' => '0', + 'is_visible' => '0', + 'attribute_set_id' => $entityType->getDefaultAttributeSetId(), + 'attribute_group_id' => $attributeSet->getDefaultGroupId($entityType->getDefaultAttributeSetId()), + 'entity_type_id' => $entityType->getId(), + 'backend_model' => ArrayBackend::class, + 'used_in_forms' => ['customer_register_address'], + 'option' => [ + 'value' => [ + 'dog' => ['Dog'], + 'cat' => ['Cat'], + ], + 'order' => [ + 'dog' => 1, + 'cat' => 2, + ], + ], + ] + ] +); + +$attributeMultiselect->setAttributeCode('address_multiselect_attribute'); +$attributeMultiselect->save(); + +$attributeMultiline = $objectManager->create( + Attribute::class, + [ + 'data' => [ + 'frontend_input' => 'multiline', + 'frontend_label' => ['Multiline Attribute'], + 'multiline_count' => 2, + 'sort_order' => '0', + 'backend_type' => 'varchar', + 'is_user_defined' => 1, + 'is_system' => 0, + 'is_required' => '0', + 'is_visible' => '0', + 'attribute_set_id' => $entityType->getDefaultAttributeSetId(), + 'attribute_group_id' => $attributeSet->getDefaultGroupId($entityType->getDefaultAttributeSetId()), + 'entity_type_id' => $entityType->getId(), + 'backend_model' => ArrayBackend::class, + 'used_in_forms' => ['customer_register_address'], + ] + ] +); + +$attributeMultiline->setAttributeCode('address_multiline_attribute'); +$attributeMultiline->save(); + +$billingAddress = $objectManager->create( + Address::class, + ['data' => $addressData] +); +$billingAddress->setAddressType('billing'); +$billingAddress->save(); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_address_with_multi_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_address_with_multi_attribute_rollback.php new file mode 100644 index 0000000000000..e7014583e872c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_address_with_multi_attribute_rollback.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Api\FilterBuilder; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Sales\Api\OrderAddressRepositoryInterface; +use Magento\Sales\Api\Data\OrderAddressInterface; +use Magento\Eav\Api\AttributeRepositoryInterface; + +$attributeCodes = [ + 'fixture_address_multiselect_attribute', + 'fixture_address_multiline_attribute', +]; +$eavConfigType = 'customer_address'; + +$objectManager = Bootstrap::getObjectManager(); +/** @var OrderAddressRepositoryInterface $salesAddressRepository */ +$salesAddressRepository = $objectManager->get(OrderAddressRepositoryInterface::class); +/** @var SearchCriteriaBuilder $searchCriteriaBuilder */ +$searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class); +/** @var FilterBuilder $filterBuilder */ +$filterBuilder = $objectManager->get(FilterBuilder::class); +$filters = [ + $filterBuilder->setField(OrderAddressInterface::EMAIL) + ->setValue('multiattribute@example.com') + ->create(), +]; +$searchCriteria = $searchCriteriaBuilder->addFilters($filters) + ->create(); +$saleAddresses = $salesAddressRepository->getList($searchCriteria) + ->getItems(); +foreach ($saleAddresses as $saleAddress) { + $salesAddressRepository->delete($saleAddress); +} + +/** @var AttributeRepositoryInterface $attributerepository */ +$attributeRepository = $objectManager->get(AttributeRepositoryInterface::class); +/** @var FilterBuilder $filterBuilder */ +$filterBuilder = $objectManager->get(FilterBuilder::class); +$filters = [ + $filterBuilder->setField('attribute_code') + ->setValue( + [ + 'address_multiline_attribute', + 'address_multiselect_attribute', + ] + ) + ->setConditionType('IN') + ->create(), +]; +$searchCriteria = $searchCriteriaBuilder->addFilters($filters) + ->create(); +$attributes = $attributeRepository->getList($eavConfigType, $searchCriteria) + ->getItems(); +foreach ($attributes as $attribute) { + $attributeRepository->delete($attribute); +}