Skip to content

Commit

Permalink
magento#13126: 2.2.2 - Duplicating Bundle Product Removes Bundle Opti…
Browse files Browse the repository at this point in the history
…ons From Original Product
  • Loading branch information
nmalevanec committed Jan 12, 2018
1 parent 8848d94 commit b57eb03
Show file tree
Hide file tree
Showing 3 changed files with 277 additions and 7 deletions.
11 changes: 10 additions & 1 deletion app/code/Magento/Bundle/Model/Product/CopyConstructor/Bundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,16 @@ public function build(Product $product, Product $duplicate)
$bundleOptions = $product->getExtensionAttributes()->getBundleProductOptions() ?: [];
$duplicatedBundleOptions = [];
foreach ($bundleOptions as $key => $bundleOption) {
$duplicatedBundleOptions[$key] = clone $bundleOption;
$duplicatedBundleOption = clone $bundleOption;
/**
* Set option and selection ids to 'null' in order to create new option(selection) for duplicated product,
* but not modifying existing one, which led to lost of option(selection) in original product.
*/
foreach ($duplicatedBundleOption->getProductLinks() as $productLink) {
$productLink->setSelectionId(null);
}
$duplicatedBundleOption->setOptionId(null);
$duplicatedBundleOptions[$key] = $duplicatedBundleOption;
}
$duplicate->getExtensionAttributes()->setBundleProductOptions($duplicatedBundleOptions);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
namespace Magento\Bundle\Test\Unit\Model\Product\CopyConstructor;

