10
10
11
11
use Magento \Catalog \Api \CategoryRepositoryInterface ;
12
12
use Magento \Catalog \Model \Category as CategoryModel ;
13
+ use Magento \Catalog \Model \Indexer \Category \Product as CategoryProductIndexer ;
14
+ use Magento \Catalog \Model \Indexer \Product \Category as ProductCategoryIndexer ;
13
15
use Magento \Catalog \Model \ResourceModel \Category as CategoryResource ;
14
16
use Magento \Catalog \Model \ResourceModel \Category \Collection as CategoryCollection ;
15
17
use Magento \Catalog \Model \ResourceModel \Category \CollectionFactory as CategoryCollectionFactory ;
18
+ use Magento \Catalog \Model \ResourceModel \Product as ProductResource ;
19
+ use Magento \Catalog \Model \ResourceModel \Product \Collection as ProductCollection ;
16
20
use Magento \Framework \App \Filesystem \DirectoryList ;
17
21
use Magento \Framework \Filesystem ;
18
22
use Magento \Framework \Filesystem \Directory \WriteInterface ;
23
+ use Magento \Framework \Indexer \IndexerInterface ;
24
+ use Magento \Framework \Indexer \IndexerRegistry ;
19
25
use Magento \Framework \ObjectManagerInterface ;
20
26
use Magento \Framework \UrlInterface ;
27
+ use Magento \Indexer \Cron \UpdateMview ;
21
28
use Magento \Store \Model \StoreManagerInterface ;
22
29
use Magento \TestFramework \Helper \Bootstrap ;
23
30
use PHPUnit \Framework \TestCase ;
26
33
* Tests category resource model
27
34
*
28
35
* @see \Magento\Catalog\Model\ResourceModel\Category
36
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
29
37
*/
30
38
class CategoryTest extends TestCase
31
39
{
@@ -54,6 +62,11 @@ class CategoryTest extends TestCase
54
62
/** @var WriteInterface */
55
63
private $ mediaDirectory ;
56
64
65
+ /**
66
+ * @var ProductResource
67
+ */
68
+ private $ productResource ;
69
+
57
70
/**
58
71
* @inheritdoc
59
72
*/
@@ -68,6 +81,7 @@ protected function setUp(): void
68
81
$ this ->categoryCollection = $ this ->objectManager ->get (CategoryCollectionFactory::class)->create ();
69
82
$ this ->filesystem = $ this ->objectManager ->get (Filesystem::class);
70
83
$ this ->mediaDirectory = $ this ->filesystem ->getDirectoryWrite (DirectoryList::MEDIA );
84
+ $ this ->productResource = Bootstrap::getObjectManager ()->get (ProductResource::class);
71
85
}
72
86
73
87
/**
@@ -116,6 +130,128 @@ public function testAddImageForCategory(): void
116
130
$ this ->assertFileExists ($ this ->mediaDirectory ->getAbsolutePath ($ imageRelativePath ));
117
131
}
118
132
133
+ /**
134
+ * Test that adding or removing products in a category should not trigger full reindex in scheduled update mode
135
+ *
136
+ * @magentoAppArea adminhtml
137
+ * @magentoAppIsolation enabled
138
+ * @magentoDbIsolation disabled
139
+ * @magentoDataFixture Magento/Catalog/_files/category_with_three_products.php
140
+ * @magentoDataFixture Magento/Catalog/_files/product_simple_duplicated.php
141
+ * @magentoDataFixture Magento/Catalog/_files/catalog_category_product_reindex_all.php
142
+ * @magentoDataFixture Magento/Catalog/_files/catalog_product_category_reindex_all.php
143
+ * @magentoDataFixture Magento/Catalog/_files/enable_catalog_product_reindex_schedule.php
144
+ * @dataProvider catalogProductChangesWithScheduledUpdateDataProvider
145
+ * @param array $products
146
+ * @return void
147
+ */
148
+ public function testCatalogProductChangesWithScheduledUpdate (array $ products ): void
149
+ {
150
+ // products are ordered by entity_id DESC because their positions are same and equal to 0
151
+ $ initialProducts = ['simple1002 ' , 'simple1001 ' , 'simple1000 ' ];
152
+ $ defaultStoreId = (int ) $ this ->storeManager ->getDefaultStoreView ()->getId ();
153
+ $ category = $ this ->getCategory (['name ' => 'Category 999 ' ]);
154
+ $ expectedProducts = array_keys ($ products );
155
+ $ productIdsBySkus = $ this ->productResource ->getProductsIdsBySkus ($ expectedProducts );
156
+ $ postedProducts = [];
157
+ foreach ($ products as $ sku => $ position ) {
158
+ $ postedProducts [$ productIdsBySkus [$ sku ]] = $ position ;
159
+ }
160
+ $ category ->setPostedProducts ($ postedProducts );
161
+ $ this ->categoryResource ->save ($ category );
162
+ // Indices should not be invalidated when adding/removing/reordering products in a category.
163
+ $ categoryProductIndexer = $ this ->getIndexer (CategoryProductIndexer::INDEXER_ID );
164
+ $ this ->assertTrue (
165
+ $ categoryProductIndexer ->isValid (),
166
+ '"Indexed category/products association" indexer should not be invalidated. '
167
+ );
168
+ $ productCategoryIndexer = $ this ->getIndexer (ProductCategoryIndexer::INDEXER_ID );
169
+ $ this ->assertTrue (
170
+ $ productCategoryIndexer ->isValid (),
171
+ '"Indexed product/categories association" indexer should not be invalidated. '
172
+ );
173
+ // catalog products is not update until partial reindex occurs
174
+ $ collection = $ this ->getCategoryProducts ($ category , $ defaultStoreId );
175
+ $ this ->assertEquals ($ initialProducts , $ collection ->getColumnValues ('sku ' ));
176
+ // Execute MVIEW cron handler for cron job "indexer_update_all_views"
177
+ /** @var $mViewCron UpdateMview */
178
+ $ mViewCron = $ this ->objectManager ->create (UpdateMview::class);
179
+ $ mViewCron ->execute ();
180
+ $ collection = $ this ->getCategoryProducts ($ category , $ defaultStoreId );
181
+ $ this ->assertEquals ($ expectedProducts , $ collection ->getColumnValues ('sku ' ));
182
+ }
183
+
184
+ /**
185
+ * @return array
186
+ */
187
+ public function catalogProductChangesWithScheduledUpdateDataProvider (): array
188
+ {
189
+ return [
190
+ 'change products position ' => [
191
+ [
192
+ 'simple1002 ' => 1 ,
193
+ 'simple1000 ' => 2 ,
194
+ 'simple1001 ' => 3 ,
195
+ ]
196
+ ],
197
+ 'Add new product ' => [
198
+ [
199
+ 'simple1002 ' => 1 ,
200
+ 'simple1000 ' => 2 ,
201
+ 'simple-1 ' => 3 ,
202
+ 'simple1001 ' => 4 ,
203
+ ]
204
+ ],
205
+ 'Delete product ' => [
206
+ [
207
+ 'simple1002 ' => 1 ,
208
+ 'simple1000 ' => 2 ,
209
+ ]
210
+ ]
211
+ ];
212
+ }
213
+
214
+ /**
215
+ * @param CategoryModel $category
216
+ * @param int $defaultStoreId
217
+ * @return ProductCollection
218
+ */
219
+ private function getCategoryProducts (CategoryModel $ category , int $ defaultStoreId )
220
+ {
221
+ /** @var ProductCollection $collection */
222
+ $ collection = $ this ->objectManager ->create (ProductCollection::class);
223
+ $ collection ->setStoreId ($ defaultStoreId );
224
+ $ collection ->addCategoryFilter ($ category );
225
+ $ collection ->addAttributeToSort ('position ' );
226
+ return $ collection ;
227
+ }
228
+
229
+ /**
230
+ * @param array $filters
231
+ * @return CategoryModel
232
+ */
233
+ private function getCategory (array $ filters ): CategoryModel
234
+ {
235
+ /** @var CategoryCollection $categoryCollection */
236
+ $ categoryCollection = $ this ->objectManager ->create (CategoryCollection::class);
237
+ foreach ($ filters as $ field => $ value ) {
238
+ $ categoryCollection ->addFieldToFilter ($ field , $ value );
239
+ }
240
+
241
+ return $ categoryCollection ->getFirstItem ();
242
+ }
243
+
244
+ /**
245
+ * @param string $indexerId
246
+ * @return IndexerInterface
247
+ */
248
+ private function getIndexer (string $ indexerId ): IndexerInterface
249
+ {
250
+ /** @var IndexerRegistry $indexerRegistry */
251
+ $ indexerRegistry = $ this ->objectManager ->get (IndexerRegistry::class);
252
+ return $ indexerRegistry ->get ($ indexerId );
253
+ }
254
+
119
255
/**
120
256
* Prepare image url for image data
121
257
*
0 commit comments