Skip to content

Commit

Permalink
MC-41422: [Magento Cloud] Using the API Async endpoint unset the spec…
Browse files Browse the repository at this point in the history
…ial price.

- Fix product attribute store view data is reset to default if the attribute is missing in the API payload
  • Loading branch information
bubasuma committed Mar 24, 2021
1 parent 1b82d65 commit 306f515
Show file tree
Hide file tree
Showing 2 changed files with 205 additions and 1 deletion.
18 changes: 17 additions & 1 deletion app/code/Magento/Catalog/Model/ProductRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Magento\Catalog\Api\CategoryLinkManagementInterface;
use Magento\Catalog\Api\Data\ProductExtension;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\Attribute\ScopeOverriddenValue;
use Magento\Catalog\Model\Product\Gallery\MimeTypeExtensionMap;
use Magento\Catalog\Model\ProductRepository\MediaGalleryProcessor;
use Magento\Catalog\Model\ResourceModel\Product\Collection;
Expand Down Expand Up @@ -181,6 +182,11 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa
*/
private $linkManagement;

/**
* @var ScopeOverriddenValue
*/
private $scopeOverriddenValue;

/**
* ProductRepository constructor.
* @param ProductFactory $productFactory
Expand Down Expand Up @@ -208,6 +214,7 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa
* @param int $cacheLimit [optional]
* @param ReadExtensions $readExtensions
* @param CategoryLinkManagementInterface $linkManagement
* @param ScopeOverriddenValue|null $scopeOverriddenValue
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
Expand Down Expand Up @@ -236,7 +243,8 @@ public function __construct(
\Magento\Framework\Serialize\Serializer\Json $serializer = null,
$cacheLimit = 1000,
ReadExtensions $readExtensions = null,
CategoryLinkManagementInterface $linkManagement = null
CategoryLinkManagementInterface $linkManagement = null,
?ScopeOverriddenValue $scopeOverriddenValue = null
) {
$this->productFactory = $productFactory;
$this->collectionFactory = $collectionFactory;
Expand All @@ -263,6 +271,8 @@ public function __construct(
->get(ReadExtensions::class);
$this->linkManagement = $linkManagement ?: \Magento\Framework\App\ObjectManager::getInstance()
->get(CategoryLinkManagementInterface::class);
$this->scopeOverriddenValue = $scopeOverriddenValue ?: \Magento\Framework\App\ObjectManager::getInstance()
->get(ScopeOverriddenValue::class);
}

/**
Expand Down Expand Up @@ -599,6 +609,12 @@ public function save(ProductInterface $product, $saveOptions = false)
&& !$attribute->isStatic()
&& !array_key_exists($attributeCode, $productDataToChange)
&& $value !== null
&& !$this->scopeOverriddenValue->containsValue(
ProductInterface::class,
$product,
$attributeCode,
$product->getStoreId()
)
) {
$product->setData($attributeCode);
$hasDataChanged = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace Magento\Catalog\Api;

use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;
use Magento\Store\Model\Store;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\TestCase\WebapiAbstract;
Expand Down Expand Up @@ -193,4 +194,191 @@ public function testProductDefaultValuesWithTwoWebsites(): void
$storeId
));
}

/**
* @magentoApiDataFixture Magento/Store/_files/second_website_with_two_stores.php
* @magentoApiDataFixture Magento/Catalog/_files/product_text_attribute.php
* @magentoApiDataFixture Magento/Catalog/_files/product_varchar_attribute.php
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function testPartialUpdate(): void
{
$this->_markTestAsRestOnly(
'Test skipped due to known issue with SOAP. NULL value is cast to corresponding attribute type.'
);
$sku = 'api_test_update_product';
$store = $this->objectManager->get(Store::class);
$storeId = (int) $store->load('fixture_third_store', 'code')->getId();
$this->updateAttribute('varchar_attribute', ['is_global' => ScopedAttributeInterface::SCOPE_STORE]);
$this->updateAttribute('text_attribute', ['is_global' => ScopedAttributeInterface::SCOPE_STORE]);
$request1 = [
ProductInterface::SKU => $sku,
ProductInterface::NAME => 'Test 1',
ProductInterface::ATTRIBUTE_SET_ID => 4,
ProductInterface::PRICE => 10,
ProductInterface::STATUS => 1,
ProductInterface::VISIBILITY => 4,
ProductInterface::TYPE_ID => 'simple',
ProductInterface::WEIGHT => 100,
ProductInterface::CUSTOM_ATTRIBUTES => [
[
'attribute_code' => 'varchar_attribute',
'value' => 'api_test_value_varchar',
],
[
'attribute_code' => 'text_attribute',
'value' => 'api_test_value_text',
]
],
];
$response = $this->saveProduct($request1, 'all');
$this->assertResponse($request1, $response);
$this->fixtureProducts[] = $sku;
/** @var ProductRepositoryInterface $productRepository */
$productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
$product = $productRepository->get($sku);
$request2 = [
ProductInterface::SKU => $sku,
ProductInterface::CUSTOM_ATTRIBUTES => [
[
'attribute_code' => 'varchar_attribute',
'value' => 'api_test_value_varchar_changed',
]
],
];
$response2 = $this->saveProduct($request2, 'fixture_third_store');
$expected = [
'varchar_attribute' => 'api_test_value_varchar_changed',
'text_attribute' => 'api_test_value_text'
];
$this->assertResponse(
array_merge($request1, $expected),
$response2
);
$this->assertOverriddenValues(
[
'visibility' => false,
'tax_class_id' => false,
'status' => false,
'name' => false,
'varchar_attribute' => true,
'text_attribute' => false,
],
$product,
$storeId
);
$request3 = [
ProductInterface::SKU => $sku,
ProductInterface::CUSTOM_ATTRIBUTES => [
[
'attribute_code' => 'text_attribute',
'value' => 'api_test_value_text_changed',
]
],
];
$response3 = $this->saveProduct($request3, 'fixture_third_store');
$expected = [
'varchar_attribute' => 'api_test_value_varchar_changed',
'text_attribute' => 'api_test_value_text_changed'
];
$this->assertResponse(
array_merge($request1, $expected),
$response3
);
$this->assertOverriddenValues(
[
'visibility' => false,
'tax_class_id' => false,
'status' => false,
'name' => false,
'varchar_attribute' => true,
'text_attribute' => true,
],
$product,
$storeId
);
$request4 = [
ProductInterface::SKU => $sku,
ProductInterface::CUSTOM_ATTRIBUTES => [
[
'attribute_code' => 'text_attribute',
'value' => null,
]
],
];
$response4 = $this->saveProduct($request4, 'fixture_third_store');
$expected = [
'varchar_attribute' => 'api_test_value_varchar_changed',
'text_attribute' => 'api_test_value_text'
];
$this->assertResponse(
array_merge($request1, $expected),
$response4
);
$this->assertOverriddenValues(
[
'visibility' => false,
'tax_class_id' => false,
'status' => false,
'name' => false,
'varchar_attribute' => true,
'text_attribute' => false,
],
$product,
$storeId
);
}

