Skip to content

Commit ea95e7d

Browse files
author
Yu Tang
committed
MAGETWO-28254: ConfigurableProduct Integration API
- Verify that no two products has the same set of attribute values
1 parent aa8fc6d commit ea95e7d

File tree

6 files changed

+137
-17
lines changed

6 files changed

+137
-17
lines changed

app/code/Magento/ConfigurableProduct/Model/Plugin/AroundProductRepositorySave.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ public function aroundSave(
8484
}
8585
if ($configurableProductOptions !== null) {
8686
$this->saveConfigurableProductOptions($result, $configurableProductOptions);
87+
$result->getTypeInstance()->resetConfigurableAttributes($result);
8788
}
8889
if ($configurableProductLinks !== null) {
8990
$this->saveConfigurableProductLinks($result, $configurableProductLinks);
@@ -163,18 +164,27 @@ protected function saveConfigurableProductLinks(
163164
*/
164165
protected function validateProductLinks(array $attributeCodes, array $linkIds)
165166
{
167+
$valueMap = [];
166168
foreach ($linkIds as $productId) {
167169
$variation = $this->productFactory->create()->load($productId);
168170
if (!$variation->getId()) {
169171
throw new InputException(__('Product with id "%1" does not exist.', $productId));
170172
}
173+
$valueKey = '';
171174
foreach ($attributeCodes as $attributeCode) {
172175
if (!$variation->getData($attributeCode)) {
173176
throw new InputException(
174177
__('Product with id "%1" does not contain required attribute "%2".', $productId, $attributeCode)
175178
);
176179
}
180+
$valueKey = $valueKey . $attributeCode . ':' . $variation->getData($attributeCode) . ';';
177181
}
182+
if (isset($valueMap[$valueKey])) {
183+
throw new InputException(
184+
__('Products "%1" and %2 have the same set of attribute values.', $productId, $valueMap[$valueKey])
185+
);
186+
}
187+
$valueMap[$valueKey] = $productId;
178188
}
179189
return $this;
180190
}

app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,18 @@ public function getConfigurableAttributes($product)
377377
return $product->getData($this->_configurableAttributes);
378378
}
379379

380+
/**
381+
* Reset the cached configurable attributes of a product
382+
*
383+
* @param \Magento\Catalog\Model\Product $product
384+
* @return $this
385+
*/
386+
public function resetConfigurableAttributes($product)
387+
{
388+
$product->unsetData($this->_configurableAttributes);
389+
return $this;
390+
}
391+
380392
/**
381393
* Retrieve Configurable Attributes as array
382394
*

app/code/Magento/ConfigurableProduct/Test/Unit/Model/Plugin/AroundProductRepositorySaveTest.php

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,10 @@ protected function setupProducts($productIds, $attributeCode, $additionalProduct
161161
$productMock->expects($this->once())
162162
->method('getId')
163163
->willReturn($productId);
164-
$productMock->expects($this->once())
164+
$productMock->expects($this->any())
165165
->method('getData')
166166
->with($attributeCode)
167-
->willReturn('value');
167+
->willReturn($productId);
168168
$this->productFactoryMock->expects($this->at($count))
169169
->method('create')
170170
->willReturn($productMock);
@@ -333,7 +333,7 @@ public function testAroundSaveWithLinksWithMissingAttribute()
333333
$simpleProductMock->expects($this->once())
334334
->method('getId')
335335
->willReturn($simpleProductId);
336-
$simpleProductMock->expects($this->once())
336+
$simpleProductMock->expects($this->any())
337337
->method('getData')
338338
->with($configurableAttributeCode)
339339
->willReturn(null);
@@ -365,6 +365,54 @@ public function testAroundSaveWithLinksWithMissingAttribute()
365365
$this->plugin->aroundSave($this->productRepositoryMock, $this->closureMock, $this->productMock);
366366
}
367367

368+
/**
369+
* @expectedException \Magento\Framework\Exception\InputException
370+
* @expectedExceptionMessage Products "6" and 4 have the same set of attribute values.
371+
*/
372+
public function testAroundSaveWithLinksWithDuplicateAttributes()
373+
{
374+
$links = [4, 5];
375+
$simpleProductId = 6;
376+
$configurableAttributeCode = 'color';
377+
378+
$this->setupConfigurableProductAttributes([$configurableAttributeCode]);
379+
$productMocks = $this->setupProducts($links, $configurableAttributeCode, $simpleProductId);
380+
$simpleProductMock = $productMocks[2];
381+
$simpleProductMock->expects($this->once())
382+
->method('getId')
383+
->willReturn($simpleProductId);
384+
$simpleProductMock->expects($this->any())
385+
->method('getData')
386+
->with($configurableAttributeCode)
387+
->willReturn(4);
388+
389+
$links[] = $simpleProductId;
390+
391+
$this->productMock->expects($this->once())->method('getTypeId')
392+
->willReturn(\Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE);
393+
$this->productMock->expects($this->once())
394+
->method('getExtensionAttributes')
395+
->willReturn($this->productExtensionMock);
396+
$this->productExtensionMock->expects($this->once())
397+
->method('getConfigurableProductOptions')
398+
->willReturn(null);
399+
$this->productExtensionMock->expects($this->once())
400+
->method('getConfigurableProductLinks')
401+
->willReturn($links);
402+
403+
$configurableTypeMock = $this->getMockBuilder(
404+
'\Magento\ConfigurableProduct\Model\Resource\Product\Type\Configurable'
405+
)->disableOriginalConstructor()->getMock();
406+
$this->configurableTypeFactoryMock->expects($this->once())
407+
->method('create')
408+
->willReturn($configurableTypeMock);
409+
$configurableTypeMock->expects($this->never())
410+
->method('saveProducts')
411+
->with($this->productMock, $links);
412+
413+
$this->plugin->aroundSave($this->productRepositoryMock, $this->closureMock, $this->productMock);
414+
}
415+
368416
public function testAroundSaveWithOptions()
369417
{
370418
$productSku = "configurable_sku";

app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,19 @@ public function getConfigurableAttributesAsArrayDataProvider()
408408
];
409409
}
410410

