Skip to content

Commit a50d335

Browse files
committed
MC-24892: Admin: Check Category products indexing when add/remove product in category and Product categories indexing when add/remove category in product
1 parent c2e2646 commit a50d335

File tree

7 files changed

+562
-0
lines changed

7 files changed

+562
-0
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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\TestFramework\Catalog\Model;
9+
10+
use Magento\Catalog\Api\Data\CategoryInterface;
11+
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory;
12+
13+
/**
14+
* Load category by category name
15+
*/
16+
class GetCategoryByName
17+
{
18+
/** @var CollectionFactory */
19+
private $categoryCollectionFactory;
20+
21+
/**
22+
* @param CollectionFactory $categoryCollectionFactory
23+
*/
24+
public function __construct(CollectionFactory $categoryCollectionFactory)
25+
{
26+
$this->categoryCollectionFactory = $categoryCollectionFactory;
27+
}
28+
29+
/**
30+
* Load category by name.
31+
*
32+
* @param string $categoryName
33+
* @return CategoryInterface
34+
*/
35+
public function execute(string $categoryName): CategoryInterface
36+
{
37+
$categoryCollection = $this->categoryCollectionFactory->create();
38+
39+
return $categoryCollection->addAttributeToFilter(CategoryInterface::KEY_NAME, $categoryName)
40+
->setPageSize(1)
41+
->getFirstItem();
42+
}
43+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
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\Controller\Adminhtml\Product\Save;
9+
10+
use Magento\Catalog\Api\Data\ProductInterface;
11+
use Magento\Catalog\Api\ProductRepositoryInterface;
12+
use Magento\Catalog\Helper\DefaultCategory;
13+
use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
14+
use Magento\Catalog\Model\ResourceModel\Product as ProductResource;
15+
use Magento\Framework\App\Request\Http;
16+
use Magento\Framework\DB\Adapter\AdapterInterface;
17+
use Magento\Framework\Message\MessageInterface;
18+
use Magento\Store\Model\Store;
19+
use Magento\TestFramework\TestCase\AbstractBackendController;
20+
21+
/**
22+
* Checks category product index in cases when category unassigned from product
23+
*
24+
* @magentoDataFixture Magento/Catalog/_files/category_product_assigned_to_website.php
25+
* @magentoAppArea adminhtml
26+
* @magentoDbIsolation disabled
27+
*/
28+
class CategoryIndexTest extends AbstractBackendController
29+
{
30+
/** @var ProductRepositoryInterface */
31+
private $productRepository;
32+
33+
/** @var ProductInterface */
34+
private $product;
35+
36+
/** @var TableMaintainer */
37+
private $tableMaintainer;
38+
39+
/** @var ProductResource */
40+
private $productResource;
41+
42+
/** @var AdapterInterface */
43+
private $connection;
44+
45+
/** @var DefaultCategory */
46+
private $defaultCategoryHelper;
47+
/**
48+
* @inheritdoc
49+
*/
50+
protected function setUp()
51+
{
52+
parent::setUp();
53+
54+
$this->productRepository = $this->_objectManager->get(ProductRepositoryInterface::class);
55+
$this->product = $this->productRepository->get('product_with_category');
56+
$this->tableMaintainer = $this->_objectManager->create(TableMaintainer::class);
57+
$this->productResource = $this->_objectManager->get(ProductResource::class);
58+
$this->connection = $this->productResource->getConnection();
59+
$this->defaultCategoryHelper = $this->_objectManager->get(DefaultCategory::class);
60+
}
61+
62+
/**
63+
* @return void
64+
*/
65+
public function testUnassignCategory(): void
66+
{
67+
$postData = $this->preparePostData();
68+
$this->dispatchSaveProductRequest($postData);
69+
$this->assertEmpty($this->fetchIndexData());
70+
}
71+
72+
/**
73+
* @magentoDataFixture Magento/Catalog/_files/category_product_assigned_to_website.php
74+
* @magentoDataFixture Magento/Catalog/_files/category.php
75+
*
76+
* @return void
77+
*/
78+
public function testReassignCategory(): void
79+
{
80+
$postData = $this->preparePostData(333);
81+
$this->dispatchSaveProductRequest($postData);
82+
$result = $this->fetchIndexData();
83+
$this->assertNotEmpty($result);
84+
$this->assertEquals(333, reset($result)['category_id']);
85+
}
86+
87+
/**
88+
* Perform request
89+
*
90+
* @param array $postData
91+
* @return void
92+
*/
93+
private function dispatchSaveProductRequest(array $postData): void
94+
{
95+
$this->getRequest()->setPostValue($postData);
96+
$this->getRequest()->setMethod(Http::METHOD_POST);
97+
$this->dispatch('backend/catalog/product/save/id/' . $this->product->getEntityId());
98+
$this->assertSessionMessages($this->equalTo(['You saved the product.']), MessageInterface::TYPE_SUCCESS);
99+
}
100+
101+
/**
102+
* Prepare data to request
103+
*
104+
* @param int|null $newCategoryId
105+
* @return array
106+
*/
107+
private function preparePostData(?int $newCategoryId = null): array
108+
{
109+
$this->product->getWebsiteIds();
110+
$data = $this->product->getData();
111+
unset($data['entity_id'], $data['category_ids']);
112+
if ($newCategoryId) {
113+
$data['category_ids'] = [$newCategoryId];
114+
}
115+
116+
return ['product' => $data];
117+
}
118+
119+
/**
120+
* Fetch data from category product index table
121+
*
122+
* @return array
123+
*/
124+
private function fetchIndexData(): array
125+
{
126+
$tableName = $this->tableMaintainer->getMainTable(Store::DISTRO_STORE_ID);
127+
$select = $this->connection->select();
128+
$select->from(['index_table' => $tableName], 'index_table.category_id')
129+
->where('index_table.product_id = ?', $this->product->getId())
130+
->where('index_table.category_id != ?', $this->defaultCategoryHelper->getId());
131+
132+
return $this->connection->fetchAll($select);
133+
}
134+
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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\Indexer\Product;
9+
10+
use Magento\Catalog\Api\CategoryRepositoryInterface;
11+
use Magento\Catalog\Api\ProductRepositoryInterface;
12+
use Magento\Catalog\Helper\DefaultCategory;
13+
use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
14+
use Magento\Catalog\Model\ResourceModel\Category as CategoryResource;
15+
use Magento\Catalog\Model\ResourceModel\Product as ProductResource;
16+
use Magento\Framework\DB\Adapter\AdapterInterface;
17+
use Magento\Framework\ObjectManagerInterface;
18+
use Magento\Store\Model\StoreManagerInterface;
19+
use Magento\TestFramework\Catalog\Model\GetCategoryByName;
20+
use Magento\TestFramework\Helper\Bootstrap;
21+
use Magento\TestFramework\Indexer\TestCase;
22+
23+
/**
24+
* Checks category products indexing
25+
*
26+
* @magentoAppArea adminhtml
27+
* @magentoAppIsolation enabled
28+
* @magentoDbIsolation disabled
29+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
30+
*/
31+
class CategoryIndexTest extends TestCase
32+
{
33+
/** @var ObjectManagerInterface */
34+
private $objectManager;
35+
36+
/** @var ProductRepositoryInterface */
37+
private $productRepository;
38+
39+
/** @var StoreManagerInterface */
40+
private $storeManager;
41+
42+
/** @var AdapterInterface */
43+
private $connection;
44+
45+
/** @var TableMaintainer */
46+
private $tableMaintainer;
47+
48+
/** @var ProductResource */
49+
private $productResource;
50+
51+
/** @var CategoryRepositoryInterface */
52+
private $categoryRepository;
53+
54+
/** @var CategoryResource */
55+
private $categoryResource;
56+
57+
/** @var GetCategoryByName */
58+
private $getCategoryByName;
59+
60+
/** @var DefaultCategory */
61+
private $defaultCategoryHelper;
62+
63+
/**
64+
* @inheritdoc
65+
*/
66+
protected function setUp()
67+
{
68+
parent::setUp();
69+
70+
$this->objectManager = Bootstrap::getObjectManager();
71+
$this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
72+
$this->productResource = $this->objectManager->get(ProductResource::class);
73+
$this->storeManager = $this->objectManager->get(StoreManagerInterface::class);
74+
$this->connection = $this->productResource->getConnection();
75+
$this->tableMaintainer = $this->objectManager->get(TableMaintainer::class);
76+
$this->categoryRepository = $this->objectManager->get(CategoryRepositoryInterface::class);
77+
$this->categoryResource = $this->objectManager->get(CategoryResource::class);
78+
$this->getCategoryByName = $this->objectManager->create(GetCategoryByName::class);
79+
$this->defaultCategoryHelper = $this->objectManager->get(DefaultCategory::class);
80+
}
81+
82+
/**
83+
* @magentoDataFixture Magento/Catalog/_files/category_with_parent_anchor.php
84+
* @magentoDataFixture Magento/Catalog/_files/second_product_simple.php
85+
*
86+
* @dataProvider assignCategoriesDataProvider
87+
*
88+
* @param string $categoryName
89+
* @param int $expectedItemsCount
90+
* @return void
91+
*/
92+
public function testProductAssignCategory(string $categoryName, int $expectedItemsCount): void
93+
{
94+
$product = $this->productRepository->get('simple2');
95+
$category = $this->getCategoryByName->execute($categoryName);
96+
$product->setCategoryIds(array_merge($product->getCategoryIds(), [$category->getId()]));
97+
$this->productResource->save($product);
98+
$result = $this->getIndexRecordsByProductId((int)$product->getId());
99+
$this->assertEquals($expectedItemsCount, $result);
100+
}
101+
102+
/**
103+
* @return array
104+
*/
105+
public function assignCategoriesDataProvider(): array
106+
{
107+
return [
108+
'assign_to_category' => [
109+
'category_name' => 'Parent category',
110+
'expected_records_count' => 1,
111+
],
112+
'assign_to_category_with_parent_anchor_category' => [
113+
'category_name' => 'Child category',
114+
'expected_records_count' => 2,
115+
],
116+
];
117+
}
118+
119+
/**
120+
* @magentoDataFixture Magento/Catalog/_files/category_with_parent_anchor.php
121+
* @magentoDataFixture Magento/Catalog/_files/second_product_simple.php
122+
*
123+
* @dataProvider assignProductsDataProvider
124+
*
125+
* @param string $categoryName
126+
* @param int $expectedCount
127+
* @return void
128+
*/
129+
public function testCategoryAssignProduct(string $categoryName, int $expectedCount): void
130+
{
131+
$product = $this->productRepository->get('simple2');
132+
$category = $this->getCategoryByName->execute($categoryName);
133+
$data = ['posted_products' => [$product->getId() => 0]];
134+
$category->addData($data);
135+
$this->categoryResource->save($category);
136+
$result = $this->getIndexRecordsByProductId((int)$product->getId());
137+
$this->assertEquals($expectedCount, $result);
138+
}
139+
140+
/**
141+
* @return array
142+
*/
143+
public function assignProductsDataProvider(): array
144+
{
145+
return [
146+
'assign_product_to_category' => [
147+
'category_name' => 'Parent category',
148+
'expected_records_count' => 1,
149+
],
150+
'assign_product_to_category_with_parent_anchor_category' => [
151+
'category_name' => 'Child category',
152+
'expected_records_count' => 2,
153+
],
154+
];
155+
}
156+
157+
/**
158+
* @magentoDataFixture Magento/Catalog/_files/category_product_assigned_to_website.php
159+
* @magentoDataFixture Magento/Catalog/_files/category_with_parent_anchor.php
160+
*
161+
* @return void
162+
*/
163+
public function testCategoryMove(): void
164+
{
165+
$product = $this->productRepository->get('product_with_category');
166+
$category = $this->getCategoryByName->execute('Category with product');
167+
$newParentCategory = $this->getCategoryByName->execute('Parent category');
168+
$afterCategory = $this->getCategoryByName->execute('Child category');
169+
$category->move($newParentCategory->getId(), $afterCategory->getId());
170+
$result = $this->getIndexRecordsByProductId((int)$product->getId());
171+
$this->assertEquals(2, $result);
172+
}
173+
174+
/**
175+
* @magentoDataFixture Magento/Catalog/_files/category_product_assigned_to_website.php
176+
*
177+
* @return void
178+
*/
179+
public function testDeleteProduct(): void
180+
{
181+
$product = $this->productRepository->get('product_with_category');
182+
$this->productRepository->delete($product);
183+
$result = $this->getIndexRecordsByProductId((int)$product->getId());
184+
$this->assertEmpty($result);
185+
}
186+
187+
/**
188+
* Fetch data from category product index table
189+
*
190+
* @param int $productId
191+
* @return int
192+
*/
193+
private function getIndexRecordsByProductId(int $productId): int
194+
{
195+
$tableName = $this->tableMaintainer->getMainTable((int)$this->storeManager->getStore()->getId());
196+
$select = $this->connection->select();
197+
$select->from(['index_table' => $tableName], new \Zend_Db_Expr('COUNT(*)'))
198+
->where('index_table.product_id = ?', $productId)
199+
->where('index_table.category_id != ?', $this->defaultCategoryHelper->getId());
200+
201+
return (int)$this->connection->fetchOne($select);
202+
}
203+
}

0 commit comments

Comments
 (0)