Skip to content

Commit b143e7b

Browse files
committed
MC-24894: Admin: Check Product categories indexing when add/remove category in product using category link management
1 parent 0ac3443 commit b143e7b

File tree

4 files changed

+298
-1
lines changed

4 files changed

+298
-1
lines changed
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Model;
9+
10+
use Magento\Catalog\Api\CategoryLinkManagementInterface;
11+
use Magento\Catalog\Api\ProductRepositoryInterface;
12+
use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
13+
use Magento\Catalog\Model\ResourceModel\Category as CategoryResourceModel;
14+
use Magento\Store\Api\StoreRepositoryInterface;
15+
use Magento\TestFramework\Helper\Bootstrap;
16+
use Magento\TestFramework\ObjectManager;
17+
use Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollectionFactory;
18+
use PHPUnit\Framework\TestCase;
19+
20+
/**
21+
* Test cases related to assign/unassign product to/from category.
22+
*/
23+
class CategoryLinkManagementTest extends TestCase
24+
{
25+
/**
26+
* @var ObjectManager
27+
*/
28+
private $objectManager;
29+
30+
/**
31+
* @var TableMaintainer
32+
*/
33+
private $tableMaintainer;
34+
35+
/**
36+
* @var StoreRepositoryInterface
37+
*/
38+
private $storeRepository;
39+
40+
/**
41+
* @var ProductRepositoryInterface
42+
*/
43+
private $productRepository;
44+
45+
/**
46+
* @var CategoryResourceModel
47+
*/
48+
private $categoryResourceModel;
49+
50+
/**
51+
* @var CategoryLinkManagementInterface
52+
*/
53+
private $categoryLinkManagement;
54+
55+
/**
56+
* @inheritdoc
57+
*/
58+
protected function setUp()
59+
{
60+
$this->objectManager = Bootstrap::getObjectManager();
61+
$this->tableMaintainer = $this->objectManager->get(TableMaintainer::class);
62+
$this->storeRepository = $this->objectManager->get(StoreRepositoryInterface::class);
63+
$this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
64+
$this->categoryResourceModel = $this->objectManager->get(CategoryResourceModel::class);
65+
$this->categoryLinkManagement = $this->objectManager->create(CategoryLinkManagementInterface::class);
66+
$this->productRepository->cleanCache();
67+
parent::setUp();
68+
}
69+
70+
/**
71+
* @inheritdoc
72+
*/
73+
protected function tearDown()
74+
{
75+
$this->objectManager->removeSharedInstance(CategoryLinkRepository::class);
76+
$this->objectManager->removeSharedInstance(CategoryRepository::class);
77+
parent::tearDown();
78+
}
79+
80+
/**
81+
* Assert that product correctly assigned to category and index table contain indexed data.
82+
*
83+
* @magentoDataFixture Magento/Catalog/_files/second_product_simple.php
84+
* @magentoDataFixture Magento/Catalog/_files/category.php
85+
*
86+
* @magentoDbIsolation disabled
87+
*
88+
* @return void
89+
*/
90+
public function testAssignProductToCategory(): void
91+
{
92+
$product = $this->productRepository->get('simple2');
93+
$this->assertEquals(0, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [333]));
94+
$this->assertEquals(0, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [333]));
95+
$this->categoryLinkManagement->assignProductToCategories('simple2', [333]);
96+
$this->assertEquals(1, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [333]));
97+
$this->assertEquals(1, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [333]));
98+
}
99+
100+
/**
101+
* Assert that product correctly unassigned from category and index table not contain indexed data.
102+
*
103+
* @magentoDataFixture Magento/Catalog/_files/product_with_category.php
104+
*
105+
* @magentoDbIsolation disabled
106+
*
107+
* @return void
108+
*/
109+
public function testUnassignProductFromCategory(): void
110+
{
111+
$product = $this->productRepository->get('in-stock-product');
112+
$this->assertEquals(1, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [333]));
113+
$this->assertEquals(1, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [333]));
114+
$this->categoryLinkManagement->assignProductToCategories('in-stock-product', []);
115+
$this->assertEquals(0, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [333]));
116+
$this->assertEquals(0, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [333]));
117+
}
118+
119+
/**
120+
* Assert that product correctly assigned to category and index table contain index data.
121+
*
122+
* @magentoDataFixture Magento/Catalog/_files/second_product_simple.php
123+
* @magentoDataFixture Magento/Catalog/_files/categories_no_products.php
124+
*
125+
* @magentoDbIsolation disabled
126+
*
127+
* @return void
128+
*/
129+
public function testAssignProductToCategoryWhichHasParentCategories(): void
130+
{
131+
$product = $this->productRepository->get('simple2');
132+
$this->assertEquals(0, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [5]));
133+
$this->assertEquals(0, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [3, 4, 5]));
134+
$this->categoryLinkManagement->assignProductToCategories('simple2', [5]);
135+
$this->assertEquals(1, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [5]));
136+
$this->assertEquals(3, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [3, 4, 5]));
137+
}
138+
139+
/**
140+
* Assert that product correctly unassigned from category and index table doesn't contain index data.
141+
*
142+
* @magentoDataFixture Magento/Catalog/_files/product_simple_with_category_which_has_parent_category.php
143+
*
144+
* @magentoDbIsolation disabled
145+
*
146+
* @return void
147+
*/
148+
public function testUnassignProductFromCategoryWhichHasParentCategories(): void
149+
{
150+
$product = $this->productRepository->get('simple_with_child_category');
151+
$this->assertEquals(1, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [5]));
152+
$this->assertEquals(3, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [3, 4, 5]));
153+
$this->categoryLinkManagement->assignProductToCategories('simple_with_child_category', []);
154+
$this->assertEquals(0, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [5]));
155+
$this->assertEquals(0, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [3, 4, 5]));
156+
}
157+
158+
/**
159+
* Assert that product correctly reassigned to another category.
160+
*
161+
* @magentoDataFixture Magento/Catalog/_files/product_simple_with_category_which_has_parent_category.php
162+
*
163+
* @magentoDbIsolation disabled
164+
*
165+
* @return void
166+
*/
167+
public function testReassignProductToOtherCategory(): void
168+
{
169+
$product = $this->productRepository->get('simple_with_child_category');
170+
$this->assertEquals(1, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [5]));
171+
$this->assertEquals(3, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [3, 4, 5]));
172+
$this->categoryLinkManagement->assignProductToCategories('simple_with_child_category', [6]);
173+
$this->assertEquals(1, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [6]));
174+
$this->assertEquals(1, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [6]));
175+
$this->assertEquals(0, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [5]));
176+
$this->assertEquals(0, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [3, 4, 5]));
177+
}
178+
179+
/**
180+
* Return count of product which assigned to provided categories.
181+
*
182+
* @param int $productId
183+
* @param int[] $categoryIds
184+
* @return int
185+
*/
186+
private function getCategoryProductRelationRecordsCount(int $productId, array $categoryIds): int
187+
{
188+
$select = $this->categoryResourceModel->getConnection()->select();
189+
$select->from(
190+
$this->categoryResourceModel->getCategoryProductTable(),
191+
[
192+
'row_count' => new \Zend_Db_Expr('COUNT(*)')
193+
]
194+
);
195+
$select->where('product_id = ?', $productId);
196+
$select->where('category_id IN (?)', $categoryIds);
197+
198+
return (int)$this->categoryResourceModel->getConnection()->fetchOne($select);
199+
}
200+
201+
/**
202+
* Return count of products which added to index table with all provided category ids.
203+
*
204+
* @param int $productId
205+
* @param array $categoryIds
206+
* @param string $storeCode
207+
* @return int
208+
*/
209+
private function getCategoryProductIndexRecordsCount(
210+
int $productId,
211+
array $categoryIds,
212+
string $storeCode = 'default'
213+
): int {
214+
$storeId = (int)$this->storeRepository->get($storeCode)->getId();
215+
$select = $this->categoryResourceModel->getConnection()->select();
216+
$select->from(
217+
$this->tableMaintainer->getMainTable($storeId),
218+
[
219+
'row_count' => new \Zend_Db_Expr('COUNT(*)')
220+
]
221+
);
222+
$select->where('product_id = ?', $productId);
223+
$select->where('category_id IN (?)', $categoryIds);
224+
225+
return (int)$this->categoryResourceModel->getConnection()->fetchOne($select);
226+
}
227+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
use Magento\Catalog\Api\Data\ProductInterfaceFactory;
9+
use Magento\Catalog\Api\ProductRepositoryInterface;
10+
use Magento\Catalog\Model\Product\Attribute\Source\Status;
11+
use Magento\Catalog\Model\Product\Visibility;
12+
use Magento\Store\Api\WebsiteRepositoryInterface;
13+
use Magento\TestFramework\Helper\Bootstrap;
14+
15+
require __DIR__ . '/categories_no_products.php';
16+
17+
/** @var ProductRepositoryInterface $productRepository */
18+
$productRepository = $objectManager->get(ProductRepositoryInterface::class);
19+
/** @var WebsiteRepositoryInterface $websiteRepository */
20+
$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class);
21+
/** @var ProductInterfaceFactory $productFactory */
22+
$productFactory = $objectManager->get(ProductInterfaceFactory::class);
23+
$defaultWebsiteId = $websiteRepository->get('base')->getId();
24+
$product = $productFactory->create();
25+
$product->setData(
26+
[
27+
'attribute_set_id' => $product->getDefaultAttributeSetId(),
28+
'website_ids' => [
29+
$defaultWebsiteId
30+
],
31+
'name' => 'Simple product with child category',
32+
'sku' => 'simple_with_child_category',
33+
'price' => 10,
34+
'description' => 'Description product with category which has parent category',
35+
'visibility' => Visibility::VISIBILITY_BOTH,
36+
'status' => Status::STATUS_ENABLED,
37+
'category_ids' => [5],
38+
'stock_data' => [
39+
'use_config_manage_stock' => 1,
40+
'qty' => 100,
41+
'is_qty_decimal' => 0,
42+
'is_in_stock' => 1
43+
],
44+
'url_key' => 'simple-with-child-category'
45+
]
46+
);
47+
$productRepository->save($product);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
use Magento\Catalog\Api\ProductRepositoryInterface;
9+
use Magento\Framework\Exception\NoSuchEntityException;
10+
11+
require __DIR__ . '/categories_no_products_rollback.php';
12+
13+
/** @var ProductRepositoryInterface $productRepository */
14+
$productRepository = $objectManager->get(ProductRepositoryInterface::class);
15+
$registry->unregister('isSecureArea');
16+
$registry->register('isSecureArea', true);
17+
try {
18+
$productRepository->deleteById('simple_with_child_category');
19+
} catch (NoSuchEntityException $exception) {
20+
//Product already deleted.
21+
}
22+
$registry->unregister('isSecureArea');
23+
$registry->register('isSecureArea', false);

dev/tests/integration/testsuite/Magento/Catalog/_files/product_with_category.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
->setSku('in-stock-product')
3131
->setPrice(10)
3232
->setWeight(1)
33-
->setShortDescription("Short description")
33+
->setShortDescription('Short description')
3434
->setTaxClassId(0)
3535
->setDescription('Description with <b>html tag</b>')
3636
->setMetaTitle('meta title')

0 commit comments

Comments
 (0)