/**
* @param array $expected
* @param array $actual
*/
private function assertResponse(array $expected, array $actual): void
{
$customAttributes = $expected[ProductInterface::CUSTOM_ATTRIBUTES] ?? [];
unset($expected[ProductInterface::CUSTOM_ATTRIBUTES]);
$expected = array_merge(array_column($customAttributes, 'value', 'attribute_code'), $expected);

$customAttributes = $actual[ProductInterface::CUSTOM_ATTRIBUTES] ?? [];
unset($actual[ProductInterface::CUSTOM_ATTRIBUTES]);
$actual = array_merge(array_column($customAttributes, 'value', 'attribute_code'), $actual);

$this->assertEquals($expected, array_intersect_key($actual, $expected));
}

/**
* @param Product $product
* @param array $expected
* @param int $storeId
*/
private function assertOverriddenValues(array $expected, Product $product, int $storeId): void
{
/** @var ScopeOverriddenValue $scopeOverriddenValue */
$scopeOverriddenValue = $this->objectManager->create(ScopeOverriddenValue::class);
$actual = [];
foreach (array_keys($expected) as $attribute) {
$actual[$attribute] = $scopeOverriddenValue->containsValue(
ProductInterface::class,
$product,
$attribute,
$storeId
);
}
$this->assertEquals($expected, $actual);
}

/**
* Update attribute
*
* @param string $code
* @param array $data
* @return void
*/
private function updateAttribute(string $code, array $data): void
{
$attributeRepository = $this->objectManager->create(ProductAttributeRepositoryInterface::class);
$attribute = $attributeRepository->get($code);
$attribute->addData($data);
$attributeRepository->save($attribute);
}
}

0 comments on commit 306f515

Please sign in to comment.