From 10770c3a5c4c52c02b14b718f4719c1a74b34611 Mon Sep 17 00:00:00 2001 From: Maksym Aposov Date: Thu, 24 Nov 2016 19:00:26 +0200 Subject: [PATCH 1/3] MAGETWO-60605: Exception when adding configurable product by sku from customer account if associated simple product is out of stock --- .../Price/ConfigurablePriceResolver.php | 10 +-- .../Price/ConfigurablePriceResolverTest.php | 6 +- .../Pricing/Price/FinalPriceTest.php | 71 +++++++++++++++++++ 3 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Price/FinalPriceTest.php diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php index 68e82ed76a23f..4fc7a2869b61a 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php @@ -53,8 +53,7 @@ public function __construct( /** * @param \Magento\Framework\Pricing\SaleableInterface|\Magento\Catalog\Model\Product $product - * @return float - * @throws \Magento\Framework\Exception\LocalizedException + * @return float | null */ public function resolvePrice(\Magento\Framework\Pricing\SaleableInterface $product) { @@ -64,12 +63,7 @@ public function resolvePrice(\Magento\Framework\Pricing\SaleableInterface $produ $productPrice = $this->priceResolver->resolvePrice($subProduct); $price = $price ? min($price, $productPrice) : $productPrice; } - if ($price === null) { - throw new \Magento\Framework\Exception\LocalizedException( - __('Configurable product "%1" does not have sub-products', $product->getSku()) - ); - } - return (float)$price; + return $price === null ? null : (float)$price; } } diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php index 8db61bb5e0a43..58d050881cea6 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php @@ -53,8 +53,6 @@ protected function setUp() /** * situation: There are no used products, thus there are no prices - * - * @expectedException \Magento\Framework\Exception\LocalizedException */ public function testResolvePriceWithNoPrices() { @@ -62,11 +60,11 @@ public function testResolvePriceWithNoPrices() \Magento\Catalog\Model\Product::class )->disableOriginalConstructor()->getMock(); - $product->expects($this->once())->method('getSku')->willReturn('Kiwi'); + $product->expects($this->never())->method('getSku'); $this->lowestPriceOptionsProvider->expects($this->once())->method('getProducts')->willReturn([]); - $this->resolver->resolvePrice($product); + $this->assertNull($this->resolver->resolvePrice($product)); } /** diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Price/FinalPriceTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Price/FinalPriceTest.php new file mode 100644 index 0000000000000..25e2340d7b23a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Price/FinalPriceTest.php @@ -0,0 +1,71 @@ +productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + */ + public function testNullPriceForConfigurbaleIfAllChildIsOutOfStock() + { + //prepare configurable product + $configurableProduct = $this->productRepository->get('configurable', false, null, true); + foreach ($configurableProduct->getExtensionAttributes()->getConfigurableProductLinks() as $productId) { + $product = $this->productRepository->getById($productId); + $product->getExtensionAttributes()->getStockItem()->setIsInStock(0); + $this->productRepository->save($product); + } + + $finalPrice = Bootstrap::getObjectManager()->create( + FinalPrice::class, + [ + 'saleableItem' => $configurableProduct, + 'quantity' => 1 + ] + ); + + static::assertNull($finalPrice->getValue()); + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + */ + public function testNullPriceForConfigurbaleIfAllChildIsDisabled() + { + //prepare configurable product + $configurableProduct = $this->productRepository->get('configurable', false, null, true); + foreach ($configurableProduct->getExtensionAttributes()->getConfigurableProductLinks() as $productId) { + $product = $this->productRepository->getById($productId); + $product->setStatus(Status::STATUS_DISABLED); + $this->productRepository->save($product); + } + + $finalPrice = Bootstrap::getObjectManager()->create( + FinalPrice::class, + [ + 'saleableItem' => $configurableProduct, + 'quantity' => 1 + ] + ); + + static::assertNull($finalPrice->getValue()); + } +} From 1b945dce27b86a8e89139bdf5674d4c83eac2737 Mon Sep 17 00:00:00 2001 From: Illia Grybkov Date: Mon, 28 Nov 2016 09:11:13 +0200 Subject: [PATCH 2/3] MAGETWO-61055: [Backport] - Catalog broken when all child products of configurable are disabled - for 2.1.3 --- .../Pricing/Renderer/SalableResolver.php | 24 ++ .../Renderer/SalableResolverInterface.php | 21 + .../Catalog/Pricing/Render/FinalPriceBox.php | 33 +- .../Pricing/Renderer/SalableResolverTest.php | 60 +++ .../Unit/Pricing/Render/FinalPriceBoxTest.php | 49 ++- app/code/Magento/Catalog/etc/di.xml | 3 +- .../Price/ConfigurablePriceResolver.php | 5 - .../Price/ConfigurablePriceResolverTest.php | 18 - .../Unit/Pricing/Render/FinalPriceBoxTest.php | 406 +++++++++++++++--- 9 files changed, 513 insertions(+), 106 deletions(-) create mode 100644 app/code/Magento/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php create mode 100644 app/code/Magento/Catalog/Model/Product/Pricing/Renderer/SalableResolverInterface.php create mode 100644 app/code/Magento/Catalog/Test/Unit/Model/Product/Pricing/Renderer/SalableResolverTest.php diff --git a/app/code/Magento/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php b/app/code/Magento/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php new file mode 100644 index 0000000000000..e7b30d76f66b5 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php @@ -0,0 +1,24 @@ +getCanShowPrice() !== false && $salableItem->isSalable(); + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Pricing/Renderer/SalableResolverInterface.php b/app/code/Magento/Catalog/Model/Product/Pricing/Renderer/SalableResolverInterface.php new file mode 100644 index 0000000000000..1ad37292611d5 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Pricing/Renderer/SalableResolverInterface.php @@ -0,0 +1,21 @@ +salableResolver = $salableResolver ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(SalableResolverInterface::class); + } + /** * @return string */ protected function _toHtml() { - if (!$this->getSaleableItem() || $this->getSaleableItem()->getCanShowPrice() === false) { + if (!$this->salableResolver->isSalable($this->getSaleableItem())) { return ''; } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Pricing/Renderer/SalableResolverTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Pricing/Renderer/SalableResolverTest.php new file mode 100644 index 0000000000000..7ef15b0781931 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Pricing/Renderer/SalableResolverTest.php @@ -0,0 +1,60 @@ +product = $this->getMock( + \Magento\Catalog\Model\Product::class, + ['__wakeup', 'getCanShowPrice', 'isSalable'], + [], + '', + false + ); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->object = $objectManager->getObject( + \Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolver::class + ); + } + + public function testSalableItem() + { + $this->product->expects($this->any()) + ->method('getCanShowPrice') + ->willReturn(true); + + $this->product->expects($this->any())->method('isSalable')->willReturn(true); + + $result = $this->object->isSalable($this->product); + $this->assertTrue($result); + } + + public function testNotSalableItem() + { + $this->product->expects($this->any()) + ->method('getCanShowPrice') + ->willReturn(true); + + $this->product->expects($this->any())->method('isSalable')->willReturn(false); + + $result = $this->object->isSalable($this->product); + $this->assertFalse($result); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Pricing/Render/FinalPriceBoxTest.php b/app/code/Magento/Catalog/Test/Unit/Pricing/Render/FinalPriceBoxTest.php index d15afad70cf11..ba7905153e24e 100644 --- a/app/code/Magento/Catalog/Test/Unit/Pricing/Render/FinalPriceBoxTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Pricing/Render/FinalPriceBoxTest.php @@ -6,6 +6,8 @@ namespace Magento\Catalog\Test\Unit\Pricing\Render; +use Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolverInterface; + /** * Class FinalPriceBoxTest */ @@ -56,11 +58,16 @@ class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase */ protected $price; + /** + * @var SalableResolverInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $salableResolverMock; + protected function setUp() { $this->product = $this->getMock( - 'Magento\Catalog\Model\Product', - ['getPriceInfo', '__wakeup', 'getCanShowPrice'], + \Magento\Catalog\Model\Product::class, + ['getPriceInfo', '__wakeup', 'getCanShowPrice', 'isSalable'], [], '', false @@ -77,9 +84,7 @@ protected function setUp() $this->priceBox = $this->getMock('Magento\Framework\Pricing\Render\PriceBox', [], [], '', false); $this->logger = $this->getMock('Psr\Log\LoggerInterface'); - $this->layout->expects($this->any()) - ->method('getBlock') - ->will($this->returnValue($this->priceBox)); + $this->layout->expects($this->any())->method('getBlock')->willReturn($this->priceBox); $cacheState = $this->getMockBuilder(\Magento\Framework\App\Cache\StateInterface::class) ->getMockForAbstractClass(); @@ -92,13 +97,10 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); - $urlBuilder = $this->getMockBuilder(\Magento\Framework\UrlInterface::class) - ->getMockForAbstractClass(); + $urlBuilder = $this->getMockBuilder(\Magento\Framework\UrlInterface::class)->getMockForAbstractClass(); - $store = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) - ->getMockForAbstractClass(); - - $storeManager = $this->getMockBuilder('\Magento\Store\Model\StoreManagerInterface') + $store = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class)->getMockForAbstractClass(); + $storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) ->setMethods(['getStore', 'getCode']) ->getMockForAbstractClass(); $storeManager->expects($this->any())->method('getStore')->will($this->returnValue($store)); @@ -146,6 +148,10 @@ protected function setUp() ->will($this->returnValue(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE)); $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->salableResolverMock = $this->getMockBuilder(SalableResolverInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->object = $objectManager->getObject( 'Magento\Catalog\Pricing\Render\FinalPriceBox', [ @@ -153,7 +159,8 @@ protected function setUp() 'saleableItem' => $this->product, 'rendererPool' => $this->rendererPool, 'price' => $this->price, - 'data' => ['zone' => 'test_zone', 'list_category_page' => true] + 'data' => ['zone' => 'test_zone', 'list_category_page' => true], + 'salableResolver' => $this->salableResolverMock ] ); } @@ -171,6 +178,8 @@ public function testRenderMsrpDisabled() ->with($this->equalTo($this->product)) ->will($this->returnValue(false)); + $this->salableResolverMock->expects($this->once())->method('isSalable')->with($this->product)->willReturn(true); + $result = $this->object->toHtml(); //assert price wrapper @@ -179,6 +188,18 @@ public function testRenderMsrpDisabled() $this->assertRegExp('/[final_price]/', $result); } + public function testNotSalableItem() + { + $this->salableResolverMock + ->expects($this->once()) + ->method('isSalable') + ->with($this->product) + ->willReturn(false); + $result = $this->object->toHtml(); + + $this->assertEmpty($result); + } + public function testRenderMsrpEnabled() { $priceType = $this->getMock('Magento\Msrp\Pricing\Price\MsrpPrice', [], [], '', false); @@ -213,6 +234,8 @@ public function testRenderMsrpEnabled() ->with('msrp_price', $this->product, $arguments) ->will($this->returnValue($priceBoxRender)); + $this->salableResolverMock->expects($this->once())->method('isSalable')->with($this->product)->willReturn(true); + $result = $this->object->toHtml(); //assert price wrapper @@ -232,6 +255,8 @@ public function testRenderMsrpNotRegisteredException() ->with($this->equalTo('msrp_price')) ->will($this->throwException(new \InvalidArgumentException())); + $this->salableResolverMock->expects($this->once())->method('isSalable')->with($this->product)->willReturn(true); + $result = $this->object->toHtml(); //assert price wrapper diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml index 89d9bf7734f2e..a01ae422fdd0f 100644 --- a/app/code/Magento/Catalog/etc/di.xml +++ b/app/code/Magento/Catalog/etc/di.xml @@ -44,6 +44,7 @@ + @@ -615,7 +616,7 @@ - + Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php index 68e82ed76a23f..eb3040ad2f668 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php @@ -64,11 +64,6 @@ public function resolvePrice(\Magento\Framework\Pricing\SaleableInterface $produ $productPrice = $this->priceResolver->resolvePrice($subProduct); $price = $price ? min($price, $productPrice) : $productPrice; } - if ($price === null) { - throw new \Magento\Framework\Exception\LocalizedException( - __('Configurable product "%1" does not have sub-products', $product->getSku()) - ); - } return (float)$price; } diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php index 8db61bb5e0a43..78ac3a3097458 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php @@ -51,24 +51,6 @@ protected function setUp() ); } - /** - * situation: There are no used products, thus there are no prices - * - * @expectedException \Magento\Framework\Exception\LocalizedException - */ - public function testResolvePriceWithNoPrices() - { - $product = $this->getMockBuilder( - \Magento\Catalog\Model\Product::class - )->disableOriginalConstructor()->getMock(); - - $product->expects($this->once())->method('getSku')->willReturn('Kiwi'); - - $this->lowestPriceOptionsProvider->expects($this->once())->method('getProducts')->willReturn([]); - - $this->resolver->resolvePrice($product); - } - /** * situation: one product is supplying the price, which could be a price of zero (0) * diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php index b102e1d81f48e..f757d483a1897 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php @@ -3,139 +3,407 @@ * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\ConfigurableProduct\Test\Unit\Pricing\Render; -use Magento\Catalog\Pricing\Price\FinalPrice; -use Magento\Catalog\Pricing\Price\RegularPrice; -use Magento\ConfigurableProduct\Pricing\Price\LowestPriceOptionsProviderInterface; -use Magento\ConfigurableProduct\Pricing\Render\FinalPriceBox; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolverInterface; +/** + * Class FinalPriceBoxTest + */ class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase { /** - * @var \Magento\Framework\View\Element\Template\Context|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Catalog\Pricing\Render\FinalPriceBox + */ + protected $object; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $priceType; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $priceInfo; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $priceBox; + + /** + * @var \Magento\Framework\View\LayoutInterface | \PHPUnit_Framework_MockObject_MockObject */ - private $context; + protected $layout; /** * @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject */ - private $saleableItem; + protected $product; /** - * @var \Magento\Framework\Pricing\Price\PriceInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $price; + protected $logger; /** * @var \Magento\Framework\Pricing\Render\RendererPool|\PHPUnit_Framework_MockObject_MockObject */ - private $rendererPool; + protected $rendererPool; /** - * @var LowestPriceOptionsProviderInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Pricing\Price\PriceInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $lowestPriceOptionsProvider; + protected $price; /** - * @var FinalPriceBox + * @var SalableResolverInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $model; + private $salableResolverMock; protected function setUp() { - $this->context = $this->getMockBuilder(\Magento\Framework\View\Element\Template\Context::class) + $this->product = $this->getMock( + \Magento\Catalog\Model\Product::class, + ['getPriceInfo', '__wakeup', 'getCanShowPrice', 'isSalable'], + [], + '', + false + ); + $this->priceInfo = $this->getMock('Magento\Framework\Pricing\PriceInfo', ['getPrice'], [], '', false); + $this->product->expects($this->any()) + ->method('getPriceInfo') + ->will($this->returnValue($this->priceInfo)); + + $eventManager = $this->getMock('Magento\Framework\Event\Test\Unit\ManagerStub', [], [], '', false); + $config = $this->getMock('Magento\Store\Model\Store\Config', [], [], '', false); + $this->layout = $this->getMock('Magento\Framework\View\Layout', [], [], '', false); + + $this->priceBox = $this->getMock('Magento\Framework\Pricing\Render\PriceBox', [], [], '', false); + $this->logger = $this->getMock('Psr\Log\LoggerInterface'); + + $this->layout->expects($this->any())->method('getBlock')->willReturn($this->priceBox); + + $cacheState = $this->getMockBuilder(\Magento\Framework\App\Cache\StateInterface::class) + ->getMockForAbstractClass(); + + $appState = $this->getMockBuilder(\Magento\Framework\App\State::class) ->disableOriginalConstructor() ->getMock(); - $this->saleableItem = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + $resolver = $this->getMockBuilder(\Magento\Framework\View\Element\Template\File\Resolver::class) ->disableOriginalConstructor() ->getMock(); - $this->price = $this->getMockBuilder(\Magento\Framework\Pricing\Price\PriceInterface::class) + $urlBuilder = $this->getMockBuilder(\Magento\Framework\UrlInterface::class)->getMockForAbstractClass(); + + $store = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class)->getMockForAbstractClass(); + $storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) + ->setMethods(['getStore', 'getCode']) ->getMockForAbstractClass(); + $storeManager->expects($this->any())->method('getStore')->will($this->returnValue($store)); + + $scopeConfigMock = $this->getMockForAbstractClass('Magento\Framework\App\Config\ScopeConfigInterface'); + $context = $this->getMock('Magento\Framework\View\Element\Template\Context', [], [], '', false); + $context->expects($this->any()) + ->method('getEventManager') + ->will($this->returnValue($eventManager)); + $context->expects($this->any()) + ->method('getStoreConfig') + ->will($this->returnValue($config)); + $context->expects($this->any()) + ->method('getLayout') + ->will($this->returnValue($this->layout)); + $context->expects($this->any()) + ->method('getLogger') + ->will($this->returnValue($this->logger)); + $context->expects($this->any()) + ->method('getScopeConfig') + ->will($this->returnValue($scopeConfigMock)); + $context->expects($this->any()) + ->method('getCacheState') + ->will($this->returnValue($cacheState)); + $context->expects($this->any()) + ->method('getStoreManager') + ->will($this->returnValue($storeManager)); + $context->expects($this->any()) + ->method('getAppState') + ->will($this->returnValue($appState)); + $context->expects($this->any()) + ->method('getResolver') + ->will($this->returnValue($resolver)); + $context->expects($this->any()) + ->method('getUrlBuilder') + ->will($this->returnValue($urlBuilder)); - $this->rendererPool = $this->getMockBuilder(\Magento\Framework\Pricing\Render\RendererPool::class) + $this->rendererPool = $this->getMockBuilder('Magento\Framework\Pricing\Render\RendererPool') ->disableOriginalConstructor() ->getMock(); - $this->lowestPriceOptionsProvider = $this->getMockBuilder(LowestPriceOptionsProviderInterface::class) + $this->price = $this->getMock('Magento\Framework\Pricing\Price\PriceInterface'); + $this->price->expects($this->any()) + ->method('getPriceCode') + ->will($this->returnValue(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE)); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->salableResolverMock = $this->getMockBuilder(SalableResolverInterface::class) + ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->model = (new ObjectManager($this))->getObject( - FinalPriceBox::class, + $this->object = $objectManager->getObject( + 'Magento\Catalog\Pricing\Render\FinalPriceBox', [ - 'context' => $this->context, - 'saleableItem' => $this->saleableItem, - 'price' => $this->price, + 'context' => $context, + 'saleableItem' => $this->product, 'rendererPool' => $this->rendererPool, - 'lowestPriceOptionsProvider' => $this->lowestPriceOptionsProvider, + 'price' => $this->price, + 'data' => ['zone' => 'test_zone', 'list_category_page' => true], + 'salableResolver' => $this->salableResolverMock ] ); } - /** - * @param float $regularPrice - * @param float $finalPrice - * @param bool $expected - * @dataProvider hasSpecialPriceDataProvider - */ - public function testHasSpecialPrice( - $regularPrice, - $finalPrice, - $expected - ) { - $priceMockOne = $this->getMockBuilder(\Magento\Framework\Pricing\Price\PriceInterface::class) - ->getMockForAbstractClass(); + public function testRenderMsrpDisabled() + { + $priceType = $this->getMock('Magento\Msrp\Pricing\Price\MsrpPrice', [], [], '', false); + $this->priceInfo->expects($this->once()) + ->method('getPrice') + ->with($this->equalTo('msrp_price')) + ->will($this->returnValue($priceType)); - $priceMockOne->expects($this->once()) - ->method('getValue') - ->willReturn($regularPrice); + $priceType->expects($this->any()) + ->method('canApplyMsrp') + ->with($this->equalTo($this->product)) + ->will($this->returnValue(false)); - $priceMockTwo = $this->getMockBuilder(\Magento\Framework\Pricing\Price\PriceInterface::class) - ->getMockForAbstractClass(); + $this->salableResolverMock->expects($this->once())->method('isSalable')->with($this->product)->willReturn(true); - $priceMockTwo->expects($this->once()) - ->method('getValue') - ->willReturn($finalPrice); + $result = $this->object->toHtml(); - $priceInfoMock = $this->getMockBuilder(\Magento\Framework\Pricing\PriceInfo\Base::class) + //assert price wrapper + $this->assertStringStartsWith('assertRegExp('/[final_price]/', $result); + } + + public function testNotSalableItem() + { + $this->salableResolverMock + ->expects($this->once()) + ->method('isSalable') + ->with($this->product) + ->willReturn(false); + $result = $this->object->toHtml(); + + $this->assertEmpty($result); + } + + public function testRenderMsrpEnabled() + { + $priceType = $this->getMock('Magento\Msrp\Pricing\Price\MsrpPrice', [], [], '', false); + $this->priceInfo->expects($this->once()) + ->method('getPrice') + ->with($this->equalTo('msrp_price')) + ->will($this->returnValue($priceType)); + + $priceType->expects($this->any()) + ->method('canApplyMsrp') + ->with($this->equalTo($this->product)) + ->will($this->returnValue(true)); + + $priceType->expects($this->any()) + ->method('isMinimalPriceLessMsrp') + ->with($this->equalTo($this->product)) + ->will($this->returnValue(true)); + + $priceBoxRender = $this->getMockBuilder('Magento\Framework\Pricing\Render\PriceBox') ->disableOriginalConstructor() ->getMock(); + $priceBoxRender->expects($this->once()) + ->method('toHtml') + ->will($this->returnValue('test')); + + $arguments = [ + 'real_price_html' => '', + 'zone' => 'test_zone', + ]; + $this->rendererPool->expects($this->once()) + ->method('createPriceRender') + ->with('msrp_price', $this->product, $arguments) + ->will($this->returnValue($priceBoxRender)); + + $this->salableResolverMock->expects($this->once())->method('isSalable')->with($this->product)->willReturn(true); - $priceInfoMock->expects($this->exactly(2)) + $result = $this->object->toHtml(); + + //assert price wrapper + $this->assertEquals( + '
test
', + $result + ); + } + + public function testRenderMsrpNotRegisteredException() + { + $this->logger->expects($this->once()) + ->method('critical'); + + $this->priceInfo->expects($this->once()) ->method('getPrice') - ->willReturnMap([ - [RegularPrice::PRICE_CODE, $priceMockOne], - [FinalPrice::PRICE_CODE, $priceMockTwo], - ]); + ->with($this->equalTo('msrp_price')) + ->will($this->throwException(new \InvalidArgumentException())); - $productMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) - ->setMethods(['getPriceInfo']) - ->getMockForAbstractClass(); + $this->salableResolverMock->expects($this->once())->method('isSalable')->with($this->product)->willReturn(true); - $productMock->expects($this->exactly(2)) - ->method('getPriceInfo') - ->willReturn($priceInfoMock); + $result = $this->object->toHtml(); + + //assert price wrapper + $this->assertStringStartsWith('assertRegExp('/[final_price]/', $result); + } + + public function testRenderAmountMinimal() + { + $priceType = $this->getMock('Magento\Catalog\Pricing\Price\FinalPrice', [], [], '', false); + $amount = $this->getMockForAbstractClass('Magento\Framework\Pricing\Amount\AmountInterface'); + $priceId = 'price_id'; + $html = 'html'; + $this->object->setData('price_id', $priceId); + + $arguments = [ + 'zone' => 'test_zone', + 'list_category_page' => true, + 'display_label' => 'As low as', + 'price_id' => $priceId, + 'include_container' => false, + 'skip_adjustments' => true, + ]; - $this->lowestPriceOptionsProvider->expects($this->once()) - ->method('getProducts') - ->with($this->saleableItem) - ->willReturn([$productMock]); + $amountRender = $this->getMock('Magento\Framework\Pricing\Render\Amount', ['toHtml'], [], '', false); + $amountRender->expects($this->once()) + ->method('toHtml') + ->will($this->returnValue($html)); - $this->assertEquals($expected, $this->model->hasSpecialPrice()); + $this->priceInfo->expects($this->once()) + ->method('getPrice') + ->with(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE) + ->will($this->returnValue($priceType)); + + $priceType->expects($this->once()) + ->method('getMinimalPrice') + ->will($this->returnValue($amount)); + + $this->rendererPool->expects($this->once()) + ->method('createAmountRender') + ->with($amount, $this->product, $this->price, $arguments) + ->will($this->returnValue($amountRender)); + + $this->assertEquals($html, $this->object->renderAmountMinimal()); } /** - * @return array + * @dataProvider hasSpecialPriceProvider + * @param float $regularPrice + * @param float $finalPrice + * @param bool $expectedResult */ - public function hasSpecialPriceDataProvider() + public function testHasSpecialPrice($regularPrice, $finalPrice, $expectedResult) + { + $regularPriceType = $this->getMock('Magento\Catalog\Pricing\Price\RegularPrice', [], [], '', false); + $finalPriceType = $this->getMock('Magento\Catalog\Pricing\Price\FinalPrice', [], [], '', false); + $regularPriceAmount = $this->getMockForAbstractClass('Magento\Framework\Pricing\Amount\AmountInterface'); + $finalPriceAmount = $this->getMockForAbstractClass('Magento\Framework\Pricing\Amount\AmountInterface'); + + $regularPriceAmount->expects($this->once()) + ->method('getValue') + ->will($this->returnValue($regularPrice)); + $finalPriceAmount->expects($this->once()) + ->method('getValue') + ->will($this->returnValue($finalPrice)); + + $regularPriceType->expects($this->once()) + ->method('getAmount') + ->will($this->returnValue($regularPriceAmount)); + $finalPriceType->expects($this->once()) + ->method('getAmount') + ->will($this->returnValue($finalPriceAmount)); + + $this->priceInfo->expects($this->at(0)) + ->method('getPrice') + ->with(\Magento\Catalog\Pricing\Price\RegularPrice::PRICE_CODE) + ->will($this->returnValue($regularPriceType)); + $this->priceInfo->expects($this->at(1)) + ->method('getPrice') + ->with(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE) + ->will($this->returnValue($finalPriceType)); + + $this->assertEquals($expectedResult, $this->object->hasSpecialPrice()); + } + + public function hasSpecialPriceProvider() { return [ - [10., 20., false], - [10., 10., false], - [20., 10., true], + [10.0, 20.0, false], + [20.0, 10.0, true], + [10.0, 10.0, false] ]; } + + public function testShowMinimalPrice() + { + $finalPrice = 10.0; + $minimalPrice = 5.0; + $displayMininmalPrice = 2.0; + + $this->object->setDisplayMinimalPrice($displayMininmalPrice); + + $finalPriceType = $this->getMock('Magento\Catalog\Pricing\Price\FinalPrice', [], [], '', false); + + $finalPriceAmount = $this->getMockForAbstractClass('Magento\Framework\Pricing\Amount\AmountInterface'); + $minimalPriceAmount = $this->getMockForAbstractClass('Magento\Framework\Pricing\Amount\AmountInterface'); + + $finalPriceAmount->expects($this->once()) + ->method('getValue') + ->will($this->returnValue($finalPrice)); + $minimalPriceAmount->expects($this->once()) + ->method('getValue') + ->will($this->returnValue($minimalPrice)); + + $finalPriceType->expects($this->at(0)) + ->method('getAmount') + ->will($this->returnValue($finalPriceAmount)); + $finalPriceType->expects($this->at(1)) + ->method('getMinimalPrice') + ->will($this->returnValue($minimalPriceAmount)); + + $this->priceInfo->expects($this->once()) + ->method('getPrice') + ->with(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE) + ->will($this->returnValue($finalPriceType)); + + $this->assertTrue($this->object->showMinimalPrice()); + } + + public function testHidePrice() + { + $this->product->expects($this->any()) + ->method('getCanShowPrice') + ->will($this->returnValue(false)); + + $this->assertEmpty($this->object->toHtml()); + } + + public function testGetCacheKey() + { + $result = $this->object->getCacheKey(); + $this->assertStringEndsWith('list-category-page', $result); + } + + public function testGetCacheKeyInfo() + { + $this->assertArrayHasKey('display_minimal_price', $this->object->getCacheKeyInfo()); + } } From 25fb12209a084d520e4a1bb4fe27ffbead81e320 Mon Sep 17 00:00:00 2001 From: Illia Grybkov Date: Wed, 30 Nov 2016 10:31:19 +0200 Subject: [PATCH 3/3] MAGETWO-60605: Exception when adding configurable product by sku from customer account if associated simple product is out of stock --- .../Pricing/Price/ConfigurablePriceResolver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php index 4fc7a2869b61a..c64c1e2157eba 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php @@ -53,7 +53,7 @@ public function __construct( /** * @param \Magento\Framework\Pricing\SaleableInterface|\Magento\Catalog\Model\Product $product - * @return float | null + * @return float|null */ public function resolvePrice(\Magento\Framework\Pricing\SaleableInterface $product) {