use Magento\Bundle\Api\Data\BundleOptionInterface;
use Magento\Bundle\Model\Link;
use Magento\Bundle\Model\Product\CopyConstructor\Bundle;
use Magento\Catalog\Api\Data\ProductExtensionInterface;
use Magento\Catalog\Model\Product;
Expand Down Expand Up @@ -45,6 +46,7 @@ public function testBuildNegative()
*/
public function testBuildPositive()
{
/** @var Product|\PHPUnit_Framework_MockObject_MockObject $product */
$product = $this->getMockBuilder(Product::class)
->disableOriginalConstructor()
->getMock();
Expand All @@ -60,18 +62,42 @@ public function testBuildPositive()
->method('getExtensionAttributes')
->willReturn($extensionAttributesProduct);

$productLink = $this->getMockBuilder(Link::class)
->setMethods(['setSelectionId'])
->disableOriginalConstructor()
->getMock();
$productLink->expects($this->exactly(2))
->method('setSelectionId')
->with($this->identicalTo(null));
$firstOption = $this->getMockBuilder(BundleOptionInterface::class)
->setMethods(['getProductLinks'])
->disableOriginalConstructor()
->getMockForAbstractClass();
$firstOption->expects($this->once())
->method('getProductLinks')
->willReturn([$productLink]);
$firstOption->expects($this->once())
->method('setOptionId')
->with($this->identicalTo(null));
$secondOption = $this->getMockBuilder(BundleOptionInterface::class)
->setMethods(['getProductLinks'])
->disableOriginalConstructor()
->getMockForAbstractClass();
$secondOption->expects($this->once())
->method('getProductLinks')
->willReturn([$productLink]);
$secondOption->expects($this->once())
->method('setOptionId')
->with($this->identicalTo(null));
$bundleOptions = [
$this->getMockBuilder(BundleOptionInterface::class)
->disableOriginalConstructor()
->getMockForAbstractClass(),
$this->getMockBuilder(BundleOptionInterface::class)
->disableOriginalConstructor()
->getMockForAbstractClass()
$firstOption,
$secondOption
];
$extensionAttributesProduct->expects($this->once())
->method('getBundleProductOptions')
->willReturn($bundleOptions);

/** @var Product|\PHPUnit_Framework_MockObject_MockObject $duplicate */
$duplicate = $this->getMockBuilder(Product::class)
->disableOriginalConstructor()
->getMock();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

namespace Magento\Bundle\Controller\Adminhtml;

use Magento\Bundle\Api\Data\OptionInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\Product\Type;
use Magento\Framework\Data\Form\FormKey;
use Magento\Framework\Message\MessageInterface;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\TestCase\AbstractBackendController;

/**
* Provide tests for product admin controllers.
* @magentoAppArea adminhtml
*/
class ProductTest extends AbstractBackendController
{
/**
* Test bundle product duplicate won't remove bundle options from original product.
*
* @magentoDataFixture Magento/Catalog/_files/products_new.php
* @return void
*/
public function testDuplicateProduct()
{
$params = $this->getRequestParamsForDuplicate();
$this->getRequest()->setParams(['type' => Type::TYPE_BUNDLE]);
$this->getRequest()->setPostValue($params);
$this->dispatch('backend/catalog/product/save');
$this->assertSessionMessages(
$this->equalTo(
[
'You saved the product.',
'You duplicated the product.',
]
),
MessageInterface::TYPE_SUCCESS
);
$this->assertOptions();
}

/**
* Get necessary request post params for creating and duplicating bundle product.
*
* @return array
*/
private function getRequestParamsForDuplicate()
{
$product = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class)->get('simple');
return [
'product' =>
[
'attribute_set_id' => '4',
'gift_message_available' => '0',
'use_config_gift_message_available' => '1',
'stock_data' =>
[
'min_qty_allowed_in_shopping_cart' =>
[
[
'record_id' => '0',
'customer_group_id' => '32000',
'min_sale_qty' => '',
],
],
'min_qty' => '0',
'max_sale_qty' => '10000',
'notify_stock_qty' => '1',
'min_sale_qty' => '1',
'qty_increments' => '1',
'use_config_manage_stock' => '1',
'manage_stock' => '1',
'use_config_min_qty' => '1',
'use_config_max_sale_qty' => '1',
'use_config_backorders' => '1',
'backorders' => '0',
'use_config_notify_stock_qty' => '1',
'use_config_enable_qty_inc' => '1',
'enable_qty_increments' => '0',
'use_config_qty_increments' => '1',
'use_config_min_sale_qty' => '1',
'is_qty_decimal' => '0',
'is_decimal_divided' => '0',
],
'status' => '1',
'affect_product_custom_options' => '1',
'name' => 'b1',
'price' => '',
'weight' => '',
'url_key' => '',
'special_price' => '',
'quantity_and_stock_status' =>
[
'qty' => '',
'is_in_stock' => '1',
],
'sku_type' => '0',
'price_type' => '0',
'weight_type' => '0',
'website_ids' =>
[
1 => '1',
],
'sku' => 'b1',
'meta_title' => 'b1',
'meta_keyword' => 'b1',
'meta_description' => 'b1 ',
'tax_class_id' => '2',
'product_has_weight' => '1',
'visibility' => '4',
'country_of_manufacture' => '',
'page_layout' => '',
'options_container' => 'container2',
'custom_design' => '',
'custom_layout' => '',
'price_view' => '0',
'shipment_type' => '0',
'news_from_date' => '',
'news_to_date' => '',
'custom_design_from' => '',
'custom_design_to' => '',
'special_from_date' => '',
'special_to_date' => '',
'description' => '',
'short_description' => '',
'custom_layout_update' => '',
'image' => '',
'small_image' => '',
'thumbnail' => '',
],
'bundle_options' =>
[
'bundle_options' =>
[
[
'record_id' => '0',
'type' => 'select',
'required' => '1',
'title' => 'test option title',
'position' => '1',
'option_id' => '',
'delete' => '',
'bundle_selections' =>
[
[
'product_id' => $product->getId(),
'name' => $product->getName(),
'sku' => $product->getSku(),
'price' => $product->getPrice(),
'delete' => '',
'selection_can_change_qty' => '',
'selection_id' => '',
'selection_price_type' => '0',
'selection_price_value' => '',
'selection_qty' => '1',
'position' => '1',
'option_id' => '',
'record_id' => '1',
'is_default' => '0',
],
],
'bundle_button_proxy' =>
[
[
'entity_id' => '1',
],
],
],
],
],
'affect_bundle_product_selections' => '1',
'back' => 'duplicate',
'form_key' => Bootstrap::getObjectManager()->get(FormKey::class)->getFormKey(),
];
}

/**
* Check options in created and duplicated products.
*
* @return void
*/
private function assertOptions()
{
$createdOptions = $this->getProductOptions('b1');
$createdOption = array_shift($createdOptions);
$duplicatedOptions = $this->getProductOptions('b1-1');
$duplicatedOption = array_shift($duplicatedOptions);
$this->assertNotEmpty($createdOption);
$this->assertNotEmpty($duplicatedOption);
$optionFields = ['type', 'title', 'position', 'required', 'default_title'];
foreach ($optionFields as $field) {
$this->assertSame($createdOption->getData($field), $duplicatedOption->getData($field));
}
$createdLinks = $createdOption->getProductLinks();
$createdLink = array_shift($createdLinks);
$duplicatedLinks = $duplicatedOption->getProductLinks();
$duplicatedLink = array_shift($duplicatedLinks);
$this->assertNotEmpty($createdLink);
$this->assertNotEmpty($duplicatedLink);
$linkFields = [
'entity_id',
'sku',
'position',
'is_default',
'price',
'qty',
'selection_can_change_quantity',
'price_type',
];
foreach ($linkFields as $field) {
$this->assertSame($createdLink->getData($field), $duplicatedLink->getData($field));
}
}

/**
* Get options for given product.
*
* @param string $sku
* @return OptionInterface[]
*/
private function getProductOptions(string $sku)
{
$product = Bootstrap::getObjectManager()->create(Product::class);
$productId = $product->getResource()->getIdBySku($sku);
$product->load($productId);

return $product->getExtensionAttributes()->getBundleProductOptions();
}
}

0 comments on commit b57eb03

Please sign in to comment.