diff --git a/app/code/Magento/AdminNotification/Setup/InstallSchema.php b/app/code/Magento/AdminNotification/Setup/InstallSchema.php index 31b8842ee72e5502ff200716c3761e519c378a45..f8118f245e8ff3cebc4cdf1e2adfc5ee3075a4ba 100644 --- a/app/code/Magento/AdminNotification/Setup/InstallSchema.php +++ b/app/code/Magento/AdminNotification/Setup/InstallSchema.php @@ -45,7 +45,7 @@ class InstallSchema implements InstallSchemaInterface 'date_added', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Create date' )->addColumn( 'title', @@ -112,7 +112,7 @@ class InstallSchema implements InstallSchemaInterface 'created_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Create date' )->setComment( 'Admin System Messages' diff --git a/app/code/Magento/Bundle/Api/Data/LinkInterface.php b/app/code/Magento/Bundle/Api/Data/LinkInterface.php index e16fad7f555bee04fb7f42743598acff877c6c6b..4e2268eae8f9a533b2bed55f7a4e97990f05d62e 100644 --- a/app/code/Magento/Bundle/Api/Data/LinkInterface.php +++ b/app/code/Magento/Bundle/Api/Data/LinkInterface.php @@ -9,6 +9,24 @@ namespace Magento\Bundle\Api\Data; interface LinkInterface extends \Magento\Framework\Api\ExtensibleDataInterface { + const PRICE_TYPE_FIXED = 0; + const PRICE_TYPE_PERCENT = 1; + + /** + * Get the identifier + * + * @return string|null + */ + public function getId(); + + /** + * Set id + * + * @param string $id + * @return $this + */ + public function setId($id); + /** * Get linked product sku * @@ -69,21 +87,6 @@ interface LinkInterface extends \Magento\Framework\Api\ExtensibleDataInterface */ public function setPosition($position); - /** - * Get is defined - * - * @return bool|null - */ - public function getIsDefined(); - - /** - * Set is defined - * - * @param bool $isDefined - * @return $this - */ - public function setIsDefined($isDefined); - /** * Get is default * diff --git a/app/code/Magento/Bundle/Api/ProductLinkManagementInterface.php b/app/code/Magento/Bundle/Api/ProductLinkManagementInterface.php index 2109cde0f95d452be9456038b8cfc2fa8ca30726..0529831f4094f7b936550e37914b91208f3d282e 100644 --- a/app/code/Magento/Bundle/Api/ProductLinkManagementInterface.php +++ b/app/code/Magento/Bundle/Api/ProductLinkManagementInterface.php @@ -11,12 +11,13 @@ interface ProductLinkManagementInterface /** * Get all children for Bundle product * - * @param string $productId + * @param string $productSku + * @param int $optionId * @return \Magento\Bundle\Api\Data\LinkInterface[] * @throws \Magento\Framework\Exception\NoSuchEntityException * @throws \Magento\Framework\Exception\InputException */ - public function getChildren($productId); + public function getChildren($productSku, $optionId = null); /** * Add child product to specified Bundle option by product sku @@ -31,10 +32,23 @@ interface ProductLinkManagementInterface */ public function addChildByProductSku($sku, $optionId, \Magento\Bundle\Api\Data\LinkInterface $linkedProduct); + /** + * @param string $sku + * @param \Magento\Bundle\Api\Data\LinkInterface $linkedProduct + * @throws \Magento\Framework\Exception\NoSuchEntityException + * @throws \Magento\Framework\Exception\CouldNotSaveException + * @throws \Magento\Framework\Exception\InputException + * @return bool + */ + public function saveChild( + $sku, + \Magento\Bundle\Api\Data\LinkInterface $linkedProduct + ); + /** * @param \Magento\Catalog\Api\Data\ProductInterface $product * @param int $optionId - * @param Data\LinkInterface $linkedProduct + * @param \Magento\Bundle\Api\Data\LinkInterface $linkedProduct * @throws \Magento\Framework\Exception\NoSuchEntityException * @throws \Magento\Framework\Exception\CouldNotSaveException * @throws \Magento\Framework\Exception\InputException diff --git a/app/code/Magento/Bundle/Model/Link.php b/app/code/Magento/Bundle/Model/Link.php index eb99eff55e334fd9c878af2054243956b76a115c..fef35d3261b030978b73d67377a75ebf4c10a491 100644 --- a/app/code/Magento/Bundle/Model/Link.php +++ b/app/code/Magento/Bundle/Model/Link.php @@ -16,17 +16,34 @@ class Link extends \Magento\Framework\Model\AbstractExtensibleModel implements /**#@+ * Constants */ + const KEY_ID = 'id'; const KEY_SKU = 'sku'; const KEY_OPTION_ID = 'option_id'; const KEY_QTY = 'qty'; const KEY_POSITION = 'position'; - const KEY_IS_DEFINED = 'is_defined'; const KEY_IS_DEFAULT = 'is_default'; const KEY_PRICE = 'price'; const KEY_PRICE_TYPE = 'price_type'; - const KEY_CAN_CHANGE_QUANTITY = 'can_change_quantity'; + const KEY_CAN_CHANGE_QUANTITY = 'selection_can_change_quantity'; /**#@-*/ + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->getData(self::KEY_ID); + } + + /** + * {@inheritdoc} + */ + public function setId($id) + { + return $this->setData(self::KEY_ID, $id); + } + + /** * {@inheritdoc} */ @@ -59,14 +76,6 @@ class Link extends \Magento\Framework\Model\AbstractExtensibleModel implements return $this->getData(self::KEY_POSITION); } - /** - * {@inheritdoc} - */ - public function getIsDefined() - { - return $this->getData(self::KEY_IS_DEFINED); - } - /** * {@inheritdoc} */ @@ -143,17 +152,6 @@ class Link extends \Magento\Framework\Model\AbstractExtensibleModel implements return $this->setData(self::KEY_POSITION, $position); } - /** - * Set is defined - * - * @param bool $isDefined - * @return $this - */ - public function setIsDefined($isDefined) - { - return $this->setData(self::KEY_IS_DEFINED, $isDefined); - } - /** * Set is default * diff --git a/app/code/Magento/Bundle/Model/LinkManagement.php b/app/code/Magento/Bundle/Model/LinkManagement.php index fe2c0d85d7b168d21183813afc8b72ffbb9f280e..5d240c0517e595fc5812c5c1ad4e5d1d23530b6d 100644 --- a/app/code/Magento/Bundle/Model/LinkManagement.php +++ b/app/code/Magento/Bundle/Model/LinkManagement.php @@ -75,15 +75,18 @@ class LinkManagement implements \Magento\Bundle\Api\ProductLinkManagementInterfa /** * {@inheritdoc} */ - public function getChildren($productId) + public function getChildren($productSku, $optionId = null) { - $product = $this->productRepository->get($productId); + $product = $this->productRepository->get($productSku); if ($product->getTypeId() != \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE) { throw new InputException(__('Only implemented for bundle product')); } $childrenList = []; foreach ($this->getOptions($product) as $option) { + if ($optionId !== null && $option->getOptionId() != $optionId) { + continue; + } /** @var \Magento\Catalog\Model\Product $selection */ foreach ($option->getSelections() as $selection) { $childrenList[] = $this->buildLink($selection, $product); @@ -107,6 +110,93 @@ class LinkManagement implements \Magento\Bundle\Api\ProductLinkManagementInterfa * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ + public function saveChild( + $sku, + \Magento\Bundle\Api\Data\LinkInterface $linkedProduct + ) { + $product = $this->productRepository->get($sku); + if ($product->getTypeId() != \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE) { + throw new InputException( + __('Product with specified sku: "%1" is not a bundle product', [$product->getSku()]) + ); + } + + /** @var \Magento\Catalog\Model\Product $linkProductModel */ + $linkProductModel = $this->productRepository->get($linkedProduct->getSku()); + if ($linkProductModel->isComposite()) { + throw new InputException(__('Bundle product could not contain another composite product')); + } + + if (!$linkedProduct->getId()) { + throw new InputException(__('Id field of product link is required')); + } + + /** @var \Magento\Bundle\Model\Selection $selectionModel */ + $selectionModel = $this->bundleSelection->create(); + $selectionModel->load($linkedProduct->getId()); + if (!$selectionModel->getId()) { + throw new InputException(__('Can not find product link with id "%1"', [$linkedProduct->getId()])); + } + + $selectionModel = $this->mapProductLinkToSelectionModel( + $selectionModel, + $linkedProduct, + $linkProductModel->getId(), + $product->getId() + ); + + try { + $selectionModel->save(); + } catch (\Exception $e) { + throw new CouldNotSaveException(__('Could not save child: "%1"', $e->getMessage()), $e); + } + + return true; + } + + /** + * @param \Magento\Bundle\Model\Selection $selectionModel + * @param \Magento\Bundle\Api\Data\LinkInterface $productLink + * @param string $linkedProductId + * @param string $parentProductId + * @return \Magento\Bundle\Model\Selection + */ + protected function mapProductLinkToSelectionModel( + \Magento\Bundle\Model\Selection $selectionModel, + \Magento\Bundle\Api\Data\LinkInterface $productLink, + $linkedProductId, + $parentProductId + ) { + $selectionModel->setProductId($linkedProductId); + $selectionModel->setParentProductId($parentProductId); + if (($productLink->getOptionId() !== null)) { + $selectionModel->setOptionId($productLink->getOptionId()); + } + if ($productLink->getPosition() !== null) { + $selectionModel->setPosition($productLink->getPosition()); + } + if ($productLink->getQty() !== null) { + $selectionModel->setSelectionQty($productLink->getQty()); + } + if ($productLink->getPriceType() !== null) { + $selectionModel->setSelectionPriceType($productLink->getPriceType()); + } + if ($productLink->getPrice() !== null) { + $selectionModel->setSelectionPriceValue($productLink->getPrice()); + } + if ($productLink->getCanChangeQuantity() !== null) { + $selectionModel->setSelectionCanChangeQty($productLink->getCanChangeQuantity()); + } + if ($productLink->getIsDefault() !== null) { + $selectionModel->setIsDefault($productLink->getIsDefault()); + } + + return $selectionModel; + } + + /** + * {@inheritdoc} + */ public function addChild( \Magento\Catalog\Api\Data\ProductInterface $product, $optionId, @@ -119,17 +209,10 @@ class LinkManagement implements \Magento\Bundle\Api\ProductLinkManagementInterfa } $options = $this->optionCollection->create(); - $options->setProductIdFilter($product->getId())->joinValues($this->storeManager->getStore()->getId()); - $isNewOption = true; - /** @var \Magento\Bundle\Model\Option $option */ - foreach ($options as $option) { - if ($option->getOptionId() == $optionId) { - $isNewOption = false; - break; - } - } + $options->setIdFilter($optionId); + $existingOption = $options->getFirstItem(); - if ($isNewOption) { + if (!$existingOption->getId()) { throw new InputException( __( 'Product with specified sku: "%1" does not contain option: "%2"', @@ -161,16 +244,13 @@ class LinkManagement implements \Magento\Bundle\Api\ProductLinkManagementInterfa } $selectionModel = $this->bundleSelection->create(); - $selectionModel->setOptionId($optionId) - ->setPosition($linkedProduct->getPosition()) - ->setSelectionQty($linkedProduct->getQty()) - ->setSelectionPriceType($linkedProduct->getPriceType()) - ->setSelectionPriceValue($linkedProduct->getPrice()) - ->setSelectionCanChangeQty($linkedProduct->getCanChangeQuantity()) - ->setProductId($linkProductModel->getId()) - ->setParentProductId($product->getId()) - ->setIsDefault($linkedProduct->getIsDefault()) - ->setWebsiteId($this->storeManager->getStore()->getWebsiteId()); + $selectionModel = $this->mapProductLinkToSelectionModel( + $selectionModel, + $linkedProduct, + $linkProductModel->getId(), + $product->getId() + ); + $selectionModel->setOptionId($optionId); try { $selectionModel->save(); @@ -242,8 +322,9 @@ class LinkManagement implements \Magento\Bundle\Api\ProductLinkManagementInterfa '\Magento\Bundle\Api\Data\LinkInterface' ); $link->setIsDefault($selection->getIsDefault()) + ->setId($selection->getSelectionId()) ->setQty($selection->getSelectionQty()) - ->setIsDefined($selection->getSelectionCanChangeQty()) + ->setCanChangeQuantity($selection->getSelectionCanChangeQty()) ->setPrice($selectionPrice) ->setPriceType($selectionPriceType); return $link; @@ -251,7 +332,7 @@ class LinkManagement implements \Magento\Bundle\Api\ProductLinkManagementInterfa /** * @param \Magento\Catalog\Api\Data\ProductInterface $product - * @return \Magento\Bundle\Api\Data\OptionTypeInterface[] + * @return \Magento\Bundle\Api\Data\OptionInterface[] */ private function getOptions(\Magento\Catalog\Api\Data\ProductInterface $product) { diff --git a/app/code/Magento/Bundle/Model/OptionRepository.php b/app/code/Magento/Bundle/Model/OptionRepository.php index 024b6f1cd2482780901ce6a8ca475a54e8e8785e..78caa83e801b3fd014eadfcfbbaf074c554d23f5 100644 --- a/app/code/Magento/Bundle/Model/OptionRepository.php +++ b/app/code/Magento/Bundle/Model/OptionRepository.php @@ -161,7 +161,6 @@ class OptionRepository implements \Magento\Bundle\Api\ProductOptionRepositoryInt /** * {@inheritdoc} - * @SuppressWarnings(PHPMD.NPathComplexity) */ public function save( \Magento\Catalog\Api\Data\ProductInterface $product, @@ -170,35 +169,25 @@ class OptionRepository implements \Magento\Bundle\Api\ProductOptionRepositoryInt $option->setStoreId($this->storeManager->getStore()->getId()); $option->setParentId($product->getId()); - if (!$option->getOptionId()) { + $optionId = $option->getOptionId(); + $linksToAdd = []; + if (!$optionId) { $option->setDefaultTitle($option->getTitle()); - $linksToAdd = is_array($option->getProductLinks()) ? $option->getProductLinks() : []; + if (is_array($option->getProductLinks())) { + $linksToAdd = $option->getProductLinks(); + } } else { $optionCollection = $this->type->getOptionsCollection($product); - $optionCollection->setIdFilter($option->getOptionId()); /** @var \Magento\Bundle\Model\Option $existingOption */ - $existingOption = $optionCollection->getFirstItem(); + $existingOption = $optionCollection->getItemById($option->getOptionId()); - if (!$existingOption->getOptionId()) { + if (!isset($existingOption) || !$existingOption->getOptionId()) { throw new NoSuchEntityException(__('Requested option doesn\'t exist')); } $option->setData(array_merge($existingOption->getData(), $option->getData())); - - /** @var \Magento\Bundle\Api\Data\LinkInterface[] $existingLinks */ - $existingLinks = is_array($existingOption->getProductLinks()) ? $existingOption->getProductLinks() : []; - - /** @var \Magento\Bundle\Api\Data\LinkInterface[] $newProductLinks */ - $newProductLinks = is_array($option->getProductLinks()) ? $option->getProductLinks() : []; - - /** @var \Magento\Bundle\Api\Data\LinkInterface[] $linksToDelete */ - $linksToDelete = array_udiff($existingLinks, $newProductLinks, [$this, 'compareLinks']); - foreach ($linksToDelete as $link) { - $this->linkManagement->removeChild($product->getSku(), $option->getOptionId(), $link->getSku()); - } - /** @var \Magento\Bundle\Api\Data\LinkInterface[] $linksToAdd */ - $linksToAdd = array_udiff($newProductLinks, $existingLinks, [$this, 'compareLinks']); + $this->updateOptionSelection($product, $option); } try { @@ -215,6 +204,50 @@ class OptionRepository implements \Magento\Bundle\Api\ProductOptionRepositoryInt return $option->getOptionId(); } + /** + * Update option selections + * + * @param \Magento\Catalog\Api\Data\ProductInterface $product + * @param \Magento\Bundle\Api\Data\OptionInterface $option + * @return $this + */ + protected function updateOptionSelection( + \Magento\Catalog\Api\Data\ProductInterface $product, + \Magento\Bundle\Api\Data\OptionInterface $option + ) { + $optionId = $option->getOptionId(); + $existingLinks = $this->linkManagement->getChildren($product->getSku(), $optionId); + $linksToAdd = []; + $linksToUpdate = []; + $linksToDelete = []; + if (is_array($option->getProductLinks())) { + $productLinks = $option->getProductLinks(); + foreach ($productLinks as $productLink) { + if (!$productLink->getId()) { + $linksToAdd[] = $productLink; + } else { + $linksToUpdate[] = $productLink; + } + } + /** @var \Magento\Bundle\Api\Data\LinkInterface[] $linksToDelete */ + $linksToDelete = array_udiff($existingLinks, $linksToUpdate, [$this, 'compareLinks']); + } + foreach ($linksToUpdate as $linkedProduct) { + $this->linkManagement->saveChild($product->getSku(), $linkedProduct); + } + foreach ($linksToDelete as $linkedProduct) { + $this->linkManagement->removeChild( + $product->getSku(), + $option->getOptionId(), + $linkedProduct->getSku() + ); + } + foreach ($linksToAdd as $linkedProduct) { + $this->linkManagement->addChild($product, $option->getOptionId(), $linkedProduct); + } + return $this; + } + /** * @param string $sku * @return \Magento\Catalog\Api\Data\ProductInterface @@ -241,7 +274,7 @@ class OptionRepository implements \Magento\Bundle\Api\ProductOptionRepositoryInt \Magento\Bundle\Api\Data\LinkInterface $firstLink, \Magento\Bundle\Api\Data\LinkInterface $secondLink ) { - if ($firstLink->getSku() == $secondLink->getSku()) { + if ($firstLink->getId() == $secondLink->getId()) { return 0; } else { return 1; diff --git a/app/code/Magento/Bundle/Model/Plugin/BundleLoadOptions.php b/app/code/Magento/Bundle/Model/Plugin/BundleLoadOptions.php index 76b5dd84b3620c4ee3fa4da5eae8fc6490b602ce..16bce777104a27d1a45a9ad0c26bc7282f76a000 100644 --- a/app/code/Magento/Bundle/Model/Plugin/BundleLoadOptions.php +++ b/app/code/Magento/Bundle/Model/Plugin/BundleLoadOptions.php @@ -52,7 +52,10 @@ class BundleLoadOptions return $product; } - $productExtension = $this->productExtensionFactory->create(); + $productExtension = $product->getExtensionAttributes(); + if ($productExtension === null) { + $productExtension = $this->productExtensionFactory->create(); + } $productExtension->setBundleProductOptions($this->productOptionList->getItems($product)); $product->setExtensionAttributes($productExtension); diff --git a/app/code/Magento/Bundle/Model/Plugin/BundleSaveOptions.php b/app/code/Magento/Bundle/Model/Plugin/BundleSaveOptions.php index b0e9ee6de4700da4651aee4c31aaed0b03db657b..ccfd78d1054804c09bafbe0e38c2b1675f3efd51 100644 --- a/app/code/Magento/Bundle/Model/Plugin/BundleSaveOptions.php +++ b/app/code/Magento/Bundle/Model/Plugin/BundleSaveOptions.php @@ -17,8 +17,9 @@ class BundleSaveOptions /** * @param \Magento\Bundle\Api\ProductOptionRepositoryInterface $optionRepository */ - public function __construct(\Magento\Bundle\Api\ProductOptionRepositoryInterface $optionRepository) - { + public function __construct( + \Magento\Bundle\Api\ProductOptionRepositoryInterface $optionRepository + ) { $this->optionRepository = $optionRepository; } @@ -45,13 +46,37 @@ class BundleSaveOptions } /* @var \Magento\Bundle\Api\Data\OptionInterface[] $options */ - $bundleProductOptions = $product->getExtensionAttributes()->getBundleProductOptions(); + $extendedAttributes = $product->getExtensionAttributes(); + if ($extendedAttributes === null) { + return $result; + } + $bundleProductOptions = $extendedAttributes->getBundleProductOptions(); + if ($bundleProductOptions == null) { + return $result; + } - if (is_array($bundleProductOptions)) { - foreach ($bundleProductOptions as $option) { - $this->optionRepository->save($result, $option); + /** @var \Magento\Bundle\Api\Data\OptionInterface[] $bundleProductOptions */ + $existingOptions = $this->optionRepository->getList($product->getSku()); + $existingOptionsMap = []; + foreach ($existingOptions as $existingOption) { + $existingOptionsMap[$existingOption->getOptionId()] = $existingOption; + } + $updatedOptionIds = []; + foreach ($bundleProductOptions as $bundleOption) { + $optionId = $bundleOption->getOptionId(); + if ($optionId) { + $updatedOptionIds[] = $optionId; } } - return $result; + $optionIdsToDelete = array_diff(array_keys($existingOptionsMap), $updatedOptionIds); + //Handle new and existing options + foreach ($bundleProductOptions as $option) { + $this->optionRepository->save($result, $option); + } + //Delete options that are not in the list + foreach ($optionIdsToDelete as $optionId) { + $this->optionRepository->delete($existingOptionsMap[$optionId]); + } + return $subject->get($result->getSku(), false, $result->getStoreId(), true); } } diff --git a/app/code/Magento/Bundle/Model/Product/LinksList.php b/app/code/Magento/Bundle/Model/Product/LinksList.php index d0b6a78635b6a905d8ba9c9e9aa0c163ee56d53e..2d0076f67847b6df0b65e0b9af418bcf4f9934f1 100644 --- a/app/code/Magento/Bundle/Model/Product/LinksList.php +++ b/app/code/Magento/Bundle/Model/Product/LinksList.php @@ -61,8 +61,9 @@ class LinksList '\Magento\Bundle\Api\Data\LinkInterface' ); $productLink->setIsDefault($selection->getIsDefault()) + ->setId($selection->getSelectionId()) ->setQty($selection->getSelectionQty()) - ->setIsDefined($selection->getSelectionCanChangeQty()) + ->setCanChangeQuantity($selection->getSelectionCanChangeQty()) ->setPrice($selectionPrice) ->setPriceType($selectionPriceType); $productLinks[] = $productLink; diff --git a/app/code/Magento/Bundle/Model/Selection.php b/app/code/Magento/Bundle/Model/Selection.php index 919c54051566f0bc631c233bb0cfd7618955a907..1f68f9795dd2b66e4b9e8866aa1fb8c7de697f8c 100644 --- a/app/code/Magento/Bundle/Model/Selection.php +++ b/app/code/Magento/Bundle/Model/Selection.php @@ -8,6 +8,8 @@ namespace Magento\Bundle\Model; /** * Bundle Selection Model * + * @method int getSelectionId() + * @method \Magento\Bundle\Model\Selection setSelectionId(int $value) * @method int getOptionId() * @method \Magento\Bundle\Model\Selection setOptionId(int $value) * @method int getParentProductId() diff --git a/app/code/Magento/Bundle/Model/Source/Option/Selection/Price/Type.php b/app/code/Magento/Bundle/Model/Source/Option/Selection/Price/Type.php index 706de8be46be2f927b9783076ae2c2350564e273..a541ac48c12486820616203fb337e3b0ae610cc0 100644 --- a/app/code/Magento/Bundle/Model/Source/Option/Selection/Price/Type.php +++ b/app/code/Magento/Bundle/Model/Source/Option/Selection/Price/Type.php @@ -5,6 +5,8 @@ */ namespace Magento\Bundle\Model\Source\Option\Selection\Price; +use Magento\Bundle\Api\Data\LinkInterface; + /** * Extended Attributes Source Model * @@ -17,6 +19,9 @@ class Type implements \Magento\Framework\Option\ArrayInterface */ public function toOptionArray() { - return [['value' => '0', 'label' => __('Fixed')], ['value' => '1', 'label' => __('Percent')]]; + return [ + ['value' => LinkInterface::PRICE_TYPE_FIXED, 'label' => __('Fixed')], + ['value' => LinkInterface::PRICE_TYPE_PERCENT, 'label' => __('Percent')] + ]; } } diff --git a/app/code/Magento/Bundle/Test/Unit/Model/LinkManagementTest.php b/app/code/Magento/Bundle/Test/Unit/Model/LinkManagementTest.php index a1d9391b3e112a3785dfa4f7f5cdbff682db01f4..e4f49d48adcf35004827caf8d85fb4028f3b19ca 100644 --- a/app/code/Magento/Bundle/Test/Unit/Model/LinkManagementTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Model/LinkManagementTest.php @@ -111,7 +111,7 @@ class LinkManagementTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); $this->option = $this->getMockBuilder('Magento\Bundle\Model\Option') - ->setMethods(['getSelections', '__wakeup']) + ->setMethods(['getSelections', 'getOptionId', '__wakeup']) ->disableOriginalConstructor() ->getMock(); $this->optionCollection = $this->getMockBuilder('Magento\Bundle\Model\Resource\Option\Collection') @@ -193,14 +193,48 @@ class LinkManagementTest extends \PHPUnit_Framework_TestCase ->willReturnSelf(); $this->link->expects($this->once())->method('setIsDefault')->willReturnSelf(); $this->link->expects($this->once())->method('setQty')->willReturnSelf(); - $this->link->expects($this->once())->method('setIsDefined')->willReturnSelf(); + $this->link->expects($this->once())->method('setCanChangeQuantity')->willReturnSelf(); $this->link->expects($this->once())->method('setPrice')->willReturnSelf(); $this->link->expects($this->once())->method('setPriceType')->willReturnSelf(); + $this->link->expects($this->once())->method('setId')->willReturnSelf(); $this->linkFactory->expects($this->once())->method('create')->willReturn($this->link); $this->assertEquals([$this->link], $this->model->getChildren($productSku)); } + public function testGetChildrenWithOptionId() + { + $productSku = 'productSku'; + + $this->getOptions(); + + $this->productRepository->expects($this->any())->method('get')->with($this->equalTo($productSku)) + ->will($this->returnValue($this->product)); + + $this->product->expects($this->once())->method('getTypeId')->will($this->returnValue('bundle')); + + $this->productType->expects($this->once())->method('setStoreFilter')->with( + $this->equalTo($this->storeId), + $this->product + ); + $this->productType->expects($this->once())->method('getSelectionsCollection') + ->with($this->equalTo($this->optionIds), $this->equalTo($this->product)) + ->will($this->returnValue($this->selectionCollection)); + $this->productType->expects($this->once())->method('getOptionsIds')->with($this->equalTo($this->product)) + ->will($this->returnValue($this->optionIds)); + + $this->optionCollection->expects($this->once())->method('appendSelections') + ->with($this->equalTo($this->selectionCollection)) + ->will($this->returnValue([$this->option])); + + $this->option->expects($this->any())->method('getOptionId')->will($this->returnValue(10)); + $this->option->expects($this->never())->method('getSelections'); + + $this->dataObjectHelperMock->expects($this->never())->method('populateWithArray'); + + $this->assertEquals([], $this->model->getChildren($productSku, 1)); + } + /** * @expectedException \Magento\Framework\Exception\InputException */ @@ -243,31 +277,29 @@ class LinkManagementTest extends \PHPUnit_Framework_TestCase $productMock->expects($this->once())->method('getTypeId')->will($this->returnValue( \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE )); - $productMock->expects($this->once())->method('getId')->will($this->returnValue('product_id')); $store = $this->getMock('\Magento\Store\Model\Store', [], [], '', false); $this->storeManagerMock->expects($this->any())->method('getStore')->will($this->returnValue($store)); $store->expects($this->any())->method('getId')->will($this->returnValue(0)); - $option = $this->getMockBuilder('\Magento\Bundle\Model\Option')->disableOriginalConstructor() - ->setMethods(['getOptionId', '__wakeup']) + $emptyOption = $this->getMockBuilder('\Magento\Bundle\Model\Option')->disableOriginalConstructor() + ->setMethods(['getId', '__wakeup']) ->getMock(); - $option->expects($this->once())->method('getOptionId')->will($this->returnValue(2)); + $emptyOption->expects($this->once()) + ->method('getId') + ->will($this->returnValue(null)); $optionsCollectionMock = $this->getMock( '\Magento\Bundle\Model\Resource\Option\Collection', [], [], '', false ); $optionsCollectionMock->expects($this->once()) - ->method('setProductIdFilter') - ->with($this->equalTo('product_id')) + ->method('setIdFilter') + ->with($this->equalTo(1)) ->will($this->returnSelf()); $optionsCollectionMock->expects($this->once()) - ->method('joinValues') - ->with($this->equalTo(0)) - ->will($this->returnSelf()); - $optionsCollectionMock->expects($this->any())->method('getIterator')->will( - $this->returnValue(new \ArrayIterator([$option])) - ); + ->method('getFirstItem') + ->will($this->returnValue($emptyOption)); + $this->optionCollectionFactoryMock->expects($this->any())->method('create')->will( $this->returnValue($optionsCollectionMock) ); @@ -304,22 +336,18 @@ class LinkManagementTest extends \PHPUnit_Framework_TestCase $store->expects($this->any())->method('getId')->will($this->returnValue(0)); $option = $this->getMockBuilder('\Magento\Bundle\Model\Option')->disableOriginalConstructor() - ->setMethods(['getOptionId', '__wakeup']) + ->setMethods(['getId', '__wakeup']) ->getMock(); - $option->expects($this->once())->method('getOptionId')->will($this->returnValue(1)); + $option->expects($this->once())->method('getId')->will($this->returnValue(1)); $optionsCollectionMock = $this->getMock('\Magento\Bundle\Model\Resource\Option\Collection', [], [], '', false); $optionsCollectionMock->expects($this->once()) - ->method('setProductIdFilter') - ->with($this->equalTo('product_id')) + ->method('setIdFilter') + ->with($this->equalTo('1')) ->will($this->returnSelf()); $optionsCollectionMock->expects($this->once()) - ->method('joinValues') - ->with($this->equalTo(0)) - ->will($this->returnSelf()); - $optionsCollectionMock->expects($this->any())->method('getIterator')->will( - $this->returnValue(new \ArrayIterator([$option])) - ); + ->method('getFirstItem') + ->will($this->returnValue($option)); $this->optionCollectionFactoryMock->expects($this->any())->method('create')->will( $this->returnValue($optionsCollectionMock) ); @@ -359,22 +387,18 @@ class LinkManagementTest extends \PHPUnit_Framework_TestCase $store->expects($this->any())->method('getId')->will($this->returnValue(0)); $option = $this->getMockBuilder('\Magento\Bundle\Model\Option')->disableOriginalConstructor() - ->setMethods(['getOptionId', '__wakeup']) + ->setMethods(['getId', '__wakeup']) ->getMock(); - $option->expects($this->once())->method('getOptionId')->will($this->returnValue(1)); + $option->expects($this->once())->method('getId')->will($this->returnValue(1)); $optionsCollectionMock = $this->getMock('\Magento\Bundle\Model\Resource\Option\Collection', [], [], '', false); $optionsCollectionMock->expects($this->once()) - ->method('setProductIdFilter') - ->with($this->equalTo('product_id')) + ->method('setIdFilter') + ->with($this->equalTo(1)) ->will($this->returnSelf()); $optionsCollectionMock->expects($this->once()) - ->method('joinValues') - ->with($this->equalTo(0)) - ->will($this->returnSelf()); - $optionsCollectionMock->expects($this->any())->method('getIterator')->will( - $this->returnValue(new \ArrayIterator([$option])) - ); + ->method('getFirstItem') + ->will($this->returnValue($option)); $this->optionCollectionFactoryMock->expects($this->any())->method('create')->will( $this->returnValue($optionsCollectionMock) ); @@ -420,24 +444,20 @@ class LinkManagementTest extends \PHPUnit_Framework_TestCase $store->expects($this->any())->method('getId')->will($this->returnValue(0)); $option = $this->getMockBuilder('\Magento\Bundle\Model\Option')->disableOriginalConstructor() - ->setMethods(['getOptionId', '__wakeup']) + ->setMethods(['getId', '__wakeup']) ->getMock(); - $option->expects($this->once())->method('getOptionId')->will($this->returnValue(1)); + $option->expects($this->once())->method('getId')->will($this->returnValue(1)); $optionsCollectionMock = $this->getMock( '\Magento\Bundle\Model\Resource\Option\Collection', [], [], '', false ); $optionsCollectionMock->expects($this->once()) - ->method('setProductIdFilter') - ->with($this->equalTo('product_id')) + ->method('setIdFilter') + ->with($this->equalTo(1)) ->will($this->returnSelf()); $optionsCollectionMock->expects($this->once()) - ->method('joinValues') - ->with($this->equalTo(0)) - ->will($this->returnSelf()); - $optionsCollectionMock->expects($this->any())->method('getIterator')->will( - $this->returnValue(new \ArrayIterator([$option])) - ); + ->method('getFirstItem') + ->will($this->returnValue($option)); $this->optionCollectionFactoryMock->expects($this->any())->method('create')->will( $this->returnValue($optionsCollectionMock) ); @@ -452,7 +472,7 @@ class LinkManagementTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue($selections)); $this->bundleFactoryMock->expects($this->once())->method('create')->will($this->returnValue($bundle)); - $selection = $this->getMock('\Magento\Framework\Object', ['save'], [], '', false); + $selection = $this->getMock('\Magento\Bundle\Model\Selection', ['save'], [], '', false); $selection->expects($this->once())->method('save') ->will( $this->returnCallback( @@ -491,24 +511,20 @@ class LinkManagementTest extends \PHPUnit_Framework_TestCase $store->expects($this->any())->method('getId')->will($this->returnValue(0)); $option = $this->getMockBuilder('\Magento\Bundle\Model\Option')->disableOriginalConstructor() - ->setMethods(['getOptionId', '__wakeup']) + ->setMethods(['getId', '__wakeup']) ->getMock(); - $option->expects($this->once())->method('getOptionId')->will($this->returnValue(1)); + $option->expects($this->once())->method('getId')->will($this->returnValue(1)); $optionsCollectionMock = $this->getMock( '\Magento\Bundle\Model\Resource\Option\Collection', [], [], '', false ); $optionsCollectionMock->expects($this->once()) - ->method('setProductIdFilter') - ->with($this->equalTo('product_id')) + ->method('setIdFilter') + ->with($this->equalTo(1)) ->will($this->returnSelf()); $optionsCollectionMock->expects($this->once()) - ->method('joinValues') - ->with($this->equalTo(0)) - ->will($this->returnSelf()); - $optionsCollectionMock->expects($this->any())->method('getIterator')->will( - $this->returnValue(new \ArrayIterator([$option])) - ); + ->method('getFirstItem') + ->will($this->returnValue($option)); $this->optionCollectionFactoryMock->expects($this->any())->method('create')->will( $this->returnValue($optionsCollectionMock) ); @@ -523,7 +539,7 @@ class LinkManagementTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue($selections)); $this->bundleFactoryMock->expects($this->once())->method('create')->will($this->returnValue($bundle)); - $selection = $this->getMock('\Magento\Framework\Object', ['save', 'getId'], [], '', false); + $selection = $this->getMock('\Magento\Bundle\Model\Selection', ['save', 'getId'], [], '', false); $selection->expects($this->once())->method('save'); $selection->expects($this->once())->method('getId')->will($this->returnValue(42)); $this->bundleSelectionMock->expects($this->once())->method('create')->will($this->returnValue($selection)); @@ -531,6 +547,298 @@ class LinkManagementTest extends \PHPUnit_Framework_TestCase $this->assertEquals(42, $result); } + public function testSaveChild() + { + $id = 12; + $optionId = 1; + $position = 3; + $qty = 2; + $priceType = 1; + $price = 10.5; + $canChangeQuantity = true; + $isDefault = true; + $linkProductId = 45; + $parentProductId = 32; + $bundleProductSku = 'bundleProductSku'; + + $productLink = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface'); + $productLink->expects($this->any())->method('getSku')->will($this->returnValue('linked_product_sku')); + $productLink->expects($this->any())->method('getId')->will($this->returnValue($id)); + $productLink->expects($this->any())->method('getOptionId')->will($this->returnValue($optionId)); + $productLink->expects($this->any())->method('getPosition')->will($this->returnValue($position)); + $productLink->expects($this->any())->method('getQty')->will($this->returnValue($qty)); + $productLink->expects($this->any())->method('getPriceType')->will($this->returnValue($priceType)); + $productLink->expects($this->any())->method('getPrice')->will($this->returnValue($price)); + $productLink->expects($this->any())->method('getCanChangeQuantity')->will($this->returnValue($canChangeQuantity)); + $productLink->expects($this->any())->method('getIsDefault')->will($this->returnValue($isDefault)); + + $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + $productMock->expects($this->once())->method('getTypeId')->will($this->returnValue( + \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE + )); + $productMock->expects($this->any())->method('getId')->will($this->returnValue($parentProductId)); + + $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + $linkedProductMock->expects($this->any())->method('getId')->will($this->returnValue($linkProductId)); + $linkedProductMock->expects($this->once())->method('isComposite')->will($this->returnValue(false)); + $this->productRepository + ->expects($this->at(0)) + ->method('get') + ->with($bundleProductSku) + ->will($this->returnValue($productMock)); + $this->productRepository + ->expects($this->at(1)) + ->method('get') + ->with('linked_product_sku') + ->will($this->returnValue($linkedProductMock)); + + $store = $this->getMock('\Magento\Store\Model\Store', [], [], '', false); + $this->storeManagerMock->expects($this->any())->method('getStore')->will($this->returnValue($store)); + $store->expects($this->any())->method('getId')->will($this->returnValue(0)); + + $selection = $this->getMock( + '\Magento\Bundle\Model\Selection', + [ + 'save', + 'getId', + 'load', + 'setProductId', + 'setParentProductId', + 'setOptionId', + 'setPosition', + 'setSelectionQty', + 'setSelectionPriceType', + 'setSelectionPriceValue', + 'setSelectionCanChangeQty', + 'setIsDefault' + ], + [], + '', + false + ); + $selection->expects($this->once())->method('save'); + $selection->expects($this->once())->method('load')->with($id)->will($this->returnSelf()); + $selection->expects($this->any())->method('getId')->will($this->returnValue($id)); + $selection->expects($this->once())->method('setProductId')->with($linkProductId); + $selection->expects($this->once())->method('setParentProductId')->with($parentProductId); + $selection->expects($this->once())->method('setOptionId')->with($optionId); + $selection->expects($this->once())->method('setPosition')->with($position); + $selection->expects($this->once())->method('setSelectionQty')->with($qty); + $selection->expects($this->once())->method('setSelectionPriceType')->with($priceType); + $selection->expects($this->once())->method('setSelectionPriceValue')->with($price); + $selection->expects($this->once())->method('setSelectionCanChangeQty')->with($canChangeQuantity); + $selection->expects($this->once())->method('setIsDefault')->with($isDefault); + + $this->bundleSelectionMock->expects($this->once())->method('create')->will($this->returnValue($selection)); + $this->assertTrue($this->model->saveChild($bundleProductSku, $productLink)); + } + + /** + * @expectedException \Magento\Framework\Exception\CouldNotSaveException + */ + public function testSaveChildFailedToSave() + { + $id = 12; + $linkProductId = 45; + $parentProductId = 32; + $productLink = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface'); + $productLink->expects($this->any())->method('getSku')->will($this->returnValue('linked_product_sku')); + $productLink->expects($this->any())->method('getId')->will($this->returnValue($id)); + $bundleProductSku = 'bundleProductSku'; + + $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + $productMock->expects($this->once())->method('getTypeId')->will($this->returnValue( + \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE + )); + $productMock->expects($this->any())->method('getId')->will($this->returnValue($parentProductId)); + + $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + $linkedProductMock->expects($this->any())->method('getId')->will($this->returnValue($linkProductId)); + $linkedProductMock->expects($this->once())->method('isComposite')->will($this->returnValue(false)); + $this->productRepository + ->expects($this->at(0)) + ->method('get') + ->with($bundleProductSku) + ->will($this->returnValue($productMock)); + $this->productRepository + ->expects($this->at(1)) + ->method('get') + ->with('linked_product_sku') + ->will($this->returnValue($linkedProductMock)); + + $store = $this->getMock('\Magento\Store\Model\Store', [], [], '', false); + $this->storeManagerMock->expects($this->any())->method('getStore')->will($this->returnValue($store)); + $store->expects($this->any())->method('getId')->will($this->returnValue(0)); + + $selection = $this->getMock( + '\Magento\Bundle\Model\Selection', + [ + 'save', + 'getId', + 'load', + 'setProductId', + 'setParentProductId', + 'setSelectionId', + 'setOptionId', + 'setPosition', + 'setSelectionQty', + 'setSelectionPriceType', + 'setSelectionPriceValue', + 'setSelectionCanChangeQty', + 'setIsDefault' + ], + [], + '', + false + ); + $mockException = $this->getMock('\Exception'); + $selection->expects($this->once())->method('save')->will($this->throwException($mockException)); + $selection->expects($this->once())->method('load')->with($id)->will($this->returnSelf()); + $selection->expects($this->any())->method('getId')->will($this->returnValue($id)); + $selection->expects($this->once())->method('setProductId')->with($linkProductId); + + $this->bundleSelectionMock->expects($this->once())->method('create')->will($this->returnValue($selection)); + $this->model->saveChild($bundleProductSku, $productLink); + } + + /** + * @expectedException \Magento\Framework\Exception\InputException + */ + public function testSaveChildWithoutId() + { + $bundleProductSku = "bundleSku"; + $linkedProductSku = 'simple'; + $productLink = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface'); + $productLink->expects($this->any())->method('getId')->will($this->returnValue(null)); + $productLink->expects($this->any())->method('getSku')->will($this->returnValue($linkedProductSku)); + + $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + $productMock->expects($this->once())->method('getTypeId')->will($this->returnValue( + \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE + )); + + $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + $linkedProductMock->expects($this->once())->method('isComposite')->will($this->returnValue(false)); + $this->productRepository + ->expects($this->at(0)) + ->method('get') + ->with($bundleProductSku) + ->will($this->returnValue($productMock)); + $this->productRepository + ->expects($this->at(1)) + ->method('get') + ->with($linkedProductSku) + ->will($this->returnValue($linkedProductMock)); + + $this->model->saveChild($bundleProductSku, $productLink); + } + + /** + * @expectedException \Magento\Framework\Exception\InputException + * @expectedExceptionMessage Can not find product link with id "12345" + */ + public function testSaveChildWithInvalidId() + { + $id = 12345; + $linkedProductSku = 'simple'; + $bundleProductSku = "bundleProductSku"; + $productLink = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface'); + $productLink->expects($this->any())->method('getId')->will($this->returnValue($id)); + $productLink->expects($this->any())->method('getSku')->will($this->returnValue($linkedProductSku)); + + $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + $productMock->expects($this->once())->method('getTypeId')->will($this->returnValue( + \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE + )); + + $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + $linkedProductMock->expects($this->once())->method('isComposite')->will($this->returnValue(false)); + $this->productRepository + ->expects($this->at(0)) + ->method('get') + ->with($bundleProductSku) + ->will($this->returnValue($productMock)); + $this->productRepository + ->expects($this->at(1)) + ->method('get') + ->with($linkedProductSku) + ->will($this->returnValue($linkedProductMock)); + + $selection = $this->getMock( + '\Magento\Bundle\Model\Selection', + [ + 'getId', + 'load', + ], + [], + '', + false + ); + $selection->expects($this->once())->method('load')->with($id)->will($this->returnSelf()); + $selection->expects($this->any())->method('getId')->will($this->returnValue(null)); + + $this->bundleSelectionMock->expects($this->once())->method('create')->will($this->returnValue($selection)); + + $this->model->saveChild($bundleProductSku, $productLink); + } + + /** + * @expectedException \Magento\Framework\Exception\InputException + */ + public function testSaveChildWithCompositeProductLink() + { + $bundleProductSku = "bundleProductSku"; + $id = 12; + $linkedProductSku = 'simple'; + $productLink = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface'); + $productLink->expects($this->any())->method('getId')->will($this->returnValue($id)); + $productLink->expects($this->any())->method('getSku')->will($this->returnValue($linkedProductSku)); + + $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + $productMock->expects($this->once())->method('getTypeId')->will($this->returnValue( + \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE + )); + + $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + $linkedProductMock->expects($this->once())->method('isComposite')->will($this->returnValue(true)); + $this->productRepository + ->expects($this->at(0)) + ->method('get') + ->with($bundleProductSku) + ->will($this->returnValue($productMock)); + $this->productRepository + ->expects($this->at(1)) + ->method('get') + ->with($linkedProductSku) + ->will($this->returnValue($linkedProductMock)); + + $this->model->saveChild($bundleProductSku, $productLink); + } + + /** + * @expectedException \Magento\Framework\Exception\InputException + */ + public function testSaveChildWithSimpleProduct() + { + $id = 12; + $linkedProductSku = 'simple'; + $bundleProductSku = "bundleProductSku"; + + $productLink = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface'); + $productLink->expects($this->any())->method('getId')->will($this->returnValue($id)); + $productLink->expects($this->any())->method('getSku')->will($this->returnValue($linkedProductSku)); + + $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + $productMock->expects($this->once())->method('getTypeId')->will($this->returnValue( + \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE + )); + + $this->productRepository->expects($this->once())->method('get')->with($bundleProductSku) + ->willReturn($productMock); + + $this->model->saveChild($bundleProductSku, $productLink); + } + public function testRemoveChild() { $this->productRepository->expects($this->any())->method('get')->will($this->returnValue($this->product)); diff --git a/app/code/Magento/Bundle/Test/Unit/Model/OptionRepositoryTest.php b/app/code/Magento/Bundle/Test/Unit/Model/OptionRepositoryTest.php index 7b91140a3bfe6fd0aa0196384d298e656afbb5bd..6c4fc418f0390d2e5262d425da13355a16724a34 100644 --- a/app/code/Magento/Bundle/Test/Unit/Model/OptionRepositoryTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Model/OptionRepositoryTest.php @@ -335,8 +335,9 @@ class OptionRepositoryTest extends \PHPUnit_Framework_TestCase ->willReturn($optCollectionMock); $existingOptionMock = $this->getMock('\Magento\Bundle\Model\Option', ['getOptionId'], [], '', false); - $optCollectionMock->expects($this->once())->method('setIdFilter')->with($optionId)->willReturnSelf(); - $optCollectionMock->expects($this->once())->method('getFirstItem')->willReturn($existingOptionMock); + $optCollectionMock->expects($this->once())->method('getItemById') + ->with($optionId) + ->willReturn($existingOptionMock); $existingOptionMock->expects($this->once())->method('getOptionId')->willReturn(null); $this->assertEquals($optionId, $this->model->save($productMock, $optionMock)); @@ -345,13 +346,16 @@ class OptionRepositoryTest extends \PHPUnit_Framework_TestCase /** * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ - public function testUpdate() + public function testSaveExistingOption() { $productId = 1; + $productSku = 'bundle_sku'; $storeId = 2; $optionId = 5; $existingOptionId = 5; - $existingOptionTitle = 'option_title'; + $existingLinkToUpdateId = '23'; + $existingLinkToDeleteId = '24'; + $productSkuToDelete = 'simple2'; $storeMock = $this->getMock('\Magento\Store\Model\Store', ['getId'], [], '', false); $storeMock->expects($this->once())->method('getId')->willReturn($storeId); @@ -359,6 +363,7 @@ class OptionRepositoryTest extends \PHPUnit_Framework_TestCase $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); $productMock->expects($this->once())->method('getId')->willReturn($productId); + $productMock->expects($this->any())->method('getSku')->willReturn($productSku); $optionMock = $this->getMock( '\Magento\Bundle\Model\Option', [ @@ -378,6 +383,17 @@ class OptionRepositoryTest extends \PHPUnit_Framework_TestCase $optionMock->expects($this->once())->method('setParentId')->with($productId)->willReturnSelf(); $optionMock->expects($this->any())->method('getOptionId')->willReturn($optionId); + $existingLinkToUpdate = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface'); + $existingLinkToUpdate->expects($this->any())->method('getId')->willReturn($existingLinkToUpdateId); + $existingLinkToDelete = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface'); + $existingLinkToDelete->expects($this->any())->method('getId')->willReturn($existingLinkToDeleteId); + $existingLinkToDelete->expects($this->once())->method('getSku')->willReturn($productSkuToDelete); + $existingLinks = [$existingLinkToUpdate, $existingLinkToDelete]; + $this->linkManagementMock->expects($this->once()) + ->method('getChildren') + ->with($productSku, $optionId) + ->willReturn($existingLinks); + $optCollectionMock = $this->getMock('\Magento\Bundle\Model\Resource\Option\Collection', [], [], '', false); $this->typeMock->expects($this->once()) ->method('getOptionsCollection') @@ -390,22 +406,183 @@ class OptionRepositoryTest extends \PHPUnit_Framework_TestCase '', false ); - $optCollectionMock->expects($this->once())->method('setIdFilter')->with($optionId)->willReturnSelf(); - $optCollectionMock->expects($this->once())->method('getFirstItem')->willReturn($existingOptionMock); + $optCollectionMock->expects($this->once())->method('getItemById') + ->with($optionId) + ->willReturn($existingOptionMock); $existingOptionMock->expects($this->any())->method('getOptionId')->willReturn($existingOptionId); - $existingOptionMock->expects($this->once())->method('getProductLinks')->willReturn(null); - $linkedProductMock = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface'); - $optionMock->expects($this->exactly(2))->method('getProductLinks')->willReturn([$linkedProductMock]); + $productLinkUpdate = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface'); + $productLinkUpdate->expects($this->any())->method('getId')->willReturn($existingLinkToUpdateId); + $productLinkNew = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface'); + $productLinkNew->expects($this->any())->method('getId')->willReturn(null); + $optionMock->expects($this->exactly(2)) + ->method('getProductLinks') + ->willReturn([$productLinkUpdate, $productLinkNew]); $this->optionResourceMock->expects($this->once())->method('save')->with($optionMock)->willReturnSelf(); $this->linkManagementMock->expects($this->once()) ->method('addChild') - ->with($productMock, $optionId, $linkedProductMock) - ->willReturn(1); + ->with($productMock, $optionId, $productLinkNew); + $this->linkManagementMock->expects($this->once()) + ->method('saveChild') + ->with($productSku, $productLinkUpdate); + $this->linkManagementMock->expects($this->once()) + ->method('removeChild') + ->with($productSku, $optionId, $productSkuToDelete); + $this->assertEquals($optionId, $this->model->save($productMock, $optionMock)); + } + + /** + * @expectedException \Magento\Framework\Exception\NoSuchEntityException + * @expectedExceptionMessage Requested option doesn't exist + */ + public function testSaveExistingOptionNoSuchOption() + { + $productId = 1; + $productSku = 'bundle_sku'; + $storeId = 2; + $optionId = 5; + + $storeMock = $this->getMock('\Magento\Store\Model\Store', ['getId'], [], '', false); + $storeMock->expects($this->once())->method('getId')->willReturn($storeId); + $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($storeMock); + + $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + $productMock->expects($this->once())->method('getId')->willReturn($productId); + $productMock->expects($this->any())->method('getSku')->willReturn($productSku); + $optionMock = $this->getMock( + '\Magento\Bundle\Model\Option', + [ + 'setStoreId', + 'setParentId', + 'getOptionId', + ], + [], + '', + false + ); + $optionMock->expects($this->once())->method('setStoreId')->with($storeId)->willReturnSelf(); + $optionMock->expects($this->once())->method('setParentId')->with($productId)->willReturnSelf(); + $optionMock->expects($this->any())->method('getOptionId')->willReturn($optionId); + + $optCollectionMock = $this->getMock('\Magento\Bundle\Model\Resource\Option\Collection', [], [], '', false); + $this->typeMock->expects($this->once()) + ->method('getOptionsCollection') + ->with($productMock) + ->willReturn($optCollectionMock); + $existingOptionMock = $this->getMock( + '\Magento\Bundle\Model\Option', + ['getOptionId', 'getTitle', 'getProductLinks'], + [], + '', + false + ); + $optCollectionMock->expects($this->once())->method('getItemById') + ->with($optionId) + ->willReturn($existingOptionMock); + $existingOptionMock->expects($this->any())->method('getOptionId')->willReturn(null); + + $this->model->save($productMock, $optionMock); + } + + public function testSaveNewOption() + { + $productId = 1; + $productSku = 'bundle_sku'; + $storeId = 2; + $optionId = 5; + + $storeMock = $this->getMock('\Magento\Store\Model\Store', ['getId'], [], '', false); + $storeMock->expects($this->once())->method('getId')->willReturn($storeId); + $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($storeMock); + + $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + $productMock->expects($this->once())->method('getId')->willReturn($productId); + $productMock->expects($this->any())->method('getSku')->willReturn($productSku); + $optionMock = $this->getMock( + '\Magento\Bundle\Model\Option', + [ + 'setStoreId', + 'setParentId', + 'getProductLinks', + 'getOptionId', + 'setOptionId', + 'setDefaultTitle', + 'getTitle' + ], + [], + '', + false + ); + $optionMock->expects($this->once())->method('setStoreId')->with($storeId)->willReturnSelf(); + $optionMock->expects($this->once())->method('setParentId')->with($productId)->willReturnSelf(); + $optionMock->method('getOptionId') + ->will($this->onConsecutiveCalls(null, $optionId, $optionId, $optionId, $optionId)); + + $productLink1 = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface'); + $productLink2 = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface'); + $optionMock->expects($this->exactly(2)) + ->method('getProductLinks') + ->willReturn([$productLink1, $productLink2]); + + $this->optionResourceMock->expects($this->once())->method('save')->with($optionMock)->willReturnSelf(); + $this->linkManagementMock->expects($this->at(0)) + ->method('addChild') + ->with($productMock, $optionId, $productLink1); + $this->linkManagementMock->expects($this->at(1)) + ->method('addChild') + ->with($productMock, $optionId, $productLink2); $this->assertEquals($optionId, $this->model->save($productMock, $optionMock)); } + /** + * @expectedException \Magento\Framework\Exception\CouldNotSaveException + * @expectedExceptionMessage Could not save option + */ + public function testSaveCanNotSave() + { + $productId = 1; + $productSku = 'bundle_sku'; + $storeId = 2; + $optionId = 5; + + $storeMock = $this->getMock('\Magento\Store\Model\Store', ['getId'], [], '', false); + $storeMock->expects($this->once())->method('getId')->willReturn($storeId); + $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($storeMock); + + $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + $productMock->expects($this->once())->method('getId')->willReturn($productId); + $productMock->expects($this->any())->method('getSku')->willReturn($productSku); + $optionMock = $this->getMock( + '\Magento\Bundle\Model\Option', + [ + 'setStoreId', + 'setParentId', + 'getProductLinks', + 'getOptionId', + 'setOptionId', + 'setDefaultTitle', + 'getTitle' + ], + [], + '', + false + ); + $optionMock->expects($this->once())->method('setStoreId')->with($storeId)->willReturnSelf(); + $optionMock->expects($this->once())->method('setParentId')->with($productId)->willReturnSelf(); + $optionMock->method('getOptionId')->will($this->onConsecutiveCalls(null, $optionId, $optionId, $optionId)); + + $productLink1 = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface'); + $productLink2 = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface'); + $optionMock->expects($this->exactly(2)) + ->method('getProductLinks') + ->willReturn([$productLink1, $productLink2]); + + $this->optionResourceMock->expects($this->once())->method('save')->with($optionMock) + ->willThrowException($this->getMock('\Exception')); + $this->model->save($productMock, $optionMock); + } + public function testGetList() { $productSku = 'simple'; diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Plugin/BundleLoadOptionsTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Plugin/BundleLoadOptionsTest.php index e4050d99040ca61dacc97b54731fd79e67719af1..77d92ff634334b7d1fbc04e3022a33d6eea06334 100644 --- a/app/code/Magento/Bundle/Test/Unit/Model/Plugin/BundleLoadOptionsTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Model/Plugin/BundleLoadOptionsTest.php @@ -60,7 +60,6 @@ class BundleLoadOptionsTest extends \PHPUnit_Framework_TestCase public function testAroundLoad() { - $this->markTestSkipped('MAGETWO-34577'); $productMock = $this->getMock( '\Magento\Catalog\Model\Product', ['getTypeId', 'setExtensionAttributes'], @@ -82,6 +81,7 @@ class BundleLoadOptionsTest extends \PHPUnit_Framework_TestCase ->willReturn([$optionMock]); $productExtensionMock = $this->getMockBuilder('\Magento\Catalog\Api\Data\ProductExtension') ->disableOriginalConstructor() + ->setMethods(['setBundleProductOptions', 'getBundleProductOptions']) ->getMock(); $this->productExtensionFactory->expects($this->once()) ->method('create') diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Plugin/BundleSaveOptionsTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Plugin/BundleSaveOptionsTest.php index 1615760e6dbee5b4ad44d0d30cd81b53b19e691e..62095af664f2b1c04fbd1332ba5c959149e04a4f 100644 --- a/app/code/Magento/Bundle/Test/Unit/Model/Plugin/BundleSaveOptionsTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Model/Plugin/BundleSaveOptionsTest.php @@ -43,6 +43,11 @@ class BundleSaveOptionsTest extends \PHPUnit_Framework_TestCase */ protected $productBundleOptionsMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $productInterfaceFactoryMock; + /** * @var \Closure */ @@ -54,7 +59,7 @@ class BundleSaveOptionsTest extends \PHPUnit_Framework_TestCase $this->productOptionRepositoryMock = $this->getMock('Magento\Bundle\Api\ProductOptionRepositoryInterface'); $this->productMock = $this->getMock( 'Magento\Catalog\Model\Product', - ['getExtensionAttributes', 'getTypeId'], + ['getExtensionAttributes', 'getTypeId', 'getSku', 'getStoreId'], [], '', false @@ -62,10 +67,12 @@ class BundleSaveOptionsTest extends \PHPUnit_Framework_TestCase $this->closureMock = function () { return $this->productMock; }; - $this->plugin = new BundleSaveOptions($this->productOptionRepositoryMock); + $this->plugin = new BundleSaveOptions( + $this->productOptionRepositoryMock + ); $this->productExtensionMock = $this->getMock( 'Magento\Catalog\Api\Data\ProductExtension', - ['getBundleProductOptions'], + ['getBundleProductOptions', 'setBundleProductOptions'], [], '', false @@ -98,7 +105,7 @@ class BundleSaveOptionsTest extends \PHPUnit_Framework_TestCase ->willReturn($this->productExtensionMock); $this->productExtensionMock->expects($this->once()) ->method('getBundleProductOptions') - ->willReturn([]); + ->willReturn(null); $this->productOptionRepositoryMock->expects($this->never())->method('save'); @@ -110,20 +117,105 @@ class BundleSaveOptionsTest extends \PHPUnit_Framework_TestCase public function testAroundSaveWhenProductIsBundleWithOptions() { + $productSku = "bundle_sku"; + $option = $this->getMock('\Magento\Bundle\Api\Data\OptionInterface'); + $this->productMock->expects($this->once())->method('getTypeId')->willReturn('bundle'); + $this->productMock->expects($this->once()) + ->method('getExtensionAttributes') + ->willReturn($this->productExtensionMock); + $this->productExtensionMock->expects($this->once()) + ->method('getBundleProductOptions') + ->willReturn([$option]); + + $this->productOptionRepositoryMock->expects($this->once())->method('save')->with($this->productMock, $option); + + $this->productMock->expects($this->exactly(2))->method('getSku') + ->will($this->returnValue($productSku)); + + $this->productOptionRepositoryMock->expects($this->once()) + ->method('getList') + ->with($productSku) + ->will($this->returnValue([])); + + $newProductMock = $this->getMockBuilder('Magento\Catalog\Api\Data\ProductInterface') + ->disableOriginalConstructor()->getMock(); + $this->productRepositoryMock->expects($this->once()) + ->method('get') + ->with($productSku, false, null, true) + ->willReturn($newProductMock); + + $this->assertEquals( + $newProductMock, + $this->plugin->aroundSave($this->productRepositoryMock, $this->closureMock, $this->productMock) + ); + } + + /** + * Test the case where the product has existing options + */ + public function testAroundSaveWhenProductIsBundleWithOptionsAndExistingOptions() + { + $existOption1Id = 10; + $existOption2Id = 11; + $productSku = 'bundle_sku'; + $existingOption1 = $this->getMock('\Magento\Bundle\Api\Data\OptionInterface'); + $existingOption1->expects($this->once()) + ->method('getOptionId') + ->will($this->returnValue($existOption1Id)); + $existingOption2 = $this->getMock('\Magento\Bundle\Api\Data\OptionInterface'); + $existingOption2->expects($this->once()) + ->method('getOptionId') + ->will($this->returnValue($existOption2Id)); + + $bundleOptionExisting = $this->getMock('\Magento\Bundle\Api\Data\OptionInterface'); + $bundleOptionExisting->expects($this->once()) + ->method('getOptionId') + ->will($this->returnValue($existOption1Id)); + + $bundleOptionNew = $this->getMock('\Magento\Bundle\Api\Data\OptionInterface'); + $bundleOptionNew->expects($this->once()) + ->method('getOptionId') + ->will($this->returnValue(null)); + $this->productMock->expects($this->once())->method('getTypeId')->willReturn('bundle'); $this->productMock->expects($this->once()) ->method('getExtensionAttributes') ->willReturn($this->productExtensionMock); $this->productExtensionMock->expects($this->once()) ->method('getBundleProductOptions') - ->willReturn([$this->productBundleOptionsMock]); + ->willReturn([$bundleOptionExisting, $bundleOptionNew]); + $this->productMock->expects($this->exactly(2))->method('getSku') + ->will($this->returnValue($productSku)); $this->productOptionRepositoryMock->expects($this->once()) + ->method('getList') + ->with($productSku) + ->will($this->returnValue([$existingOption1, $existingOption2])); + + $this->productOptionRepositoryMock + ->expects($this->at(1)) + ->method('save') + ->with($this->productMock, $bundleOptionExisting); + + $this->productOptionRepositoryMock + ->expects($this->at(1)) ->method('save') - ->with($this->productMock, $this->productBundleOptionsMock); + ->with($this->productMock, $bundleOptionNew); + + $this->productOptionRepositoryMock + ->expects($this->once()) + ->method('delete') + ->with($existingOption2); + + $newProductMock = $this->getMockBuilder('Magento\Catalog\Api\Data\ProductInterface') + ->disableOriginalConstructor()->getMock(); + $this->productRepositoryMock->expects($this->once()) + ->method('get') + ->with($productSku, false, null, true) + ->willReturn($newProductMock); $this->assertEquals( - $this->productMock, + $newProductMock, $this->plugin->aroundSave($this->productRepositoryMock, $this->closureMock, $this->productMock) ); } diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Product/LinksListTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Product/LinksListTest.php index 86c409a3de09e7bde03a28658c897ab6371f5bc0..0909834ce5914307bd82d13fa7bbfcecd3a07dc6 100644 --- a/app/code/Magento/Bundle/Test/Unit/Model/Product/LinksListTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Model/Product/LinksListTest.php @@ -64,6 +64,7 @@ class LinksListTest extends \PHPUnit_Framework_TestCase 'getIsDefault', 'getSelectionQty', 'getSelectionCanChangeQty', + 'getSelectionId', '__wakeup' ], [], @@ -89,6 +90,7 @@ class LinksListTest extends \PHPUnit_Framework_TestCase public function testLinksList() { $optionId = 665; + $selectionId = 1345; $this->productTypeMock->expects($this->once()) ->method('getSelectionsCollection') ->with([$optionId], $this->productMock) @@ -99,6 +101,7 @@ class LinksListTest extends \PHPUnit_Framework_TestCase ->willReturn('selection_price_type'); $this->selectionMock->expects($this->once())->method('getSelectionPriceValue')->willReturn(12); $this->selectionMock->expects($this->once())->method('getData')->willReturn(['some data']); + $this->selectionMock->expects($this->once())->method('getSelectionId')->willReturn($selectionId); $this->selectionMock->expects($this->once())->method('getIsDefault')->willReturn(true); $this->selectionMock->expects($this->once())->method('getSelectionQty')->willReturn(66); $this->selectionMock->expects($this->once())->method('getSelectionCanChangeQty')->willReturn(22); @@ -108,8 +111,9 @@ class LinksListTest extends \PHPUnit_Framework_TestCase ->with($linkMock, ['some data'], '\Magento\Bundle\Api\Data\LinkInterface')->willReturnSelf(); $linkMock->expects($this->once())->method('setIsDefault')->with(true)->willReturnSelf(); $linkMock->expects($this->once())->method('setQty')->with(66)->willReturnSelf(); - $linkMock->expects($this->once())->method('setIsDefined')->with(22)->willReturnSelf(); + $linkMock->expects($this->once())->method('setCanChangeQuantity')->with(22)->willReturnSelf(); $linkMock->expects($this->once())->method('setPrice')->with(12)->willReturnSelf(); + $linkMock->expects($this->once())->method('setId')->with($selectionId)->willReturnSelf(); $linkMock->expects($this->once()) ->method('setPriceType')->with('selection_price_type')->willReturnSelf(); $this->linkFactoryMock->expects($this->once())->method('create')->willReturn($linkMock); diff --git a/app/code/Magento/Bundle/etc/data_object.xml b/app/code/Magento/Bundle/etc/data_object.xml index 88e317dafc78c8ce9d0c9a3fe6f703b777361e4a..2b3da013978f971bb20baca3a5e0ca95f2b501fc 100644 --- a/app/code/Magento/Bundle/etc/data_object.xml +++ b/app/code/Magento/Bundle/etc/data_object.xml @@ -8,7 +8,5 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Api/etc/data_object.xsd"> <custom_attributes for="Magento\Catalog\Api\Data\ProductInterface"> <attribute code="bundle_product_options" type="Magento\Bundle\Api\Data\OptionInterface[]" /> - <attribute code="price_type" type="integer" /> - <attribute code="price_view" type="string" /> </custom_attributes> </config> diff --git a/app/code/Magento/Bundle/etc/webapi.xml b/app/code/Magento/Bundle/etc/webapi.xml index ec0dffcf1049e2d29da72bb80d02e8fba97ee316..550987ba13e669b5e2a5634384b3793d8932f97e 100644 --- a/app/code/Magento/Bundle/etc/webapi.xml +++ b/app/code/Magento/Bundle/etc/webapi.xml @@ -13,7 +13,13 @@ <resource ref="Magento_Catalog::products"/> </resources> </route> - <route url="/V1/bundle-products/:productId/children" method="GET"> + <route url="/V1/bundle-products/:sku/links/:id" method="PUT"> + <service class="Magento\Bundle\Api\ProductLinkManagementInterface" method="saveChild"/> + <resources> + <resource ref="Magento_Catalog::products"/> + </resources> + </route> + <route url="/V1/bundle-products/:productSku/children" method="GET"> <service class="Magento\Bundle\Api\ProductLinkManagementInterface" method="getChildren"/> <resources> <resource ref="Magento_Catalog::products"/> diff --git a/app/code/Magento/Catalog/Api/ProductRepositoryInterface.php b/app/code/Magento/Catalog/Api/ProductRepositoryInterface.php index 8205f68fca6095f9d516c01f5ad04be1a8515725..7fb25a7c046f5124a52ba8e50171112ca670c6c3 100644 --- a/app/code/Magento/Catalog/Api/ProductRepositoryInterface.php +++ b/app/code/Magento/Catalog/Api/ProductRepositoryInterface.php @@ -27,10 +27,11 @@ interface ProductRepositoryInterface * @param string $sku * @param bool $editMode * @param null|int $storeId + * @param bool $forceReload * @return \Magento\Catalog\Api\Data\ProductInterface * @throws \Magento\Framework\Exception\NoSuchEntityException */ - public function get($sku, $editMode = false, $storeId = null); + public function get($sku, $editMode = false, $storeId = null, $forceReload = false); /** * Get info about product by product id @@ -38,10 +39,11 @@ interface ProductRepositoryInterface * @param int $productId * @param bool $editMode * @param null|int $storeId + * @param bool $forceReload * @return \Magento\Catalog\Api\Data\ProductInterface * @throws \Magento\Framework\Exception\NoSuchEntityException */ - public function getById($productId, $editMode = false, $storeId = null); + public function getById($productId, $editMode = false, $storeId = null, $forceReload = false); /** * Delete product diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php index 9dbbddcee68f828b225304bea6dad7050d74c275..318fad719e5c0c857db31f5cef1dc06570328116 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php @@ -64,6 +64,8 @@ class Helper public function initialize(\Magento\Catalog\Model\Product $product) { $productData = $this->request->getPost('product'); + unset($productData['custom_attributes']); + unset($productData['extension_attributes']); if ($productData) { $stockData = isset($productData['stock_data']) ? $productData['stock_data'] : []; diff --git a/app/code/Magento/Catalog/Model/Plugin/ProductRepository/TransactionWrapper.php b/app/code/Magento/Catalog/Model/Plugin/ProductRepository/TransactionWrapper.php new file mode 100644 index 0000000000000000000000000000000000000000..1d940a5d7aee5e62da18e7a623ee2295b36afcea --- /dev/null +++ b/app/code/Magento/Catalog/Model/Plugin/ProductRepository/TransactionWrapper.php @@ -0,0 +1,102 @@ +<?php +/** + * Plugin for \Magento\Catalog\Api\ProductRepositoryInterface + * + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Model\Plugin\ProductRepository; + +class TransactionWrapper +{ + /** + * @var \Magento\Catalog\Model\Resource\Product + */ + protected $resourceModel; + + /** + * @param \Magento\Catalog\Model\Resource\Product $resourceModel + */ + public function __construct( + \Magento\Catalog\Model\Resource\Product $resourceModel + ) { + $this->resourceModel = $resourceModel; + } + + /** + * @param \Magento\Catalog\Api\ProductRepositoryInterface $subject + * @param callable $proceed + * @param \Magento\Catalog\Api\Data\ProductInterface $product + * @param bool $saveOptions + * @return \Magento\Catalog\Api\Data\ProductInterface + * @throws \Exception + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundSave( + \Magento\Catalog\Api\ProductRepositoryInterface $subject, + \Closure $proceed, + \Magento\Catalog\Api\Data\ProductInterface $product, + $saveOptions = false + ) { + $this->resourceModel->beginTransaction(); + try { + /** @var \Magento\Catalog\Api\Data\ProductInterface $result */ + $result = $proceed($product, $saveOptions); + $this->resourceModel->commit(); + return $result; + } catch (\Exception $e) { + $this->resourceModel->rollBack(); + throw $e; + } + } + + /** + * @param \Magento\Catalog\Api\ProductRepositoryInterface $subject + * @param callable $proceed + * @param \Magento\Catalog\Api\Data\ProductInterface $product + * @return bool + * @throws \Exception + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundDelete( + \Magento\Catalog\Api\ProductRepositoryInterface $subject, + \Closure $proceed, + \Magento\Catalog\Api\Data\ProductInterface $product + ) { + $this->resourceModel->beginTransaction(); + try { + /** @var bool $result */ + $result = $proceed($product); + $this->resourceModel->commit(); + return $result; + } catch (\Exception $e) { + $this->resourceModel->rollBack(); + throw $e; + } + } + + /** + * @param \Magento\Catalog\Api\ProductRepositoryInterface $subject + * @param callable $proceed + * @param string $productSku + * @return bool + * @throws \Exception + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundDeleteById( + \Magento\Catalog\Api\ProductRepositoryInterface $subject, + \Closure $proceed, + $productSku + ) { + $this->resourceModel->beginTransaction(); + try { + /** @var bool $result */ + $result = $proceed($productSku); + $this->resourceModel->commit(); + return $result; + } catch (\Exception $e) { + $this->resourceModel->rollBack(); + throw $e; + } + } +} diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index 43fa06093960b8abab062c90bb649cde53164d96..40ecc5d2cb3419101374a9d563b52ada19cd9967 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -181,10 +181,10 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa /** * {@inheritdoc} */ - public function get($sku, $editMode = false, $storeId = null) + public function get($sku, $editMode = false, $storeId = null, $forceReload = false) { $cacheKey = $this->getCacheKey(func_get_args()); - if (!isset($this->instances[$sku][$cacheKey])) { + if (!isset($this->instances[$sku][$cacheKey]) || $forceReload) { $product = $this->productFactory->create(); $productId = $this->resourceModel->getIdBySku($sku); @@ -204,10 +204,10 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa /** * {@inheritdoc} */ - public function getById($productId, $editMode = false, $storeId = null) + public function getById($productId, $editMode = false, $storeId = null, $forceReload = false) { $cacheKey = $this->getCacheKey(func_get_args()); - if (!isset($this->instancesById[$productId][$cacheKey])) { + if (!isset($this->instancesById[$productId][$cacheKey]) || $forceReload) { $product = $this->productFactory->create(); if ($editMode) { @@ -235,6 +235,7 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa protected function getCacheKey($data) { unset($data[0]); + unset($data['forceReload']); $serializeData = []; foreach ($data as $key => $value) { if (is_object($value)) { diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Plugin/ProductRepository/TransactionWrapperTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Plugin/ProductRepository/TransactionWrapperTest.php new file mode 100644 index 0000000000000000000000000000000000000000..681af582d1344c749c0623451ce0d4195e2f3d17 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Plugin/ProductRepository/TransactionWrapperTest.php @@ -0,0 +1,141 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Test\Unit\Model\Plugin\ProductRepository; + +class TransactionWrapperTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Catalog\Model\Plugin\ProductRepository\TransactionWrapper + */ + protected $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Catalog\Model\Resource\Product + */ + protected $resourceMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Catalog\Api\ProductRepositoryInterface + */ + protected $subjectMock; + + /** + * @var \Closure + */ + protected $closureMock; + + /** + * @var \Closure + */ + protected $rollbackClosureMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $productMock; + + /** + * @var bool + */ + protected $saveOption = true; + + const ERROR_MSG = "error occurred"; + + protected function setUp() + { + $this->resourceMock = $this->getMock('Magento\Catalog\Model\Resource\Product', [], [], '', false); + $this->subjectMock = $this->getMock('Magento\Catalog\Api\ProductRepositoryInterface', [], [], '', false); + $this->productMock = $this->getMock('Magento\Catalog\Api\Data\ProductInterface', [], [], '', false); + $productMock = $this->productMock; + $this->closureMock = function () use ($productMock) { + return $productMock; + }; + $this->rollbackClosureMock = function () use ($productMock) { + throw new \Exception(self::ERROR_MSG); + }; + + $this->model = new \Magento\Catalog\Model\Plugin\ProductRepository\TransactionWrapper($this->resourceMock); + } + + public function testAroundSaveCommit() + { + $this->resourceMock->expects($this->once())->method('beginTransaction'); + $this->resourceMock->expects($this->once())->method('commit'); + + $this->assertEquals( + $this->productMock, + $this->model->aroundSave($this->subjectMock, $this->closureMock, $this->productMock, $this->saveOption) + ); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage error occurred + */ + public function testAroundSaveRollBack() + { + $this->resourceMock->expects($this->once())->method('beginTransaction'); + $this->resourceMock->expects($this->once())->method('rollBack'); + + $this->model->aroundSave($this->subjectMock, $this->rollbackClosureMock, $this->productMock, $this->saveOption); + } + + public function testAroundDeleteCommit() + { + $this->resourceMock->expects($this->once())->method('beginTransaction'); + $this->resourceMock->expects($this->once())->method('commit'); + + $this->assertEquals( + $this->productMock, + $this->model->aroundDelete($this->subjectMock, $this->closureMock, $this->productMock, $this->saveOption) + ); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage error occurred + */ + public function testAroundDeleteRollBack() + { + $this->resourceMock->expects($this->once())->method('beginTransaction'); + $this->resourceMock->expects($this->once())->method('rollBack'); + + $this->model->aroundDelete( + $this->subjectMock, + $this->rollbackClosureMock, + $this->productMock, + $this->saveOption + ); + } + + public function testAroundDeleteByIdCommit() + { + $this->resourceMock->expects($this->once())->method('beginTransaction'); + $this->resourceMock->expects($this->once())->method('commit'); + + $this->assertEquals( + $this->productMock, + $this->model->aroundDelete($this->subjectMock, $this->closureMock, $this->productMock, $this->saveOption) + ); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage error occurred + */ + public function testAroundDeleteByIdRollBack() + { + $this->resourceMock->expects($this->once())->method('beginTransaction'); + $this->resourceMock->expects($this->once())->method('rollBack'); + + $this->model->aroundDelete( + $this->subjectMock, + $this->rollbackClosureMock, + $this->productMock, + $this->saveOption + ); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductLink/RepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductLink/RepositoryTest.php index affded52e9586413fb24378919bf538fdf4b74fb..26f70422f0e6075392f7e52c473371af78ded36c 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductLink/RepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductLink/RepositoryTest.php @@ -75,8 +75,8 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); $this->productRepositoryMock->expects($this->exactly(2))->method('get')->will($this->returnValueMap( [ - ['product', false, null, $productMock], - ['linkedProduct', false, null, $linkedProductMock], + ['product', false, null, false, $productMock], + ['linkedProduct', false, null, false, $linkedProductMock], ] )); $entityMock->expects($this->once())->method('getLinkedProductSku')->willReturn('linkedProduct'); @@ -102,8 +102,8 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); $this->productRepositoryMock->expects($this->exactly(2))->method('get')->will($this->returnValueMap( [ - ['product', false, null, $productMock], - ['linkedProduct', false, null, $linkedProductMock], + ['product', false, null, false, $productMock], + ['linkedProduct', false, null, false, $linkedProductMock], ] )); $entityMock->expects($this->once())->method('getLinkedProductSku')->willReturn('linkedProduct'); @@ -129,8 +129,8 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); $this->productRepositoryMock->expects($this->exactly(2))->method('get')->will($this->returnValueMap( [ - ['product', false, null, $productMock], - ['linkedProduct', false, null, $linkedProductMock], + ['product', false, null, false, $productMock], + ['linkedProduct', false, null, false, $linkedProductMock], ] )); $entityMock->expects($this->once())->method('getLinkedProductSku')->willReturn('linkedProduct'); @@ -157,8 +157,8 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); $this->productRepositoryMock->expects($this->exactly(2))->method('get')->will($this->returnValueMap( [ - ['product', false, null, $productMock], - ['linkedProduct', false, null, $linkedProductMock], + ['product', false, null, false, $productMock], + ['linkedProduct', false, null, false, $linkedProductMock], ] )); $entityMock->expects($this->once())->method('getLinkedProductSku')->willReturn('linkedProduct'); @@ -186,8 +186,8 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); $this->productRepositoryMock->expects($this->exactly(2))->method('get')->will($this->returnValueMap( [ - ['product', false, null, $productMock], - ['linkedProduct', false, null, $linkedProductMock], + ['product', false, null, false, $productMock], + ['linkedProduct', false, null, false, $linkedProductMock], ] )); $entityMock->expects($this->exactly(2))->method('getLinkedProductSku')->willReturn('linkedProduct'); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php index d50868fc2306bb0376ba81b02973d51f3c7b42ef..a8ba184519d797718c4613f9e5f4c4c8690764bf 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php @@ -298,6 +298,55 @@ class ProductRepositoryTest extends \PHPUnit_Framework_TestCase $this->productMock->expects($this->once())->method('load')->with($identifier); $this->productMock->expects($this->once())->method('getId')->willReturn($identifier); $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId)); + //Second invocation should just return from cache + $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId)); + } + + /** + * Test the forceReload parameter + * + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function testGetByIdForcedReload() + { + $identifier = "23"; + $editMode = false; + $storeId = 0; + + $this->productFactoryMock->expects($this->exactly(2))->method('create') + ->will($this->returnValue($this->productMock)); + $this->productMock->expects($this->exactly(2))->method('load'); + $this->productMock->expects($this->exactly(2))->method('getId')->willReturn($identifier); + $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId)); + //second invocation should just return from cache + $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId)); + //force reload + $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId, true)); + } + + /** + * Test forceReload parameter + * + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function testGetForcedReload() + { + $sku = "sku"; + $id = "23"; + $editMode = false; + $storeId = 0; + + $this->productFactoryMock->expects($this->exactly(2))->method('create') + ->will($this->returnValue($this->productMock)); + $this->productMock->expects($this->exactly(2))->method('load'); + $this->productMock->expects($this->exactly(2))->method('getId')->willReturn($sku); + $this->resourceModelMock->expects($this->exactly(2))->method('getIdBySku') + ->with($sku)->willReturn($id); + $this->assertEquals($this->productMock, $this->model->get($sku, $editMode, $storeId)); + //second invocation should just return from cache + $this->assertEquals($this->productMock, $this->model->get($sku, $editMode, $storeId)); + //force reload + $this->assertEquals($this->productMock, $this->model->get($sku, $editMode, $storeId, true)); } public function testGetByIdWithSetStoreId() diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml index 971bebcac3226849a1af0c88b08925230ea83246..acd741fdbbe66d5c107a3d24ce3d99a5ec716b52 100644 --- a/app/code/Magento/Catalog/etc/di.xml +++ b/app/code/Magento/Catalog/etc/di.xml @@ -461,4 +461,7 @@ <preference for="Magento\Catalog\Api\Data\ProductCustomOptionValuesInterface" type="\Magento\Catalog\Model\Product\Option\Value" /> <virtualType name="Magento\Catalog\Model\Resource\Attribute\Collection" type="Magento\Eav\Model\Resource\Entity\Attribute\Collection"> </virtualType> + <type name="Magento\Catalog\Api\ProductRepositoryInterface"> + <plugin name="transactionWrapper" type="\Magento\Catalog\Model\Plugin\ProductRepository\TransactionWrapper" sortOrder="-1"/> + </type> </config> diff --git a/app/code/Magento/Cron/Setup/InstallSchema.php b/app/code/Magento/Cron/Setup/InstallSchema.php index cc7b419fa4e66bbbfbb9468768fde123f37f1d03..e3368c677742fe41a016adc40ed7293e3e072dcf 100644 --- a/app/code/Magento/Cron/Setup/InstallSchema.php +++ b/app/code/Magento/Cron/Setup/InstallSchema.php @@ -57,7 +57,7 @@ class InstallSchema implements InstallSchemaInterface 'created_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Created At' )->addColumn( 'scheduled_at', diff --git a/app/code/Magento/Customer/Setup/InstallSchema.php b/app/code/Magento/Customer/Setup/InstallSchema.php index 6f6750f3797043f4f25cfb78b4fd5e83bd0f0b6a..e74309026a57bd6f342c9d09c4276cb64e1857e9 100644 --- a/app/code/Magento/Customer/Setup/InstallSchema.php +++ b/app/code/Magento/Customer/Setup/InstallSchema.php @@ -81,13 +81,13 @@ class InstallSchema implements InstallSchemaInterface 'created_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Created At' )->addColumn( 'updated_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT_UPDATE], 'Updated At' )->addColumn( 'is_active', @@ -134,7 +134,7 @@ class InstallSchema implements InstallSchemaInterface 'Customer Entity' ); $installer->getConnection()->createTable($table); - + /** * Create table 'customer_address_entity' */ @@ -174,13 +174,13 @@ class InstallSchema implements InstallSchemaInterface 'created_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Created At' )->addColumn( 'updated_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT_UPDATE], 'Updated At' )->addColumn( 'is_active', @@ -202,7 +202,7 @@ class InstallSchema implements InstallSchemaInterface 'Customer Address Entity' ); $installer->getConnection()->createTable($table); - + /** * Create table 'customer_address_entity_datetime' */ @@ -287,7 +287,7 @@ class InstallSchema implements InstallSchemaInterface 'Customer Address Entity Datetime' ); $installer->getConnection()->createTable($table); - + /** * Create table 'customer_address_entity_decimal' */ @@ -372,7 +372,7 @@ class InstallSchema implements InstallSchemaInterface 'Customer Address Entity Decimal' ); $installer->getConnection()->createTable($table); - + /** * Create table 'customer_address_entity_int' */ @@ -447,7 +447,7 @@ class InstallSchema implements InstallSchemaInterface 'Customer Address Entity Int' ); $installer->getConnection()->createTable($table); - + /** * Create table 'customer_address_entity_text' */ @@ -524,7 +524,7 @@ class InstallSchema implements InstallSchemaInterface 'Customer Address Entity Text' ); $installer->getConnection()->createTable($table); - + /** * Create table 'customer_address_entity_varchar' */ @@ -609,7 +609,7 @@ class InstallSchema implements InstallSchemaInterface 'Customer Address Entity Varchar' ); $installer->getConnection()->createTable($table); - + /** * Create table 'customer_entity_datetime' */ @@ -684,7 +684,7 @@ class InstallSchema implements InstallSchemaInterface 'Customer Entity Datetime' ); $installer->getConnection()->createTable($table); - + /** * Create table 'customer_entity_decimal' */ @@ -759,7 +759,7 @@ class InstallSchema implements InstallSchemaInterface 'Customer Entity Decimal' ); $installer->getConnection()->createTable($table); - + /** * Create table 'customer_entity_int' */ @@ -834,7 +834,7 @@ class InstallSchema implements InstallSchemaInterface 'Customer Entity Int' ); $installer->getConnection()->createTable($table); - + /** * Create table 'customer_entity_text' */ @@ -906,7 +906,7 @@ class InstallSchema implements InstallSchemaInterface 'Customer Entity Text' ); $installer->getConnection()->createTable($table); - + /** * Create table 'customer_entity_varchar' */ @@ -981,7 +981,7 @@ class InstallSchema implements InstallSchemaInterface 'Customer Entity Varchar' ); $installer->getConnection()->createTable($table); - + /** * Create table 'customer_group' */ @@ -1009,7 +1009,7 @@ class InstallSchema implements InstallSchemaInterface 'Customer Group' ); $installer->getConnection()->createTable($table); - + /** * Create table 'customer_eav_attribute' */ @@ -1073,7 +1073,7 @@ class InstallSchema implements InstallSchemaInterface 'Customer Eav Attribute' ); $installer->getConnection()->createTable($table); - + /** * Create table 'customer_form_attribute' */ @@ -1104,7 +1104,7 @@ class InstallSchema implements InstallSchemaInterface 'Customer Form Attribute' ); $installer->getConnection()->createTable($table); - + /** * Create table 'customer_eav_attribute_website' */ @@ -1165,7 +1165,7 @@ class InstallSchema implements InstallSchemaInterface 'Customer Eav Attribute Website' ); $installer->getConnection()->createTable($table); - + /** * Create table 'customer_visitor' */ @@ -1193,8 +1193,8 @@ class InstallSchema implements InstallSchemaInterface 'Visitor Table' ); $installer->getConnection()->createTable($table); - + $installer->endSetup(); - + } } diff --git a/app/code/Magento/Downloadable/Setup/InstallSchema.php b/app/code/Magento/Downloadable/Setup/InstallSchema.php index e7043312e7a49331987f862339b2d8463041f45f..f377662176a55473a8bd1461f7db95c44a440043 100644 --- a/app/code/Magento/Downloadable/Setup/InstallSchema.php +++ b/app/code/Magento/Downloadable/Setup/InstallSchema.php @@ -216,14 +216,14 @@ class InstallSchema implements InstallSchemaInterface 'created_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Date of creation' ) ->addColumn( 'updated_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT_UPDATE], 'Date of modification' ) ->addColumn( @@ -390,14 +390,14 @@ class InstallSchema implements InstallSchemaInterface 'created_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Creation Time' ) ->addColumn( 'updated_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT_UPDATE], 'Update Time' ) ->addIndex( diff --git a/app/code/Magento/Eav/Setup/InstallSchema.php b/app/code/Magento/Eav/Setup/InstallSchema.php index 244cfe2d85e9e56e0e5771ef30b4c3b8888fbc0f..24c041c7e2c05bd1365bfb0166cae0f253d3c0c5 100644 --- a/app/code/Magento/Eav/Setup/InstallSchema.php +++ b/app/code/Magento/Eav/Setup/InstallSchema.php @@ -179,13 +179,13 @@ class InstallSchema implements InstallSchemaInterface 'created_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Created At' )->addColumn( 'updated_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT_UPDATE], 'Updated At' )->addColumn( 'is_active', diff --git a/app/code/Magento/Integration/Setup/InstallSchema.php b/app/code/Magento/Integration/Setup/InstallSchema.php index 7c6bfda56692ba91f7ceb1170b00f28b055c88a4..9588da6905210f53b0c47aaecc05c1f483d30107 100644 --- a/app/code/Magento/Integration/Setup/InstallSchema.php +++ b/app/code/Magento/Integration/Setup/InstallSchema.php @@ -316,13 +316,13 @@ class InstallSchema implements InstallSchemaInterface 'created_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Creation Time' )->addColumn( 'updated_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_UPDATE], 'Update Time' )->addColumn( 'setup_type', diff --git a/app/code/Magento/Log/Setup/InstallSchema.php b/app/code/Magento/Log/Setup/InstallSchema.php index 1cf4f0832bf5032958659da8bc0b55f24b244371..372d41188ca9e537430493c40e5342e5e89fadbf 100644 --- a/app/code/Magento/Log/Setup/InstallSchema.php +++ b/app/code/Magento/Log/Setup/InstallSchema.php @@ -95,7 +95,7 @@ class InstallSchema implements InstallSchemaInterface 'created_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Creation Time' )->addColumn( 'deleted_at', @@ -147,7 +147,7 @@ class InstallSchema implements InstallSchemaInterface 'add_date', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Date' )->setComment( 'Log Summary Table' diff --git a/app/code/Magento/ProductAlert/Setup/InstallSchema.php b/app/code/Magento/ProductAlert/Setup/InstallSchema.php index 8ccda0176f7682591e0a283f97287450c331ae2d..9926f11ef1196ec4729cdb8c68a56c64ab5be4e5 100644 --- a/app/code/Magento/ProductAlert/Setup/InstallSchema.php +++ b/app/code/Magento/ProductAlert/Setup/InstallSchema.php @@ -63,7 +63,7 @@ class InstallSchema implements InstallSchemaInterface 'add_date', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Product alert add date' )->addColumn( 'last_send_date', @@ -148,7 +148,7 @@ class InstallSchema implements InstallSchemaInterface 'add_date', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Product alert add date' )->addColumn( 'send_date', diff --git a/app/code/Magento/Reports/Setup/InstallSchema.php b/app/code/Magento/Reports/Setup/InstallSchema.php index 707597f5a747a2192ff1cb20072745c0a6e21ff4..7e619a704833f703ce6f0c9f97e4a37333e1912e 100644 --- a/app/code/Magento/Reports/Setup/InstallSchema.php +++ b/app/code/Magento/Reports/Setup/InstallSchema.php @@ -73,7 +73,7 @@ class InstallSchema implements InstallSchemaInterface 'added_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Added At' ) ->addIndex( @@ -181,7 +181,7 @@ class InstallSchema implements InstallSchemaInterface 'added_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Added At' ) ->addIndex( @@ -288,7 +288,7 @@ class InstallSchema implements InstallSchemaInterface 'logged_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Logged At' ) ->addColumn( @@ -411,7 +411,7 @@ class InstallSchema implements InstallSchemaInterface 'added_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Added At' ) ->addIndex( @@ -517,7 +517,7 @@ class InstallSchema implements InstallSchemaInterface 'added_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Added At' ) ->addIndex( diff --git a/app/code/Magento/Review/Setup/InstallSchema.php b/app/code/Magento/Review/Setup/InstallSchema.php index 276b7389bff8fd84a89d735d65747ae079a59320..147688bdf15e3b7dbab52eefab5c1104c547a77a 100644 --- a/app/code/Magento/Review/Setup/InstallSchema.php +++ b/app/code/Magento/Review/Setup/InstallSchema.php @@ -84,7 +84,7 @@ class InstallSchema implements InstallSchemaInterface 'created_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Review create date' ) ->addColumn( diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Item.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Item.php index 163a03903f878e254e5817243cff2ac0bd1288b0..35d522427429f1d01def7ad15a2f784ee2e7f31f 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo/Item.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Item.php @@ -219,7 +219,7 @@ class Item extends AbstractModel implements CreditmemoItemInterface $rowTotalInclTax = $orderItem->getRowTotalInclTax(); $baseRowTotalInclTax = $orderItem->getBaseRowTotalInclTax(); - if (!$this->isLast() && $orderItemQtyInvoiced > 0 && $this->getQty() > 0) { + if (!$this->isLast() && $orderItemQtyInvoiced > 0 && $this->getQty() >= 0) { $availableQty = $orderItemQtyInvoiced - $orderItem->getQtyRefunded(); $rowTotal = $creditmemo->roundPrice($rowTotal / $availableQty * $this->getQty()); $baseRowTotal = $creditmemo->roundPrice($baseRowTotal / $availableQty * $this->getQty(), 'base'); diff --git a/app/code/Magento/Sales/Setup/InstallSchema.php b/app/code/Magento/Sales/Setup/InstallSchema.php index fd517941e38a02bb475a13e56ab6100dd531a911..93192e49bb663de74eaf6f9c87948ed058a649a4 100644 --- a/app/code/Magento/Sales/Setup/InstallSchema.php +++ b/app/code/Magento/Sales/Setup/InstallSchema.php @@ -1308,13 +1308,13 @@ class InstallSchema implements InstallSchemaInterface 'created_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], 'Created At' )->addColumn( 'updated_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT_UPDATE], 'Updated At' )->addColumn( 'product_id', diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/ItemTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/ItemTest.php index afcf792c718dddc69eab5e23a17923b32a8e8a9c..0673aa120a217ae4daa1d549b441a2969c510d81 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/ItemTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/ItemTest.php @@ -264,43 +264,53 @@ class ItemTest extends \PHPUnit_Framework_TestCase $this->assertInstanceOf('Magento\Sales\Model\Order\Creditmemo\Item', $result); } - public function testCalcRowTotal() + /** + * @dataProvider calcRowTotalDataProvider + */ + public function testCalcRowTotal($qty) { $creditmemoMock = $this->getMockBuilder('\Magento\Sales\Model\Order\Creditmemo') ->disableOriginalConstructor() ->getMock(); $creditmemoMock->expects($this->exactly(4)) ->method('roundPrice') - ->willReturnMap( - [ - [0.375, 'regular', false, 0.4], - [0.375, 'base', false, 0.4], - [1, 'including', false, 1.0], - [1, 'including_base', false, 1.0] - ] - ); + ->will($this->returnCallback( + function ($arg) { + return round($arg, 2); + } + )); + + $qtyInvoiced = 10; + $qtyRefunded = 2; + $qtyAvailable = $qtyInvoiced - $qtyRefunded; + + $rowInvoiced = 5; + $amountRefunded = 2; + + $expectedRowTotal = ($rowInvoiced - $amountRefunded) / $qtyAvailable * $qty; + $expectedRowTotal = round($expectedRowTotal, 2); $orderItemMock = $this->getMockBuilder('Magento\Sales\Model\Order\Item') ->disableOriginalConstructor() ->getMock(); $orderItemMock->expects($this->once()) ->method('getQtyInvoiced') - ->willReturn(10); + ->willReturn($qtyInvoiced); $orderItemMock->expects($this->once()) ->method('getQtyRefunded') - ->willReturn(2); + ->willReturn($qtyRefunded); $orderItemMock->expects($this->once()) ->method('getRowInvoiced') - ->willReturn(5); + ->willReturn($rowInvoiced); $orderItemMock->expects($this->once()) ->method('getAmountRefunded') - ->willReturn(2); + ->willReturn($amountRefunded); $orderItemMock->expects($this->once()) ->method('getBaseRowInvoiced') - ->willReturn(5); + ->willReturn($rowInvoiced); $orderItemMock->expects($this->once()) ->method('getBaseAmountRefunded') - ->willReturn(2); + ->willReturn($amountRefunded); $orderItemMock->expects($this->once()) ->method('getRowTotalInclTax') ->willReturn(1); @@ -313,11 +323,28 @@ class ItemTest extends \PHPUnit_Framework_TestCase $orderItemMock->expects($this->once()) ->method('getQtyOrdered') ->willReturn(1); + $orderItemMock->expects($this->any()) + ->method('getQtyToRefund') + ->willReturn($qtyAvailable); - $this->item->setData('qty', 1); + $this->item->setData('qty', $qty); $this->item->setCreditmemo($creditmemoMock); $this->item->setOrderItem($orderItemMock); $result = $this->item->calcRowTotal(); + $this->assertInstanceOf('Magento\Sales\Model\Order\Creditmemo\Item', $result); + $this->assertEquals($expectedRowTotal, $this->item->getData('row_total')); + $this->assertEquals($expectedRowTotal, $this->item->getData('base_row_total')); + } + + /** + * @return array + */ + public function calcRowTotalDataProvider() + { + return [ + 'qty 1' => [1], + 'qty 0' => [0], + ]; } } diff --git a/app/code/Magento/Search/Setup/InstallSchema.php b/app/code/Magento/Search/Setup/InstallSchema.php index 3c20f81310ed0b9ecc5c0bd6cbb08c82eafcce28..d58c0da9a8946015c721e96229746d6beae6703a 100644 --- a/app/code/Magento/Search/Setup/InstallSchema.php +++ b/app/code/Magento/Search/Setup/InstallSchema.php @@ -104,7 +104,7 @@ class InstallSchema implements InstallSchemaInterface 'updated_at', \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, null, - ['nullable' => false], + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT_UPDATE], 'Updated at' ) ->addIndex( diff --git a/app/code/Magento/Weee/Model/Total/Creditmemo/Weee.php b/app/code/Magento/Weee/Model/Total/Creditmemo/Weee.php index d45eab656ca2a5eaf6c41e5f17125b87dc6b2f8b..7ddf6d66a7d0ada34c68da378ebcde2ed6112387 100644 --- a/app/code/Magento/Weee/Model/Total/Creditmemo/Weee.php +++ b/app/code/Magento/Weee/Model/Total/Creditmemo/Weee.php @@ -49,36 +49,30 @@ class Weee extends \Magento\Sales\Model\Order\Creditmemo\Total\AbstractTotal $totalWeeeAmount = 0; $baseTotalWeeeAmount = 0; - $totalWeeeAmountInclTax = 0; $baseTotalWeeeAmountInclTax = 0; - - $totalTaxAmount = $totalWeeeAmountInclTax - $totalWeeeAmount; - $baseTotalTaxAmount = $baseTotalWeeeAmountInclTax - $baseTotalWeeeAmount; + $totalTaxAmount = 0; + $baseTotalTaxAmount = 0; foreach ($creditmemo->getAllItems() as $item) { $orderItem = $item->getOrderItem(); - if ($orderItem->isDummy() || $item->getQty() <= 0) { + $orderItemQty = $orderItem->getQtyOrdered(); + + if (!$orderItemQty || $orderItem->isDummy() || $item->getQty() < 0) { continue; } - $ratio = $item->getQty() / $orderItem->getQtyOrdered(); + $ratio = $item->getQty() / $orderItemQty; $orderItemWeeeAmountExclTax = $orderItem->getWeeeTaxAppliedRowAmount(); $orderItemBaseWeeeAmountExclTax = $orderItem->getBaseWeeeTaxAppliedRowAmnt(); $weeeAmountExclTax = $creditmemo->roundPrice($orderItemWeeeAmountExclTax * $ratio); - $baseWeeeAmountExclTax = $creditmemo->roundPrice( - $orderItemBaseWeeeAmountExclTax * $ratio, - 'base' - ); + $baseWeeeAmountExclTax = $creditmemo->roundPrice($orderItemBaseWeeeAmountExclTax * $ratio, 'base'); $orderItemWeeeAmountInclTax = $this->_weeeData->getRowWeeeTaxInclTax($orderItem); $orderItemBaseWeeeAmountInclTax = $this->_weeeData->getBaseRowWeeeTaxInclTax($orderItem); $weeeAmountInclTax = $creditmemo->roundPrice($orderItemWeeeAmountInclTax * $ratio); - $baseWeeeAmountInclTax = $creditmemo->roundPrice( - $orderItemBaseWeeeAmountInclTax * $ratio, - 'base' - ); + $baseWeeeAmountInclTax = $creditmemo->roundPrice($orderItemBaseWeeeAmountInclTax * $ratio, 'base'); $itemTaxAmount = $weeeAmountInclTax - $weeeAmountExclTax; $itemBaseTaxAmount = $baseWeeeAmountInclTax - $baseWeeeAmountExclTax; diff --git a/app/code/Magento/Weee/Model/Total/Invoice/Weee.php b/app/code/Magento/Weee/Model/Total/Invoice/Weee.php index 457a08ccd86691eac2615dcbffcc5571bc8eaf6e..a5bed91ec0c366f9d72e7e6762be8f17249a3a3b 100644 --- a/app/code/Magento/Weee/Model/Total/Invoice/Weee.php +++ b/app/code/Magento/Weee/Model/Total/Invoice/Weee.php @@ -57,11 +57,12 @@ class Weee extends \Magento\Sales\Model\Order\Invoice\Total\AbstractTotal $orderItem = $item->getOrderItem(); $orderItemQty = $orderItem->getQtyOrdered(); - if (!$orderItemQty || $orderItem->isDummy() || $item->getQty() <= 0) { + if (!$orderItemQty || $orderItem->isDummy() || $item->getQty() < 0) { continue; } $ratio = $item->getQty() / $orderItemQty; + $orderItemWeeeAmount = $orderItem->getWeeeTaxAppliedRowAmount(); $orderItemBaseWeeeAmount = $orderItem->getBaseWeeeTaxAppliedRowAmnt(); $weeeAmount = $invoice->roundPrice($orderItemWeeeAmount * $ratio); diff --git a/app/code/Magento/Weee/Test/Unit/Model/Total/Creditmemo/WeeeTest.php b/app/code/Magento/Weee/Test/Unit/Model/Total/Creditmemo/WeeeTest.php index 47aa7692a51d940fb25b894b0951aac0af7590c3..0fdd0ad9bdab19c2c51da37e8b7751c807dc9620 100644 --- a/app/code/Magento/Weee/Test/Unit/Model/Total/Creditmemo/WeeeTest.php +++ b/app/code/Magento/Weee/Test/Unit/Model/Total/Creditmemo/WeeeTest.php @@ -169,6 +169,7 @@ class WeeeTest extends \PHPUnit_Framework_TestCase public function collectDataProvider() { $result = []; + // scenario 1: 3 item_1, $100 with $weee, 8.25 tax rate, 3 items invoiced, full creditmemo $result['complete_creditmemo'] = [ 'creditmemo_data' => [ @@ -236,7 +237,6 @@ class WeeeTest extends \PHPUnit_Framework_TestCase 'tax_ratio' => serialize(['weee' => 1.0]), 'weee_tax_applied_row_amount' => 30, 'base_weee_tax_applied_row_amount' => 30, - ], ], 'creditmemo_data' => [ @@ -248,7 +248,6 @@ class WeeeTest extends \PHPUnit_Framework_TestCase 'base_subtotal' => 300, 'subtotal_incl_tax' => 357.22, 'base_subtotal_incl_tax' => 357.22, - ], ], ]; @@ -320,7 +319,6 @@ class WeeeTest extends \PHPUnit_Framework_TestCase 'tax_ratio' => serialize(['weee' => 1.65 / 2.47]), 'weee_tax_applied_row_amount' => 20, 'base_weee_tax_applied_row_amount' => 20, - ], ], 'creditmemo_data' => [ @@ -332,7 +330,6 @@ class WeeeTest extends \PHPUnit_Framework_TestCase 'base_subtotal' => 200, 'subtotal_incl_tax' => 238.15, 'base_subtotal_incl_tax' => 238.15, - ], ], ]; @@ -404,7 +401,6 @@ class WeeeTest extends \PHPUnit_Framework_TestCase 'tax_ratio' => serialize(['weee' => 0.83 / 2.47]), 'weee_tax_applied_row_amount' => 10, 'base_weee_tax_applied_row_amount' => 10, - ], ], 'creditmemo_data' => [ @@ -416,7 +412,79 @@ class WeeeTest extends \PHPUnit_Framework_TestCase 'base_subtotal' => 100, 'subtotal_incl_tax' => 119.07, 'base_subtotal_incl_tax' => 119.07, + ], + ], + ]; + // scenario 4: 3 item_1, $100 with $weee, 8.25 tax rate. Returning qty 0. + $result['zero_return'] = [ + 'creditmemo_data' => [ + 'items' => [ + 'item_1' => [ + 'order_item' => [ + 'qty_ordered' => 3, + 'weee_tax_applied_row_amount' => 30, + 'base_weee_tax_applied_row_amnt' => 30, + 'row_weee_tax_incl_tax' => 32.47, + 'base_row_weee_tax_incl_tax' => 32.47, + 'weee_amount_invoiced' => 30, + 'base_weee_amount_invoiced' => 30, + 'weee_amount_refunded' => 0, + 'base_weee_amount_refunded' => 0, + 'weee_tax_amount_invoiced' => 2.47, + 'base_weee_tax_amount_invoiced' => 2.47, + 'weee_tax_amount_refunded' => 0, + 'base_weee_tax_amount_refunded' => 0, + 'applied_weee' => [ + [ + 'title' => 'recycling_fee', + 'base_row_amount' => 30, + 'row_amount' => 30, + 'base_row_amount_incl_tax' => 32.47, + 'row_amount_incl_tax' => 32.47, + ], + ], + 'qty_invoiced' => 3, + ], + 'is_last' => true, + 'data_fields' => [ + 'qty' => 0, + 'applied_weee' => [ + [ + ], + ], + ], + ], + ], + 'include_in_subtotal' => false, + 'data_fields' => [ + 'grand_total' => 300, + 'base_grand_total' => 300, + 'subtotal' => 300, + 'base_subtotal' => 300, + 'subtotal_incl_tax' => 324.75, + 'base_subtotal_incl_tax' => 324.75, + 'tax_amount' => 0, + 'base_tax_amount' => 0, + ], + ], + 'expected_results' => [ + 'creditmemo_items' => [ + 'item_1' => [ + 'applied_weee' => [ + [ + 'title' => 'recycling_fee', + 'base_row_amount' => 0, + 'row_amount' => 0, + 'base_row_amount_incl_tax' => 0, + 'row_amount_incl_tax' => 0, + ], + ], + ], + ], + 'creditmemo_data' => [ + 'subtotal' => 300, + 'base_subtotal' => 300, ], ], ]; diff --git a/app/code/Magento/Weee/Test/Unit/Model/Total/Invoice/WeeeTest.php b/app/code/Magento/Weee/Test/Unit/Model/Total/Invoice/WeeeTest.php index 4d91954b70fa26e81bf385295b50e5c25cf60934..d034947c48d71129a8ca1bbce59d39b69275d341 100644 --- a/app/code/Magento/Weee/Test/Unit/Model/Total/Invoice/WeeeTest.php +++ b/app/code/Magento/Weee/Test/Unit/Model/Total/Invoice/WeeeTest.php @@ -172,6 +172,7 @@ class WeeeTest extends \PHPUnit_Framework_TestCase public function collectDataProvider() { $result = []; + // 3 item_1, $100 with $weee, 8.25 tax rate, full invoice $result['complete_invoice'] = [ 'order_data' => [ @@ -269,7 +270,6 @@ class WeeeTest extends \PHPUnit_Framework_TestCase 'base_subtotal' => 300, 'subtotal_incl_tax' => 344.85, 'base_subtotal_incl_tax' => 344.85, - ], ], ]; @@ -360,7 +360,6 @@ class WeeeTest extends \PHPUnit_Framework_TestCase 'tax_ratio' => serialize(['weee' => 1.65 / 2.47]), 'weee_tax_applied_row_amount' => 20, 'base_weee_tax_applied_row_amount' => 20, - ], ], 'invoice_data' => [ @@ -372,7 +371,6 @@ class WeeeTest extends \PHPUnit_Framework_TestCase 'base_subtotal' => 200, 'subtotal_incl_tax' => 238.15, 'base_subtotal_incl_tax' => 238.15, - ], ], ]; @@ -464,7 +462,6 @@ class WeeeTest extends \PHPUnit_Framework_TestCase 'tax_ratio' => serialize(['weee' => 0.82 / 2.47]), 'weee_tax_applied_row_amount' => 10, 'base_weee_tax_applied_row_amount' => 10, - ], ], 'invoice_data' => [ @@ -476,7 +473,6 @@ class WeeeTest extends \PHPUnit_Framework_TestCase 'base_subtotal' => 100, 'subtotal_incl_tax' => 119.07, 'base_subtotal_incl_tax' => 119.07, - ], ], ]; @@ -580,7 +576,98 @@ class WeeeTest extends \PHPUnit_Framework_TestCase 'base_subtotal' => 100, 'subtotal_incl_tax' => 114.95, 'base_subtotal_incl_tax' => 114.95, + ], + ], + ]; + // 3 item_1, $100 with $weee, 8.25 tax rate. Invoicing qty 0. + $result['zero_invoice'] = [ + 'order_data' => [ + 'previous_invoices' => [ + ], + 'data_fields' => [ + 'shipping_tax_amount' => 1.24, + 'base_shipping_tax_amount' => 1.24, + 'shipping_hidden_tax_amount' => 0, + 'base_shipping_hidden_tax_amount' => 0, + 'tax_amount' => 16.09, + 'tax_invoiced' => 0, + 'base_tax_amount' => 16.09, + 'base_tax_amount_invoiced' => 0, + 'subtotal' => '300', + 'base_subtotal' => '300', + ], + ], + 'invoice_data' => [ + 'items' => [ + 'item_1' => [ + 'order_item' => [ + 'qty_ordered' => 3, + 'weee_tax_applied_row_amount' => 30, + 'base_weee_tax_applied_row_amnt' => 30, + 'row_weee_tax_incl_tax' => 32.47, + 'base_row_weee_tax_incl_tax' => 32.47, + 'weee_amount_invoiced' => 0, + 'base_weee_amount_invoiced' => 0, + 'weee_tax_amount_invoiced' => 0, + 'base_weee_tax_amount_invoiced' => 0, + 'applied_weee' => [ + [ + 'title' => 'recycling_fee', + 'base_row_amount' => 30, + 'row_amount' => 30, + 'base_row_amount_incl_tax' => 32.47, + 'row_amount_incl_tax' => 32.47, + ], + ], + 'applied_weee_updated' => [ + 'base_row_amount_invoiced' => 30, + 'row_amount_invoiced' => 30, + 'base_tax_amount_invoiced' => 2.47, + 'tax_amount_invoiced' => 2.47, + ], + 'qty_invoiced' => 0, + ], + 'is_last' => true, + 'data_fields' => [ + 'qty' => 0, + 'applied_weee' => [ + [ + ], + ], + ], + ], + ], + 'is_last' => true, + 'include_in_subtotal' => false, + 'data_fields' => [ + 'grand_total' => 181.09, + 'base_grand_total' => 181.09, + 'subtotal' => 300, + 'base_subtotal' => 300, + 'subtotal_incl_tax' => 314.85, + 'base_subtotal_incl_tax' => 314.85, + 'tax_amount' => 16.09, + 'base_tax_amount' => 16.09, + ], + ], + 'expected_results' => [ + 'invoice_items' => [ + 'item_1' => [ + 'applied_weee' => [ + [ + 'title' => 'recycling_fee', + 'base_row_amount' => 0, + 'row_amount' => 0, + 'base_row_amount_incl_tax' => 0, + 'row_amount_incl_tax' => 0, + ], + ], + ], + ], + 'invoice_data' => [ + 'subtotal' => 300, + 'base_subtotal' => 300, ], ], ]; diff --git a/composer.json b/composer.json index c4157852db4b037184417bb2db56b2c17f8775f7..93c1f2b024685ab26c8bf8fc467f2aa3a71c868f 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { - "name": "magento/project-community-edition", - "description": "Magento project (Community Edition)", + "name": "magento/magento2ce", + "description": "Magento 2 (Community Edition)", "type": "project", "version": "0.74.0-beta5", "license": [ @@ -30,7 +30,7 @@ "zendframework/zend-log": "2.3.1", "zendframework/zend-http": "2.3.1", "magento/zendframework1": "1.12.10", - "composer/composer": "1.0.0-alpha8", + "composer/composer": "1.0.0-alpha9", "monolog/monolog": "1.11.0", "oyejorge/less.php": "1.7.0.3", "tubalmartin/cssmin": "2.4.8-p4", @@ -209,5 +209,7 @@ "Magento\\TestFramework\\Utility\\": "dev/tests/static/framework/Magento/TestFramework/Utility/", "Magento\\ToolkitFramework\\": "dev/tools/performance-toolkit/framework/Magento/ToolkitFramework/" } - } + }, + "minimum-stability": "alpha", + "prefer-stable": true } diff --git a/composer.lock b/composer.lock index 6494a34079ab5035d22754ca4fc8d3fbee382143..036da6915e82470dc348beffecb1a07f394215b4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,32 +4,32 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "8b3be7eb30da4335453ce07d0330ec4f", + "hash": "986eef2b3ae742a365a8a886f0babdd6", "packages": [ { "name": "composer/composer", - "version": "1.0.0-alpha8", + "version": "1.0.0-alpha9", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "1eb1df44a97fb2daca1bb8b007f3bee012f0aa46" + "reference": "eb1ce550ca51134ee619ad3e37f5a0b7e980dd24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/1eb1df44a97fb2daca1bb8b007f3bee012f0aa46", - "reference": "1eb1df44a97fb2daca1bb8b007f3bee012f0aa46", + "url": "https://api.github.com/repos/composer/composer/zipball/eb1ce550ca51134ee619ad3e37f5a0b7e980dd24", + "reference": "eb1ce550ca51134ee619ad3e37f5a0b7e980dd24", "shasum": "" }, "require": { - "justinrainbow/json-schema": "1.1.*", + "justinrainbow/json-schema": "~1.1", "php": ">=5.3.2", - "seld/jsonlint": "1.*", + "seld/jsonlint": "~1.0", "symfony/console": "~2.3", "symfony/finder": "~2.2", "symfony/process": "~2.1" }, "require-dev": { - "phpunit/phpunit": "~3.7.10" + "phpunit/phpunit": "~4.0" }, "suggest": { "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", @@ -54,46 +54,57 @@ "MIT" ], "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be", - "role": "Developer" - }, { "name": "Nils Adermann", "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de", - "role": "Developer" + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" } ], - "description": "Dependency Manager", + "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.", "homepage": "http://getcomposer.org/", "keywords": [ "autoload", "dependency", "package" ], - "time": "2014-01-06 18:39:59" + "time": "2014-12-07 17:15:20" }, { "name": "justinrainbow/json-schema", - "version": "1.1.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "05ff6d8d79fe3ad190b0663d80d3f9deee79416c" + "reference": "2465fe486c864e30badaa4d005ebdf89dbc503f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/05ff6d8d79fe3ad190b0663d80d3f9deee79416c", - "reference": "05ff6d8d79fe3ad190b0663d80d3f9deee79416c", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/2465fe486c864e30badaa4d005ebdf89dbc503f3", + "reference": "2465fe486c864e30badaa4d005ebdf89dbc503f3", "shasum": "" }, "require": { "php": ">=5.3.0" }, + "require-dev": { + "json-schema/json-schema-test-suite": "1.1.0", + "phpdocumentor/phpdocumentor": "~2", + "phpunit/phpunit": "~3.7" + }, + "bin": [ + "bin/validate-json" + ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, "autoload": { "psr-0": { "JsonSchema": "src/" @@ -101,14 +112,9 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "NewBSD" + "BSD-3-Clause" ], "authors": [ - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch", - "homepage": "http://wiedler.ch/igor/" - }, { "name": "Bruno Prieto Reis", "email": "bruno.p.reis@gmail.com" @@ -117,10 +123,13 @@ "name": "Justin Rainbow", "email": "justin.rainbow@gmail.com" }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, { "name": "Robert Schönthal", - "email": "robert.schoenthal@gmail.com", - "homepage": "http://digitalkaoz.net" + "email": "seroscho@googlemail.com" } ], "description": "A library to validate a json schema.", @@ -129,7 +138,7 @@ "json", "schema" ], - "time": "2012-01-03 00:33:17" + "time": "2015-03-27 16:41:39" }, { "name": "magento/magento-composer-installer", @@ -2007,20 +2016,19 @@ }, { "name": "fabpot/php-cs-fixer", - "version": "v1.7", + "version": "v1.6.2", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "6425aeb97ab921371182712a18c280d546e7769b" + "reference": "a574ba148953fea1f78428d4b7c6843e759711f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/6425aeb97ab921371182712a18c280d546e7769b", - "reference": "6425aeb97ab921371182712a18c280d546e7769b", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/a574ba148953fea1f78428d4b7c6843e759711f3", + "reference": "a574ba148953fea1f78428d4b7c6843e759711f3", "shasum": "" }, "require": { - "ext-tokenizer": "*", "php": ">=5.3.6", "sebastian/diff": "~1.1", "symfony/console": "~2.3", @@ -2057,7 +2065,7 @@ } ], "description": "A script to automatically fix Symfony Coding Standard", - "time": "2015-04-16 07:21:30" + "time": "2015-04-13 21:33:33" }, { "name": "league/climate", @@ -3415,12 +3423,12 @@ } ], "aliases": [], - "minimum-stability": "stable", + "minimum-stability": "alpha", "stability-flags": { "composer/composer": 15, "phpmd/phpmd": 0 }, - "prefer-stable": false, + "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "~5.5.0|~5.6.0" diff --git a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductLinkManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductLinkManagementTest.php index 463f490480ce06c6e3016e5606971eb6ee1fe392..caf1b320ef82ce37e47ca055dbcb95bebd599bf9 100644 --- a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductLinkManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductLinkManagementTest.php @@ -33,12 +33,13 @@ class ProductLinkManagementTest extends \Magento\TestFramework\TestCase\WebapiAb $this->assertArrayHasKey(0, $result); $this->assertArrayHasKey('option_id', $result[0]); $this->assertArrayHasKey('is_default', $result[0]); - $this->assertArrayHasKey('is_defined', $result[0]); + $this->assertArrayHasKey('can_change_quantity', $result[0]); $this->assertArrayHasKey('price', $result[0]); $this->assertArrayHasKey('price_type', $result[0]); + $this->assertNotNull($result[0]['id']); - unset($result[0]['option_id'], $result[0]['is_default'], $result[0]['is_defined']); - unset($result[0]['price'], $result[0]['price_type']); + unset($result[0]['option_id'], $result[0]['is_default'], $result[0]['can_change_quantity']); + unset($result[0]['price'], $result[0]['price_type'], $result[0]['id']); ksort($result[0]); ksort($expected[0]); @@ -83,6 +84,55 @@ class ProductLinkManagementTest extends \Magento\TestFramework\TestCase\WebapiAb $this->assertGreaterThan(0, $childId); } + /** + * @magentoApiDataFixture Magento/Bundle/_files/product.php + * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php + */ + public function testSaveChild() + { + $productSku = 'bundle-product'; + $children = $this->getChildren($productSku); + + $linkedProduct = $children[0]; + + //Modify a few fields + $linkedProduct['is_default'] = true; + $linkedProduct['qty'] = 2; + + $this->assertTrue($this->saveChild($productSku, $linkedProduct)); + $children = $this->getChildren($productSku); + $this->assertEquals($linkedProduct, $children[0]); + } + + /** + * @param string $productSku + * @param array $linkedProduct + * @return string + */ + private function saveChild($productSku, $linkedProduct) + { + $resourcePath = self::RESOURCE_PATH . '/:sku/links/:id'; + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => str_replace( + [':sku', ':id'], + [$productSku, $linkedProduct['id']], + $resourcePath + ), + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'SaveChild', + ], + ]; + return $this->_webApiCall( + $serviceInfo, + ['sku' => $productSku, 'linkedProduct' => $linkedProduct] + ); + } + /** * @param string $productSku * @param int $optionId @@ -158,6 +208,6 @@ class ProductLinkManagementTest extends \Magento\TestFramework\TestCase\WebapiAb 'operation' => self::SERVICE_NAME . 'getChildren', ], ]; - return $this->_webApiCall($serviceInfo, ['productId' => $productSku]); + return $this->_webApiCall($serviceInfo, ['productSku' => $productSku]); } } diff --git a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductOptionRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductOptionRepositoryTest.php index 325e35ee8291041dd553575d39ed4523f71e5284..d402aea3d67a790681fe4f8970e5bda17e8bcf67 100644 --- a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductOptionRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductOptionRepositoryTest.php @@ -29,7 +29,7 @@ class ProductOptionRepositoryTest extends \Magento\TestFramework\TestCase\Webapi 'sku' => 'simple', 'qty' => 1, 'position' => 0, - 'is_defined' => true, + 'can_change_quantity' => 1, 'is_default' => false, 'price' => null, 'price_type' => null, @@ -42,9 +42,13 @@ class ProductOptionRepositoryTest extends \Magento\TestFramework\TestCase\Webapi $this->assertArrayHasKey('option_id', $result); $expected['product_links'][0]['option_id'] = $result['option_id']; unset($result['option_id']); + $this->assertNotNull($result['product_links'][0]['id']); + unset($result['product_links'][0]['id']); ksort($expected); ksort($result); + ksort($expected['product_links'][0]); + ksort($result['product_links'][0]); $this->assertEquals($expected, $result); } @@ -66,7 +70,7 @@ class ProductOptionRepositoryTest extends \Magento\TestFramework\TestCase\Webapi 'sku' => 'simple', 'qty' => 1, 'position' => 0, - 'is_defined' => true, + 'can_change_quantity' => 1, 'is_default' => false, 'price' => null, 'price_type' => null, @@ -80,9 +84,13 @@ class ProductOptionRepositoryTest extends \Magento\TestFramework\TestCase\Webapi $this->assertArrayHasKey('option_id', $result[0]); $expected[0]['product_links'][0]['option_id'] = $result[0]['option_id']; unset($result[0]['option_id']); + $this->assertNotNull($result[0]['product_links'][0]['id']); + unset($result[0]['product_links'][0]['id']); ksort($expected[0]); ksort($result[0]); + ksort($expected[0]['product_links'][0]); + ksort($result[0]['product_links'][0]); $this->assertEquals($expected, $result); } diff --git a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductServiceTest.php index 1a817b18d3b593df2cbbef8280a42e40f0d5b84c..80fbe1999d3349f6b889931c23515cba7316e0c2 100644 --- a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductServiceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductServiceTest.php @@ -10,6 +10,7 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Framework\Api\ExtensibleDataInterface; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\WebapiAbstract; +use Magento\Bundle\Api\Data\LinkInterface; /** * Class ProductServiceTest for testing Bundle Product API @@ -19,6 +20,7 @@ class ProductServiceTest extends WebapiAbstract const SERVICE_NAME = 'catalogProductRepositoryV1'; const SERVICE_VERSION = 'V1'; const RESOURCE_PATH = '/V1/products'; + const BUNDLE_PRODUCT_ID = 'sku-test-product-bundle'; /** * @var \Magento\Catalog\Model\Resource\Product\Collection @@ -39,20 +41,7 @@ class ProductServiceTest extends WebapiAbstract */ public function tearDown() { - /** @var \Magento\Framework\Registry $registry */ - $registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry'); - - $registry->unregister('isSecureArea'); - $registry->register('isSecureArea', true); - - $this->productCollection->addFieldToFilter( - 'sku', - ['in' => ['sku-test-product-bundle']] - )->delete(); - unset($this->productCollection); - - $registry->unregister('isSecureArea'); - $registry->register('isSecureArea', false); + $this->deleteProductBySku(self::BUNDLE_PRODUCT_ID); parent::tearDown(); } @@ -61,7 +50,6 @@ class ProductServiceTest extends WebapiAbstract */ public function testCreateBundle() { - $this->markTestSkipped('Processing of custom attributes has been changed in MAGETWO-34448.'); $bundleProductOptions = [ [ "title" => "test option", @@ -73,38 +61,365 @@ class ProductServiceTest extends WebapiAbstract "qty" => 1, 'is_default' => false, 'price' => 1.0, - 'price_type' => 1 + 'price_type' => LinkInterface::PRICE_TYPE_FIXED, ], ], ], ]; - $uniqueId = 'sku-test-product-bundle'; $product = [ - "sku" => $uniqueId, - "name" => $uniqueId, + "sku" => self::BUNDLE_PRODUCT_ID, + "name" => self::BUNDLE_PRODUCT_ID, "type_id" => "bundle", "price" => 50, 'attribute_set_id' => 4, + "custom_attributes" => [ + [ + "attribute_code" => "price_type", + "value" => \Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED, + ], + [ + "attribute_code" => "price_view", + "value" => 1, + ], + ], "extension_attributes" => [ - "price_type" => \Magento\Bundle\Model\Product\Price::PRICE_TYPE_DYNAMIC, "bundle_product_options" => $bundleProductOptions, - "price_view" => "test" ], ]; $response = $this->createProduct($product); - $this->assertEquals($uniqueId, $response[ProductInterface::SKU]); + $this->assertEquals(self::BUNDLE_PRODUCT_ID, $response[ProductInterface::SKU]); + $this->assertEquals(50, $response['price']); + $this->assertTrue( + isset($response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["bundle_product_options"]) + ); + $resultBundleProductOptions + = $response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["bundle_product_options"]; + $this->assertTrue(isset($resultBundleProductOptions[0]["product_links"][0]["sku"])); + $this->assertEquals('simple', $resultBundleProductOptions[0]["product_links"][0]["sku"]); + + $response = $this->getProduct(self::BUNDLE_PRODUCT_ID); + $this->assertTrue( + isset($response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["bundle_product_options"]) + ); + $resultBundleProductOptions + = $response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["bundle_product_options"]; + $this->assertTrue(isset($resultBundleProductOptions[0]["product_links"][0]["sku"])); + $this->assertEquals('simple', $resultBundleProductOptions[0]["product_links"][0]["sku"]); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/products_new.php + * @magentoApiDataFixture Magento/Catalog/_files/second_product_simple.php + */ + public function testUpdateBundleModifyExistingSelection() + { + $bundleProduct = $this->createFixedPriceBundleProduct(); + $bundleProductOptions = $this->getBundleProductOptions($bundleProduct); + + $existingSelectionId = $bundleProductOptions[0]['product_links'][0]['id']; + + //Change the type of existing option + $bundleProductOptions[0]['type'] = 'select'; + //Change the sku of existing link and qty + $bundleProductOptions[0]['product_links'][0]['sku'] = 'simple2'; + $bundleProductOptions[0]['product_links'][0]['qty'] = 2; + $bundleProductOptions[0]['product_links'][0]['price'] = 10; + $bundleProductOptions[0]['product_links'][0]['price_type'] = 1; + $this->setBundleProductOptions($bundleProduct, $bundleProductOptions); + + $updatedProduct = $this->saveProduct($bundleProduct); + + $bundleOptions = $this->getBundleProductOptions($updatedProduct); + $this->assertEquals('select', $bundleOptions[0]['type']); + $this->assertEquals('simple2', $bundleOptions[0]['product_links'][0]['sku']); + $this->assertEquals(2, $bundleOptions[0]['product_links'][0]['qty']); + $this->assertEquals($existingSelectionId, $bundleOptions[0]['product_links'][0]['id']); + $this->assertEquals(10, $bundleOptions[0]['product_links'][0]['price']); + $this->assertEquals(1, $bundleOptions[0]['product_links'][0]['price_type']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/products_new.php + * @magentoApiDataFixture Magento/Catalog/_files/second_product_simple.php + */ + public function testUpdateBundleModifyExistingOptionOnly() + { + $bundleProduct = $this->createFixedPriceBundleProduct(); + $bundleProductOptions = $this->getBundleProductOptions($bundleProduct); + + $existingSelectionId = $bundleProductOptions[0]['product_links'][0]['id']; + + //Change the type of existing option + $bundleProductOptions[0]['type'] = 'select'; + //unset product_links attribute + unset($bundleProductOptions[0]['product_links']); + $this->setBundleProductOptions($bundleProduct, $bundleProductOptions); + + $updatedProduct = $this->saveProduct($bundleProduct); + + $bundleOptions = $this->getBundleProductOptions($updatedProduct); + $this->assertEquals('select', $bundleOptions[0]['type']); + $this->assertEquals('simple', $bundleOptions[0]['product_links'][0]['sku']); + $this->assertEquals(1, $bundleOptions[0]['product_links'][0]['qty']); + $this->assertEquals($existingSelectionId, $bundleOptions[0]['product_links'][0]['id']); + $this->assertEquals(20, $bundleOptions[0]['product_links'][0]['price']); + $this->assertEquals(1, $bundleOptions[0]['product_links'][0]['price_type']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/products_new.php + * @magentoApiDataFixture Magento/Catalog/_files/second_product_simple.php + */ + public function testUpdateProductWithoutBundleOptions() + { + $bundleProduct = $this->createFixedPriceBundleProduct(); + $bundleProductOptions = $this->getBundleProductOptions($bundleProduct); + + $existingSelectionId = $bundleProductOptions[0]['product_links'][0]['id']; + + //unset bundle_product_options + unset($bundleProductOptions[0]['product_links']); + $this->setBundleProductOptions($bundleProduct, null); + + $updatedProduct = $this->saveProduct($bundleProduct); + + $bundleOptions = $this->getBundleProductOptions($updatedProduct); + $this->assertEquals('checkbox', $bundleOptions[0]['type']); + $this->assertEquals('simple', $bundleOptions[0]['product_links'][0]['sku']); + $this->assertEquals(1, $bundleOptions[0]['product_links'][0]['qty']); + $this->assertEquals($existingSelectionId, $bundleOptions[0]['product_links'][0]['id']); + $this->assertEquals(20, $bundleOptions[0]['product_links'][0]['price']); + $this->assertEquals(1, $bundleOptions[0]['product_links'][0]['price_type']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/products_new.php + * @magentoApiDataFixture Magento/Catalog/_files/second_product_simple.php + */ + public function testUpdateBundleAddSelection() + { + $bundleProduct = $this->createDynamicBundleProduct(); + $bundleProductOptions = $this->getBundleProductOptions($bundleProduct); + + //Add a selection to existing option + $bundleProductOptions[0]['product_links'][] = [ + 'sku' => 'simple2', + 'qty' => 2, + "price" => 20, + "price_type" => 1, + "is_default" => false, + ]; + $this->setBundleProductOptions($bundleProduct, $bundleProductOptions); + $updatedProduct = $this->saveProduct($bundleProduct); + + $bundleOptions = $this->getBundleProductOptions($updatedProduct); + $this->assertEquals('simple', $bundleOptions[0]['product_links'][0]['sku']); + $this->assertEquals('simple2', $bundleOptions[0]['product_links'][1]['sku']); + $this->assertEquals(2, $bundleOptions[0]['product_links'][1]['qty']); + $this->assertGreaterThan( + $bundleOptions[0]['product_links'][0]['id'], + $bundleOptions[0]['product_links'][1]['id'] + ); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/products_new.php + * @magentoApiDataFixture Magento/Catalog/_files/second_product_simple.php + */ + public function testUpdateBundleAddAndDeleteOption() + { + $bundleProduct = $this->createDynamicBundleProduct(); + + $bundleProductOptions = $this->getBundleProductOptions($bundleProduct); + + $oldOptionId = $bundleProductOptions[0]['option_id']; + //replace current option with a new option + $bundleProductOptions[0] = [ + 'title' => 'new option', + 'required' => true, + 'type' => 'select', + 'product_links' => [ + [ + 'sku' => 'simple2', + 'qty' => 2, + "price" => 20, + "price_type" => 1, + "is_default" => false, + ], + ], + ]; + $this->setBundleProductOptions($bundleProduct, $bundleProductOptions); + $this->saveProduct($bundleProduct); + + $updatedProduct = $this->getProduct(self::BUNDLE_PRODUCT_ID); + $bundleOptions = $this->getBundleProductOptions($updatedProduct); + $this->assertEquals('new option', $bundleOptions[0]['title']); + $this->assertTrue($bundleOptions[0]['required']); + $this->assertEquals('select', $bundleOptions[0]['type']); + $this->assertGreaterThan($oldOptionId, $bundleOptions[0]['option_id']); + $this->assertFalse(isset($bundleOptions[1])); + $this->assertEquals('simple2', $bundleOptions[0]['product_links'][0]['sku']); + $this->assertEquals(2, $bundleOptions[0]['product_links'][0]['qty']); + } + + /** + * Get the bundle_product_options custom attribute from product, null if the attribute is not set + * + * @param array $product + * @return array|null + */ + protected function getBundleProductOptions($product) + { + if (isset($product[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["bundle_product_options"])) { + return $product[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["bundle_product_options"]; + } else { + return null; + } + } + + /** + * Set the bundle_product_options custom attribute, replace existing attribute if exists + * + * @param array $product + * @param array $bundleProductOptions + */ + protected function setBundleProductOptions(&$product, $bundleProductOptions) + { + $product["extension_attributes"]["bundle_product_options"] = $bundleProductOptions; + return; + } + + /** + * Create dynamic bundle product with one option + * + * @return array + */ + protected function createDynamicBundleProduct() + { + $bundleProductOptions = [ + [ + "title" => "test option", + "type" => "checkbox", + "required" => 1, + "product_links" => [ + [ + "sku" => 'simple', + "qty" => 1, + "is_default" => true, + "price" => 10, + "price_type" => 1, + ], + ], + ], + ]; + + $uniqueId = self::BUNDLE_PRODUCT_ID; + $product = [ + "sku" => $uniqueId, + "name" => $uniqueId, + "type_id" => "bundle", + 'attribute_set_id' => 4, + "custom_attributes" => [ + "price_type" => [ + 'attribute_code' => 'price_type', + 'value' => \Magento\Bundle\Model\Product\Price::PRICE_TYPE_DYNAMIC + ], + "price_view" => [ + "attribute_code" => "price_view", + "value" => "1", + ], + ], + "extension_attributes" => [ + "bundle_product_options" => $bundleProductOptions, + ], + ]; + + $response = $this->createProduct($product); + $this->assertTrue( + isset($response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["bundle_product_options"]) + ); $resultBundleProductOptions = $response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["bundle_product_options"]; - $this->assertEquals($bundleProductOptions, $resultBundleProductOptions); + $this->assertTrue(isset($resultBundleProductOptions[0]["product_links"][0]["sku"])); $this->assertEquals('simple', $resultBundleProductOptions[0]["product_links"][0]["sku"]); + $this->assertTrue(isset($response['custom_attributes'])); + $customAttributes = $this->convertCustomAttributes($response['custom_attributes']); + $this->assertTrue(isset($customAttributes['price_type'])); + $this->assertEquals(\Magento\Bundle\Model\Product\Price::PRICE_TYPE_DYNAMIC, $customAttributes['price_type']); + $this->assertTrue(isset($customAttributes['price_view'])); + $this->assertEquals(1, $customAttributes['price_view']); + return $response; + } + + /** + * Create fixed price bundle product with one option + * + * @return array + */ + protected function createFixedPriceBundleProduct() + { + $bundleProductOptions = [ + [ + "title" => "test option", + "type" => "checkbox", + "required" => 1, + "product_links" => [ + [ + "sku" => 'simple', + "qty" => 1, + "price" => 20, + "price_type" => 1, + "is_default" => true, + ], + ], + ], + ]; + + $uniqueId = self::BUNDLE_PRODUCT_ID; + $product = [ + "sku" => $uniqueId, + "name" => $uniqueId, + "type_id" => "bundle", + "price" => 50, + 'attribute_set_id' => 4, + "custom_attributes" => [ + "price_type" => [ + 'attribute_code' => 'price_type', + 'value' => \Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED + ], + "price_view" => [ + "attribute_code" => "price_view", + "value" => "1", + ], + ], + "extension_attributes" => [ + "bundle_product_options" => $bundleProductOptions, + ], + ]; - $response = $this->getProduct($uniqueId); + $response = $this->createProduct($product); $resultBundleProductOptions = $response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["bundle_product_options"]; $this->assertEquals('simple', $resultBundleProductOptions[0]["product_links"][0]["sku"]); + $this->assertTrue(isset($response['custom_attributes'])); + $customAttributes = $this->convertCustomAttributes($response['custom_attributes']); + $this->assertTrue(isset($customAttributes['price_type'])); + $this->assertEquals(\Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED, $customAttributes['price_type']); + $this->assertTrue(isset($customAttributes['price_view'])); + $this->assertEquals(1, $customAttributes['price_view']); + return $response; + } + + protected function convertCustomAttributes($customAttributes) + { + $convertedCustomAttribute = []; + foreach ($customAttributes as $customAttribute) { + $convertedCustomAttribute[$customAttribute['attribute_code']] = $customAttribute['value']; + } + return $convertedCustomAttribute; } /** @@ -154,7 +469,56 @@ class ProductServiceTest extends WebapiAbstract ]; $requestData = ['product' => $product]; $response = $this->_webApiCall($serviceInfo, $requestData); - $product[ProductInterface::SKU] = $response[ProductInterface::SKU]; - return $product; + return $response; + } + + /** + * Delete a product by sku + * + * @param $productSku + * @return bool + */ + protected function deleteProductBySku($productSku) + { + $resourcePath = self::RESOURCE_PATH . '/' . $productSku; + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => $resourcePath, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_DELETE + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'deleteById', + ], + ]; + $requestData = ["sku" => $productSku]; + $response = $this->_webApiCall($serviceInfo, $requestData); + return $response; + } + + /** + * Save product + * + * @param array $product + * @return array the created product data + */ + protected function saveProduct($product) + { + $resourcePath = self::RESOURCE_PATH . '/' . $product['sku']; + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => $resourcePath, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + $requestData = ['product' => $product]; + $response = $this->_webApiCall($serviceInfo, $requestData); + return $response; } } diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/Constraint/AssertAgreementTextPresent.php b/dev/tests/functional/tests/app/Magento/Install/Test/Constraint/AssertAgreementTextPresent.php index 7c59cf1d4c263b8493ce2dcbb0889824a55e72ed..1bc49b0acc7773869bb8b2af6f669c01661f94df 100644 --- a/dev/tests/functional/tests/app/Magento/Install/Test/Constraint/AssertAgreementTextPresent.php +++ b/dev/tests/functional/tests/app/Magento/Install/Test/Constraint/AssertAgreementTextPresent.php @@ -8,6 +8,7 @@ namespace Magento\Install\Test\Constraint; use Magento\Install\Test\Page\Install; use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\TestFramework\Inspection\Exception; /** * Check that agreement text present on Terms & Agreement page during install. @@ -15,9 +16,14 @@ use Magento\Mtf\Constraint\AbstractConstraint; class AssertAgreementTextPresent extends AbstractConstraint { /** - * Part of license agreement text. + * Part of Default license agreement text. */ - const LICENSE_AGREEMENT_TEXT = 'Open Software License ("OSL") v. 3.0'; + const DEFAULT_LICENSE_AGREEMENT_TEXT = 'Open Software License ("OSL") v. 3.0'; + + /** + * Part of Default license agreement text. + */ + const LICENSE_AGREEMENT_TEXT = 'END USER LICENSE AGREEMENT'; /** * Assert that part of license agreement text is present on Terms & Agreement page. @@ -27,11 +33,19 @@ class AssertAgreementTextPresent extends AbstractConstraint */ public function processAssert(Install $installPage) { - \PHPUnit_Framework_Assert::assertContains( - self::LICENSE_AGREEMENT_TEXT, - $installPage->getLicenseBlock()->getLicense(), - 'License agreement text is absent.' - ); + try { + \PHPUnit_Framework_Assert::assertContains( + self::LICENSE_AGREEMENT_TEXT, + $installPage->getLicenseBlock()->getLicense(), + 'License agreement text is absent.' + ); + } catch (\Exception $e) { + \PHPUnit_Framework_Assert::assertContains( + self::DEFAULT_LICENSE_AGREEMENT_TEXT, + $installPage->getLicenseBlock()->getLicense(), + 'License agreement text is absent.' + ); + } } /** diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/Plugin/BundleSaveOptionsTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/Plugin/BundleSaveOptionsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b46a6544e18aa8f3c0a466afb394c30183e3223e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/Plugin/BundleSaveOptionsTest.php @@ -0,0 +1,84 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Bundle\Model\Plugin; + +class BundleSaveOptionsTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Catalog\Model\Product + */ + protected $_model; + + /** + * @var \Magento\Catalog\Api\ProductRepositoryInterface + */ + protected $productRepository; + + protected function setUp() + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->productRepository = $objectManager->get('Magento\Catalog\Api\ProductRepositoryInterface'); + } + + /** + * @magentoDataFixture Magento/Bundle/_files/product.php + * @magentoDbIsolation enabled + */ + public function testSaveSuccess() + { + $title = "new title"; + $bundleProductSku = 'bundle-product'; + $product = $this->productRepository->get($bundleProductSku); + $bundleExtensionAttributes = $product->getExtensionAttributes()->getBundleProductOptions(); + $bundleOption = $bundleExtensionAttributes[0]; + $this->assertEquals(true, $bundleOption->getRequired()); + $bundleOption->setTitle($title); + + $oldDescription = $product->getDescription(); + $description = $oldDescription . "hello"; + $product->setDescription($description); + $product->getExtensionAttributes()->setBundleProductOptions([$bundleOption]); + $product = $this->productRepository->save($product); + + $this->assertEquals($description, $product->getDescription()); + $this->assertEquals($title, $product->getExtensionAttributes()->getBundleProductOptions()[0]->getTitle()); + } + + /** + * @magentoDataFixture Magento/Bundle/_files/product.php + * @magentoDbIsolation enabled + */ + public function testSaveFailure() + { + $this->markTestSkipped("When MAGETWO-36510 is fixed, need to change Dbisolation to disabled"); + $bundleProductSku = 'bundle-product'; + $product = $this->productRepository->get($bundleProductSku); + $bundleExtensionAttributes = $product->getExtensionAttributes()->getBundleProductOptions(); + $bundleOption = $bundleExtensionAttributes[0]; + $this->assertEquals(true, $bundleOption->getRequired()); + $bundleOption->setRequired(false); + //set an incorrect option id to trigger exception + $bundleOption->setOptionId(-1); + + $description = "hello"; + + $product->setDescription($description); + $product->getExtensionAttributes()->setBundleProductOptions([$bundleOption]); + $caughtException = false; + try { + $this->productRepository->save($product); + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + $caughtException = true; + } + + $this->assertTrue($caughtException); + /** @var \Magento\Catalog\Model\Product $product */ + $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create('Magento\Catalog\Model\Product')->load($product->getId()); + $this->assertEquals(null, $product->getDescription()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/product.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/product.php index 1bb799689c7ec3748bb251a5ae75f80dbd996271..663c2d0225742e375ea459b0b70ae903d90e00b7 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/product.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/product.php @@ -31,6 +31,12 @@ $product->setTypeId( \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED )->setStockData( ['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1] +)->setPriceView( + 1 +)->setPriceType( + 1 +)->setPrice( + 10.0 )->setBundleOptionsData( [ [ diff --git a/dev/tests/performance/benchmark.jmx b/dev/tests/performance/benchmark.jmx index 25bfa9805c159c8af83d90b1733745e3709676a4..b9c95e6d8ea77ad0c41f8ad5fa86a583504ed87b 100644 --- a/dev/tests/performance/benchmark.jmx +++ b/dev/tests/performance/benchmark.jmx @@ -848,7 +848,7 @@ <collectionProp name="Arguments.arguments"> <elementProp name="billing[address_id]" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">37</stringProp> + <stringProp name="Argument.value"></stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">billing[address_id]</stringProp> @@ -946,7 +946,7 @@ </elementProp> <elementProp name="billing[save_in_address_book]" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">1</stringProp> + <stringProp name="Argument.value">0</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">billing[save_in_address_book]</stringProp> diff --git a/lib/internal/Magento/Framework/DB/Ddl/Table.php b/lib/internal/Magento/Framework/DB/Ddl/Table.php index eee5ed9bc320a574eec14163bcf05fa4b21fd353..a623ba4c0fb3398a107d72140fe711adfa19b946 100644 --- a/lib/internal/Magento/Framework/DB/Ddl/Table.php +++ b/lib/internal/Magento/Framework/DB/Ddl/Table.php @@ -59,7 +59,7 @@ class Table const MAX_VARBINARY_SIZE = 2147483648; /** - * Default values for timestampses - fill with current timestamp on inserting record, on changing and both cases + * Default values for timestamps - fill with current timestamp on inserting record, on changing and both cases */ const TIMESTAMP_INIT_UPDATE = 'TIMESTAMP_INIT_UPDATE'; diff --git a/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php b/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php index b90ff7acb3929174c921ac4a7e0ea5c3e464fab7..7b70fa5d35e61e3174d0b008a275f2afd8880d3c 100644 --- a/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php +++ b/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php @@ -205,6 +205,21 @@ abstract class AbstractExtensibleModel extends AbstractModel implements return parent::unsetData($key); } + /** + * Convert custom values if necessary + * + * @param array $customAttributes + * @return void + */ + protected function convertCustomAttributeValues(array &$customAttributes) + { + foreach ($customAttributes as $attributeCode => $attributeValue) { + if ($attributeValue instanceof \Magento\Framework\Api\AttributeValue) { + $customAttributes[$attributeCode] = $attributeValue->getValue(); + } + } + } + /** * Object data getter * @@ -231,6 +246,7 @@ abstract class AbstractExtensibleModel extends AbstractModel implements $customAttributes = isset($this->_data[self::CUSTOM_ATTRIBUTES]) ? $this->_data[self::CUSTOM_ATTRIBUTES] : []; + $this->convertCustomAttributeValues($customAttributes); $data = array_merge($this->_data, $customAttributes); unset($data[self::CUSTOM_ATTRIBUTES]); } else { @@ -238,6 +254,9 @@ abstract class AbstractExtensibleModel extends AbstractModel implements if ($data === null) { /** Try to find necessary data in custom attributes */ $data = parent::getData(self::CUSTOM_ATTRIBUTES . "/{$key}", $index); + if ($data instanceof \Magento\Framework\Api\AttributeValue) { + $data = $data->getValue(); + } } } return $data; diff --git a/lib/internal/Magento/Framework/Model/AbstractModel.php b/lib/internal/Magento/Framework/Model/AbstractModel.php index 43223069055fdaab1fe7decf6754233bf990ab5a..fcd03252a4160d16805909a3bc45492f896f2e85 100644 --- a/lib/internal/Magento/Framework/Model/AbstractModel.php +++ b/lib/internal/Magento/Framework/Model/AbstractModel.php @@ -127,6 +127,13 @@ abstract class AbstractModel extends \Magento\Framework\Object */ protected $_actionValidator; + /** + * Array to store object's original data + * + * @var array + */ + protected $storedData = []; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -301,6 +308,7 @@ abstract class AbstractModel extends \Magento\Framework\Object $this->_afterLoad(); $this->setOrigData(); $this->_hasDataChanges = false; + $this->updateStoredData(); return $this; } @@ -354,6 +362,7 @@ abstract class AbstractModel extends \Magento\Framework\Object { $this->getResource()->afterLoad($this); $this->_afterLoad(); + $this->updateStoredData(); return $this; } @@ -566,6 +575,7 @@ abstract class AbstractModel extends \Magento\Framework\Object $this->_eventManager->dispatch('model_save_after', ['object' => $this]); $this->_eventManager->dispatch('clean_cache_by_tags', ['object' => $this]); $this->_eventManager->dispatch($this->_eventPrefix . '_save_after', $this->_getEventData()); + $this->updateStoredData(); return $this; } @@ -611,6 +621,7 @@ abstract class AbstractModel extends \Magento\Framework\Object $this->_eventManager->dispatch('model_delete_after', ['object' => $this]); $this->_eventManager->dispatch('clean_cache_by_tags', ['object' => $this]); $this->_eventManager->dispatch($this->_eventPrefix . '_delete_after', $this->_getEventData()); + $this->storedData = []; return $this; } @@ -689,4 +700,29 @@ abstract class AbstractModel extends \Magento\Framework\Object { return $this; } + + /** + * Synchronize object's stored data with the actual data + * + * @return $this + */ + private function updateStoredData() + { + if (isset($this->_data)) { + $this->storedData = $this->_data; + } else { + $this->storedData = []; + } + return $this; + } + + /** + * Model StoredData getter + * + * @return array + */ + public function getStoredData() + { + return $this->storedData; + } } diff --git a/lib/internal/Magento/Framework/Model/Resource/Db/AbstractDb.php b/lib/internal/Magento/Framework/Model/Resource/Db/AbstractDb.php index 393c90be8e000891cad8ca84d83e6e8cbcf3ccc7..351365b08d9eb57d45b6b65a50c53edbb93d482c 100644 --- a/lib/internal/Magento/Framework/Model/Resource/Db/AbstractDb.php +++ b/lib/internal/Magento/Framework/Model/Resource/Db/AbstractDb.php @@ -417,9 +417,10 @@ abstract class AbstractDb extends \Magento\Framework\Model\Resource\AbstractReso * Not auto increment primary key support */ if ($this->_isPkAutoIncrement) { - $data = $this->_prepareDataForSave($object); - unset($data[$this->getIdFieldName()]); - $this->_getWriteAdapter()->update($this->getMainTable(), $data, $condition); + $data = $this->prepareDataForUpdate($object); + if (!empty($data)) { + $this->_getWriteAdapter()->update($this->getMainTable(), $data, $condition); + } } else { $select = $this->_getWriteAdapter()->select()->from( $this->getMainTable(), @@ -428,8 +429,7 @@ abstract class AbstractDb extends \Magento\Framework\Model\Resource\AbstractReso $condition ); if ($this->_getWriteAdapter()->fetchOne($select) !== false) { - $data = $this->_prepareDataForSave($object); - unset($data[$this->getIdFieldName()]); + $data = $this->prepareDataForUpdate($object); if (!empty($data)) { $this->_getWriteAdapter()->update($this->getMainTable(), $data, $condition); } @@ -770,4 +770,27 @@ abstract class AbstractDb extends \Magento\Framework\Model\Resource\AbstractReso } return $checksum; } + + /** + * Get the array of data fields that was changed or added + * + * @param \Magento\Framework\Model\AbstractModel $object + * @return array + */ + protected function prepareDataForUpdate($object) + { + $data = $object->getData(); + foreach ($object->getStoredData() as $key => $value) { + if (array_key_exists($key, $data) && $data[$key] === $value) { + unset($data[$key]); + } + } + $dataObject = clone $object; + $dataObject->setData($data); + $data = $this->_prepareDataForTable($dataObject, $this->getMainTable()); + unset($data[$this->getIdFieldName()]); + unset($dataObject); + + return $data; + } } diff --git a/lib/internal/Magento/Framework/Model/Test/Unit/AbstractModelTest.php b/lib/internal/Magento/Framework/Model/Test/Unit/AbstractModelTest.php index 49b76a75b8ac30215540f4ebee497986fa232d84..71df4ee8ed9475b92e10972758dc62b3b5926bb3 100644 --- a/lib/internal/Magento/Framework/Model/Test/Unit/AbstractModelTest.php +++ b/lib/internal/Magento/Framework/Model/Test/Unit/AbstractModelTest.php @@ -108,4 +108,22 @@ class AbstractModelTest extends \PHPUnit_Framework_TestCase $this->resourceMock->expects($this->once())->method('delete')->with($this->model); $this->model->delete(); } + + public function testUpdateStoredData() + { + $this->model->setData( + [ + 'id' => 1000, + 'name' => 'Test Name' + ] + ); + $this->assertEmpty($this->model->getStoredData()); + $this->model->afterLoad(); + $this->assertEquals($this->model->getData(), $this->model->getStoredData()); + $this->model->setData('value', 'Test Value'); + $this->model->afterSave(); + $this->assertEquals($this->model->getData(), $this->model->getStoredData()); + $this->model->afterDelete(); + $this->assertEmpty($this->model->getStoredData()); + } } diff --git a/lib/internal/Magento/Framework/Model/Test/Unit/Resource/Db/AbstractDbTest.php b/lib/internal/Magento/Framework/Model/Test/Unit/Resource/Db/AbstractDbTest.php index 66e49e079e7f0f340db69bf355c973c1b894e9a9..6745f6d62c7412089519d0bd6569d0ef29624cbd 100644 --- a/lib/internal/Magento/Framework/Model/Test/Unit/Resource/Db/AbstractDbTest.php +++ b/lib/internal/Magento/Framework/Model/Test/Unit/Resource/Db/AbstractDbTest.php @@ -61,7 +61,12 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase $this->_model = $this->getMockForAbstractClass( 'Magento\Framework\Model\Resource\Db\AbstractDb', - [$contextMock] + [$contextMock], + '', + true, + true, + true, + ['_prepareDataForTable'] ); } @@ -422,4 +427,85 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase [null, false] ]; } + + public function testPrepareDataForUpdate() + { + $adapterInterfaceMock = $this->getMock('\Magento\Framework\DB\Adapter\AdapterInterface', [], [], '', false); + $context = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject( + 'Magento\Framework\Model\Context' + ); + $registryMock = $this->getMock('\Magento\Framework\Registry', [], [], '', false); + $resourceMock = $this->getMock( + 'Magento\Framework\Model\Resource\Db\AbstractDb', + [ + '_construct', + '_getReadAdapter', + '_getWriteAdapter', + '__wakeup', + 'getIdFieldName' + ], + [], + '', + false + ); + $adapterMock = $this->getMock('Magento\Framework\DB\Adapter\AdapterInterface', [], [], '', false); + $resourceMock->expects($this->any()) + ->method('_getWriteAdapter') + ->will($this->returnValue($adapterMock)); + $resourceCollectionMock = $this->getMock('Magento\Framework\Data\Collection\Db', [], [], '', false); + $abstractModelMock = $this->getMockForAbstractClass( + 'Magento\Framework\Model\AbstractModel', + [$context, $registryMock, $resourceMock, $resourceCollectionMock] + ); + $data = 'tableName'; + $this->_resourcesMock->expects($this->any()) + ->method('getConnection') + ->will($this->returnValue($adapterInterfaceMock) + ); + $this->_resourcesMock->expects($this->any())->method('getTableName')->with($data)->will( + $this->returnValue('tableName') + ); + $this->_resourcesMock->expects($this->any()) + ->method('_getWriteAdapter') + ->will($this->returnValue($adapterInterfaceMock)); + $mainTableReflection = new \ReflectionProperty( + 'Magento\Framework\Model\Resource\Db\AbstractDb', + '_mainTable' + ); + $mainTableReflection->setAccessible(true); + $mainTableReflection->setValue($this->_model, 'tableName'); + $idFieldNameReflection = new \ReflectionProperty( + 'Magento\Framework\Model\Resource\Db\AbstractDb', + '_idFieldName' + ); + $idFieldNameReflection->setAccessible(true); + $idFieldNameReflection->setValue($this->_model, 'idFieldName'); + $adapterInterfaceMock->expects($this->any())->method('save')->with('tableName', 'idFieldName'); + $adapterInterfaceMock->expects($this->any())->method('quoteInto')->will($this->returnValue('idFieldName')); + + $abstractModelMock->setIdFieldName('id'); + $abstractModelMock->setData( + [ + 'id' => 12345, + 'name' => 'Test Name', + 'value' => 'Test Value' + ] + ); + $abstractModelMock->afterLoad(); + $this->assertEquals($abstractModelMock->getData(), $abstractModelMock->getStoredData()); + $newData = ['value' => 'Test Value New']; + $this->_model->expects($this->once())->method('_prepareDataForTable')->will($this->returnValue($newData)); + $abstractModelMock->addData($newData); + $this->assertNotEquals($abstractModelMock->getData(), $abstractModelMock->getStoredData()); + $abstractModelMock->isObjectNew(false); + $adapterInterfaceMock->expects($this->once()) + ->method('update') + ->with( + 'tableName', + $newData, + 'idFieldName' + ); + + $this->_model->save($abstractModelMock); + } } diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index 14caad463c95a3878d7351a339c20173665b6dc1..e232546d6ac1eea71af4a52b97f22aace0e83717 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -137,8 +137,7 @@ class ServiceInputProcessor if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) { $className = substr($className, 0, -strlen('Interface')); } - $factory = $this->objectManager->get($className . 'Factory'); - $object = $factory->create(); + $object = $this->objectManager->create($className); foreach ($data as $propertyName => $value) { // Converts snake_case to uppercase CamelCase to help form getter/setter method names diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php index 41cdf8184ba372143f8ed2fdde2ff790f8633fa0..ac75d2c131fb1cd2319c1ebd36ab58014917ec87 100644 --- a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php @@ -42,6 +42,14 @@ class ServiceInputProcessorTest extends \PHPUnit_Framework_TestCase $this->objectManagerMock = $this->getMockBuilder('\Magento\Framework\ObjectManagerInterface') ->disableOriginalConstructor() ->getMock(); + $this->objectManagerMock->expects($this->any()) + ->method('create') + ->willReturnCallback( + function ($className) use ($objectManager) { + return $objectManager->getObject($className); + } + ); + /** @var \Magento\Framework\Reflection\TypeProcessor $typeProcessor */ $typeProcessor = $objectManager->getObject('Magento\Framework\Reflection\TypeProcessor'); $cache = $this->getMockBuilder('Magento\Framework\App\Cache\Type\Webapi') @@ -119,12 +127,6 @@ class ServiceInputProcessorTest extends \PHPUnit_Framework_TestCase public function testNestedDataProperties() { - $this->setupFactory( - [ - 'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Nested', - '\Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Simple', - ] - ); $data = ['nested' => ['details' => ['entityId' => 15, 'name' => 'Test']]]; $result = $this->serviceInputProcessor->process( 'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\TestService', @@ -167,7 +169,6 @@ class ServiceInputProcessorTest extends \PHPUnit_Framework_TestCase public function testAssociativeArrayProperties() { - $this->setupFactory(['Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Simple']); $data = ['associativeArray' => ['key' => 'value', 'key_two' => 'value_two']]; $result = $this->serviceInputProcessor->process( 'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\TestService', @@ -186,7 +187,6 @@ class ServiceInputProcessorTest extends \PHPUnit_Framework_TestCase public function testAssociativeArrayPropertiesWithItem() { - $this->setupFactory(['Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\AssociativeArray']); $data = ['associativeArray' => ['item' => 'value']]; $result = $this->serviceInputProcessor->process( 'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\TestService', @@ -204,7 +204,6 @@ class ServiceInputProcessorTest extends \PHPUnit_Framework_TestCase public function testAssociativeArrayPropertiesWithItemArray() { - $this->setupFactory(['Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\AssociativeArray']); $data = ['associativeArray' => ['item' => ['value1','value2']]]; $result = $this->serviceInputProcessor->process( 'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\TestService', @@ -223,11 +222,6 @@ class ServiceInputProcessorTest extends \PHPUnit_Framework_TestCase public function testArrayOfDataObjectProperties() { - $this->setupFactory( - [ - '\Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Simple' - ] - ); $data = [ 'dataObjects' => [ ['entityId' => 14, 'name' => 'First'], @@ -259,7 +253,6 @@ class ServiceInputProcessorTest extends \PHPUnit_Framework_TestCase public function testNestedSimpleArrayProperties() { - $this->setupFactory(['Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\SimpleArray']); $data = ['arrayData' => ['ids' => [1, 2, 3, 4]]]; $result = $this->serviceInputProcessor->process( 'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\TestService', @@ -281,7 +274,6 @@ class ServiceInputProcessorTest extends \PHPUnit_Framework_TestCase public function testNestedAssociativeArrayProperties() { - $this->setupFactory(['Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\AssociativeArray']); $data = [ 'associativeArrayData' => ['associativeArray' => ['key' => 'value', 'key2' => 'value2']], ]; @@ -305,12 +297,6 @@ class ServiceInputProcessorTest extends \PHPUnit_Framework_TestCase public function testNestedArrayOfDataObjectProperties() { - $this->setupFactory( - [ - 'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\DataArray', - '\Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Simple', - ] - ); $data = [ 'dataObjects' => [ 'items' => [['entityId' => 1, 'name' => 'First'], ['entityId' => 2, 'name' => 'Second']], @@ -352,14 +338,6 @@ class ServiceInputProcessorTest extends \PHPUnit_Framework_TestCase */ public function testCustomAttributesProperties($customAttributeType, $inputData, $expectedObject) { - $this->setupFactory( - [ - 'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\ObjectWithCustomAttributes', - '\Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Simple', - 'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Simple', - 'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\SimpleArray', - ] - ); $this->customAttributeTypeLocator->expects($this->any())->method('getType')->willReturn($customAttributeType); $result = $this->serviceInputProcessor->process( @@ -521,27 +499,4 @@ class ServiceInputProcessorTest extends \PHPUnit_Framework_TestCase ]] ); } - protected function setupFactory(array $classNames) - { - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - - $returnValueMap = []; - foreach ($classNames as $className) { - $factoryMock = $this->getMockBuilder($className . 'Factory') - ->setMethods(['create']) - ->disableOriginalConstructor() - ->getMock(); - $factoryMock->expects($this->any()) - ->method('create') - ->willReturnCallback( - function () use ($objectManager, $className) { - return $objectManager->getObject($className); - } - ); - $returnValueMap[] = [$className . 'Factory', $factoryMock]; - } - $this->objectManagerMock->expects($this->any()) - ->method('get') - ->will($this->returnValueMap($returnValueMap)); - } } diff --git a/setup/src/Magento/Setup/Model/License.php b/setup/src/Magento/Setup/Model/License.php index beb3d857aedea1ca21ca059697d79434a114a263..1c7458bd3404c1c816d134f61a97c4f41a43b5ee 100644 --- a/setup/src/Magento/Setup/Model/License.php +++ b/setup/src/Magento/Setup/Model/License.php @@ -16,12 +16,19 @@ use Magento\Framework\Filesystem; */ class License { + /** + * Default License File location + * + * @var string + */ + const DEFAULT_LICENSE_FILENAME = 'LICENSE.txt'; + /** * License File location * * @var string */ - const LICENSE_FILENAME = 'LICENSE.txt'; + const LICENSE_FILENAME = 'LICENSE_EE.txt'; /** * Directory that contains license file @@ -43,13 +50,16 @@ class License /** * Returns contents of License file. * - * @return string + * @return string|boolean */ public function getContents() { - if (!$this->dir->isFile(self::LICENSE_FILENAME)) { + if ($this->dir->isFile(self::LICENSE_FILENAME)) { + return $this->dir->readFile(self::LICENSE_FILENAME); + } elseif ($this->dir->isFile(self::DEFAULT_LICENSE_FILENAME)) { + return $this->dir->readFile(self::DEFAULT_LICENSE_FILENAME); + } else { return false; } - return $this->dir->readFile(self::LICENSE_FILENAME); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Model/LicenseTest.php b/setup/src/Magento/Setup/Test/Unit/Model/LicenseTest.php index 1d8f9f3accce01dd668f9f8b4ed4e998a2fafe47..3bb5a5324c33e7b58bb3d11a28cacdd10f0a8735 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/LicenseTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/LicenseTest.php @@ -33,14 +33,12 @@ class LicenseTest extends \PHPUnit_Framework_TestCase public function testGetContents() { $this->directoryReadMock - ->expects($this->once()) + ->expects($this->atLeastOnce()) ->method('readFile') - ->with(License::LICENSE_FILENAME) ->will($this->returnValue('License text')); $this->directoryReadMock - ->expects($this->once()) + ->expects($this->atLeastOnce()) ->method('isFile') - ->with(License::LICENSE_FILENAME) ->will($this->returnValue(true)); $license = new License($this->filesystemMock); @@ -50,9 +48,8 @@ class LicenseTest extends \PHPUnit_Framework_TestCase public function testGetContentsNoFile() { $this->directoryReadMock - ->expects($this->once()) + ->expects($this->atLeastOnce()) ->method('isFile') - ->with(License::LICENSE_FILENAME) ->will($this->returnValue(false)); $license = new License($this->filesystemMock);