411+
public function testResetConfigurableAttributes()
412+
{
413+
$product = $this->getMockBuilder('\Magento\Catalog\Model\Product')
414+
->setMethods(['unsetData', '__wakeup', '__sleep'])
415+
->disableOriginalConstructor()
416+
->getMock();
417+
$product->expects($this->any())->method('unsetData')
418+
->with('_cache_instance_configurable_attributes')
419+
->will($this->returnSelf());
420+
421+
$this->assertEquals($this->_model, $this->_model->resetConfigurableAttributes($product));
422+
}
423+
411424
public function testHasOptions()
412425
{
413426
$productMock = $this->getMockBuilder('\Magento\Catalog\Model\Product')

dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/ProductRepositoryTest.php

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ public function tearDown()
5454
parent::tearDown();
5555
}
5656

57+
protected function getConfigurableAttributeOptions()
58+
{
59+
/** @var \Magento\Eav\Model\Resource\Entity\Attribute\Option\Collection $optionCollection */
60+
$optionCollection = $this->objectManager->create(
61+
'Magento\Eav\Model\Resource\Entity\Attribute\Option\Collection'
62+
);
63+
$options = $optionCollection->setAttributeFilter($this->configurableAttribute->getId())->getData();
64+
return $options;
65+
}
66+
5767
protected function createConfigurableProduct()
5868
{
5969
$productId1 = 10;
@@ -63,11 +73,8 @@ protected function createConfigurableProduct()
6373

6474
$this->configurableAttribute = $this->eavConfig->getAttribute('catalog_product', 'test_configurable');
6575
$this->assertNotNull($this->configurableAttribute);
66-
/** @var \Magento\Eav\Model\Resource\Entity\Attribute\Option\Collection $optionCollection */
67-
$optionCollection = $this->objectManager->create(
68-
'Magento\Eav\Model\Resource\Entity\Attribute\Option\Collection'
69-
);
70-
$options = $optionCollection->setAttributeFilter($this->configurableAttribute->getId())->getData();
76+
77+
$options = $this->getConfigurableAttributeOptions();
7178
$this->assertEquals(2, count($options));
7279

7380
$configurableProductOptions = [
@@ -288,6 +295,37 @@ public function testUpdateConfigurableProductLinksWithNonExistingProduct()
288295
$this->saveProduct($response);
289296
}
290297

298+
/**
299+
* @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
300+
* @expectedException \Exception
301+
* @expectedExceptionMessage Products "%1" and %2 have the same set of attribute values.
302+
*/
303+
public function testUpdateConfigurableProductLinksWithDuplicateAttributes()
304+
{
305+
$productId1 = 10;
306+
$productId2 = 20;
307+
308+
$response = $this->createConfigurableProduct();
309+
$options = $response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]['configurable_product_options'];
310+
//make product2 and product1 have the same value for the configurable attribute
311+
$optionValue1 = $options[0]['values'][0]['value_index'];
312+
$product2 = $this->getProduct('simple_' . $productId2);
313+
$product2['custom_attributes'] = [
314+
[
315+
'attribute_code' => 'test_configurable',
316+
'value' => $optionValue1,
317+
]
318+
];
319+
$this->saveProduct($product2);
320+
321+
//leave existing option untouched
322+
unset($response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]['configurable_product_options']);
323+
$response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]['configurable_product_links'] = [
324+
$productId1, $productId2
325+
];
326+
$this->saveProduct($response);
327+
}
328+
291329
/**
292330
* Get product
293331
*

dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable.php

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,16 @@
1212
['resourceName' => 'catalog_setup']
1313
);
1414

15-
/* Create simple products per each option */
16-
/** @var $options \Magento\Eav\Model\Resource\Entity\Attribute\Option\Collection */
17-
$options = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
18-
'Magento\Eav\Model\Resource\Entity\Attribute\Option\Collection'
19-
);
20-
$options->setAttributeFilter($attribute->getId());
15+
/* Create simple products per each option value*/
16+
17+
/** @var \Magento\Eav\Api\Data\AttributeOptionInterface[] $options */
18+
$options = $attribute->getOptions();
2119

2220
$attributeValues = [];
2321
$productIds = [];
2422
$attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default');
2523
$productIds = [10, 20];
24+
array_shift($options); //remove the first option which is empty
2625
foreach ($options as $option) {
2726
/** @var $product \Magento\Catalog\Model\Product */
2827
$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Catalog\Model\Product');
@@ -36,13 +35,13 @@
3635
)->setWebsiteIds(
3736
[1]
3837
)->setName(
39-
'Configurable Option' . $option->getId()
38+
'Configurable Option' . $option->getLabel()
4039
)->setSku(
4140
'simple_' . $productId
4241
)->setPrice(
4342
10
4443
)->setTestConfigurable(
45-
$option->getId()
44+
$option->getValue()
4645
)->setVisibility(
4746
\Magento\Catalog\Model\Product\Visibility::VISIBILITY_NOT_VISIBLE
4847
)->setStatus(
@@ -54,7 +53,7 @@
5453
$attributeValues[] = [
5554
'label' => 'test',
5655
'attribute_id' => $attribute->getId(),
57-
'value_index' => $option->getId(),
56+
'value_index' => $option->getValue(),
5857
'is_percent' => false,
5958
'pricing_value' => 5,
6059
];

0 commit comments

Comments
 (0)