Skip to content

Commit fa83cae

Browse files
author
Dmytro Voskoboinikov
committed
MAGETWO-82060: Incorrect products displayed after changing categories and running partial reindex
1 parent 72cf401 commit fa83cae

File tree

4 files changed

+292
-1
lines changed

4 files changed

+292
-1
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Catalog\Model\Indexer\Product;
7+
8+
use Magento\Catalog\Model\Category;
9+
use Magento\Catalog\Model\Product;
10+
use Magento\TestFramework\Helper\Bootstrap;
11+
use Magento\Catalog\Model\ResourceModel\Product as ProductResource;
12+
use Magento\Indexer\Model\Indexer;
13+
use PHPUnit\Framework\TestCase;
14+
15+
/**
16+
* @magentoDataFixture Magento/Catalog/_files/indexer_catalog_product_categories.php
17+
* @magentoDataFixture Magento/Catalog/_files/indexer_catalog_products.php
18+
* @magentoDbIsolation enabled
19+
* @magentoAppIsolation enabled
20+
*/
21+
class CategoryTest extends TestCase
22+
{
23+
const DEFAULT_ROOT_CATEGORY = 2;
24+
25+
/**
26+
* @var Indexer
27+
*/
28+
private $indexer;
29+
30+
/**
31+
* @var ProductResource
32+
*/
33+
private $productResource;
34+
35+
protected function setUp()
36+
{
37+
/** @var Indexer indexer */
38+
$this->indexer = Bootstrap::getObjectManager()->create(
39+
Indexer::class
40+
);
41+
$this->indexer->load('catalog_product_category');
42+
/** @var ProductResource $productResource */
43+
$this->productResource = Bootstrap::getObjectManager()->get(
44+
ProductResource::class
45+
);
46+
}
47+
48+
/**
49+
* Check that given product is only visible in given categories.
50+
*
51+
* @param Product $product
52+
* @param Category[] $categoriesIn Categories the product is supposed
53+
* to be in.
54+
* @param Category[] $categories Whole list of categories.
55+
*
56+
* @return void
57+
*/
58+
private function assertProductIn(
59+
Product $product,
60+
array $categoriesIn,
61+
array $categories
62+
) {
63+
foreach ($categories as $category) {
64+
$visible = in_array($category, $categoriesIn, true);
65+
$this->assertEquals(
66+
$visible,
67+
(bool)$this->productResource->canBeShowInCategory(
68+
$product,
69+
$category->getId()
70+
),
71+
'Product "' .$product->getName() .'" is'
72+
.($visible? '' : ' not') .' supposed to be in category "'
73+
.$category->getName() .'"'
74+
);
75+
}
76+
}
77+
78+
/**
79+
* @magentoAppArea adminhtml
80+
*/
81+
public function testReindexAll()
82+
{
83+
//Category #1 is base category for this case, Category #2 is non-anchor
84+
//sub-category of Category #1, Category #3 is anchor sub-category of
85+
//Category #2, Category #4 is anchor sub-category of Category #1
86+
//Products are not yet assigned to categories
87+
$categories = $this->loadCategories(4);
88+
$products = $this->loadProducts(3);
89+
90+
//Leaving Product #1 unassigned, Product #2 is assigned to Category #3,
91+
//Product #3 assigned to Category #3 and #4.
92+
$products[0]->setCategoryIds(null);
93+
$this->productResource->save($products[0]);
94+
$products[1]->setCategoryIds([$categories[2]->getId()]);
95+
$this->productResource->save($products[1]);
96+
$products[2]->setCategoryIds([
97+
$categories[2]->getId(),
98+
$categories[3]->getId(),
99+
]);
100+
$this->productResource->save($products[2]);
101+
//Reindexing
102+
$this->clearIndex();
103+
$this->indexer->reindexAll();
104+
105+
//Checking that Category #1 shows only Product #2 and #3 since
106+
//Product #1 is not assigned to any category, Product #2 is assigned to
107+
//it's sub-subcategory and Product #3 is assigned to a sub-subcategory
108+
//and a subcategory.
109+
//Category #2 doesn't have any products on display because while it's
110+
//sub-category has products it's a non-anchor category.
111+
//Category #3 has 2 products directly assigned to it.
112+
//Category #4 only has 1 product directly assigned to it.
113+
$this->assertProductIn($products[0], [], $categories);
114+
$this->assertProductIn(
115+
$products[1],
116+
[$categories[0],$categories[2]],
117+
$categories
118+
);
119+
$this->assertProductIn(
120+
$products[2],
121+
[$categories[0], $categories[2], $categories[3]],
122+
$categories
123+
);
124+
125+
//Reassigning products a bit
126+
$products[0]->setCategoryIds([$categories[0]->getId()]);
127+
$this->productResource->save($products[0]);
128+
$products[1]->setCategoryIds([]);
129+
$this->productResource->save($products[1]);
130+
$products[2]->setCategoryIds([
131+
$categories[1]->getId(),
132+
$categories[2]->getId(),
133+
$categories[3]->getId(),
134+
]);
135+
$this->productResource->save($products[2]);
136+
//Reindexing
137+
$this->clearIndex();
138+
$this->indexer->reindexAll();
139+
//Checking that Category #1 now also shows Product #1 because it was
140+
//directly assigned to it and not showing Product #2 because it was
141+
//unassigned from Category #3.
142+
//Category #2 now shows Product #3 because it was directly assigned
143+
//to it.
144+
//Category #3 now shows only Product #3 because Product #2
145+
//was unassigned.
146+
//Category #4 still shows only Product #3.
147+
$this->assertProductIn($products[0], [$categories[0]], $categories);
148+
$this->assertProductIn($products[1], [], $categories);
149+
$this->assertProductIn($products[2], $categories, $categories);
150+
151+
$this->clearIndex();
152+
}
153+
154+
/**
155+
* Load categories from the fixture.
156+
*
157+
* @param int $limit
158+
* @param int $offset
159+
* @return Category[]
160+
*/
161+
private function loadCategories(int $limit, int $offset = 0): array
162+
{
163+
/** @var Category $category */
164+
$category = Bootstrap::getObjectManager()->create(
165+
Category::class
166+
);
167+
168+
$result = $category
169+
->getCollection()
170+
->addAttributeToSelect('name')
171+
->getItems();
172+
$result = array_slice($result, 2);
173+
174+
return array_slice($result, $offset, $limit);
175+
}
176+
177+
/**
178+
* Load products from the fixture.
179+
*
180+
* @param int $limit
181+
* @param int $offset
182+
* @return Product[]
183+
*/
184+
private function loadProducts(int $limit, int $offset = 0): array
185+
{
186+
/** @var Product[] $result */
187+
$result = [];
188+
$ids = range($offset + 1, $offset + $limit);
189+
foreach ($ids as $id) {
190+
/** @var \Magento\Catalog\Model\Product $product */
191+
$product = Bootstrap::getObjectManager()->create(
192+
Product::class
193+
);
194+
$result[] = $product->load($id);
195+
}
196+
197+
return $result;
198+
}
199+
200+
/**
201+
* Clear index data.
202+
*/
203+
private function clearIndex()
204+
{
205+
$this->productResource->getConnection()->delete(
206+
$this->productResource->getTable('catalog_category_product_index')
207+
);
208+
}
209+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
/** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $collection */
1717
$collection = $objectManager->create(\Magento\Catalog\Model\ResourceModel\Category\Collection::class);
1818
$collection
19-
->addAttributeToFilter('level', 2)
19+
->addAttributeToFilter('level', ['gteq' => 2])
2020
->load()
2121
->delete();
2222

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
/** @var $category \Magento\Catalog\Model\Category */
8+
$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
9+
10+
/** @var \Magento\Catalog\Model\Category $categoryFirst */
11+
$categoryFirst = $objectManager->create(\Magento\Catalog\Model\Category::class);
12+
$categoryFirst->setName('Category 1')
13+
->setPath('1/2')
14+
->setLevel(2)
15+
->setAvailableSortBy('name')
16+
->setIsActive(true)
17+
->setPosition(1)
18+
->setDefaultSortBy('name')
19+
->setIsAnchor(true)
20+
->save();
21+
22+
/** @var \Magento\Catalog\Model\Category $categorySecond */
23+
$categorySecond = $objectManager->create(\Magento\Catalog\Model\Category::class);
24+
$categorySecond->setName('Category 2')
25+
->setPath($categoryFirst->getPath())
26+
->setLevel(3)
27+
->setAvailableSortBy('name')
28+
->setIsActive(true)
29+
->setPosition(1)
30+
->setDefaultSortBy('name')
31+
->setIsAnchor(false)
32+
->save();
33+
34+
/** @var \Magento\Catalog\Model\Category $categoryThird */
35+
$categoryThird = $objectManager->create(\Magento\Catalog\Model\Category::class);
36+
$categoryThird->setName('Category 3')
37+
->setPath($categorySecond->getPath())
38+
->setLevel(4)
39+
->setAvailableSortBy('name')
40+
->setIsActive(true)
41+
->setPosition(1)
42+
->setDefaultSortBy('name')
43+
->setIsAnchor(true)
44+
->save();
45+
46+
/** @var \Magento\Catalog\Model\Category $categoryFourth */
47+
$categoryFourth = $objectManager->create(\Magento\Catalog\Model\Category::class);
48+
$categoryFourth->setName('Category 4')
49+
->setPath($categoryFirst->getPath())
50+
->setLevel(3)
51+
->setAvailableSortBy('name')
52+
->setIsActive(true)
53+
->setPosition(2)
54+
->setDefaultSortBy('name')
55+
->setIsAnchor(true)
56+
->save();
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
/** @var \Magento\Framework\ObjectManagerInterface $objectManager */
8+
$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
9+
10+
/** @var \Magento\Framework\Registry $registry */
11+
$registry = $objectManager->get(\Magento\Framework\Registry::class);
12+
13+
$registry->unregister('isSecureArea');
14+
$registry->register('isSecureArea', true);
15+
16+
/** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $collection */
17+
$collection = $objectManager->create(
18+
\Magento\Catalog\Model\ResourceModel\Category\Collection::class
19+
);
20+
$collection
21+
->addAttributeToFilter('level', ['gteq' => 2])
22+
->load()
23+
->delete();
24+
25+
$registry->unregister('isSecureArea');
26+
$registry->register('isSecureArea', false);

0 commit comments

Comments
 (0)