1414use Magento \Catalog \Model \ResourceModel \Product \Indexer \Price \Query \JoinAttributeProcessor ;
1515use Magento \Framework \App \ResourceConnection ;
1616use Magento \Framework \DB \Adapter \AdapterInterface ;
17+ use Magento \Framework \EntityManager \EntityMetadataInterface ;
1718use Magento \Framework \EntityManager \MetadataPool ;
1819use Magento \Framework \Event \ManagerInterface ;
1920use Magento \Framework \Module \Manager ;
2223
2324/**
2425 * Class to test Bundle products Price indexer resource model
26+ *
27+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
2528 */
2629class PriceTest extends TestCase
2730{
@@ -45,6 +48,11 @@ class PriceTest extends TestCase
4548 */
4649 private $ priceModel ;
4750
51+ /**
52+ * @var MetadataPool
53+ */
54+ private $ metadataPool ;
55+
4856 /**
4957 * @inheritdoc
5058 */
@@ -64,7 +72,7 @@ protected function setUp(): void
6472 /** @var TableMaintainer|MockObject $tableMaintainer */
6573 $ tableMaintainer = $ this ->createMock (TableMaintainer::class);
6674 /** @var MetadataPool|MockObject $metadataPool */
67- $ metadataPool = $ this ->createMock (MetadataPool::class);
75+ $ this -> metadataPool = $ this ->createMock (MetadataPool::class);
6876 /** @var BasePriceModifier|MockObject $basePriceModifier */
6977 $ basePriceModifier = $ this ->createMock (BasePriceModifier::class);
7078 /** @var JoinAttributeProcessor|MockObject $joinAttributeProcessor */
@@ -78,7 +86,7 @@ protected function setUp(): void
7886 $ this ->priceModel = new Price (
7987 $ indexTableStructureFactory ,
8088 $ tableMaintainer ,
81- $ metadataPool ,
89+ $ this -> metadataPool ,
8290 $ this ->resourceMock ,
8391 $ basePriceModifier ,
8492 $ joinAttributeProcessor ,
@@ -89,6 +97,124 @@ protected function setUp(): void
8997 );
9098 }
9199
100+ /**
101+ * @throws \ReflectionException
102+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
103+ */
104+ public function testCalculateDynamicBundleSelectionPrice (): void
105+ {
106+ $ entity = 'entity_id ' ;
107+ $ price = 'idx.min_price * bs.selection_qty ' ;
108+ //@codingStandardsIgnoreStart
109+ $ selectQuery = "SELECT `i`.`entity_id`,
110+ `i`.`customer_group_id`,
111+ `i`.`website_id`,
112+ `bo`.`option_id`,
113+ `bs`.`selection_id`,
114+ IF(bo.type = 'select' OR bo.type = 'radio', 0, 1) AS `group_type`,
115+ `bo`.`required` AS `is_required`,
116+ LEAST(IF(i.special_price > 0 AND i.special_price < 100,
117+ ROUND(idx.min_price * bs.selection_qty * (i.special_price / 100), 4), idx.min_price * bs.selection_qty),
118+ IFNULL((IF(i.tier_percent IS NOT NULL,
119+ ROUND((1 - i.tier_percent / 100) * idx.min_price * bs.selection_qty, 4), NULL)), idx.min_price *
120+ bs.selection_qty)) AS `price`,
121+ IF(i.tier_percent IS NOT NULL, ROUND((1 - i.tier_percent / 100) * idx.min_price * bs.selection_qty, 4),
122+ NULL) AS `tier_price`
123+ FROM `catalog_product_index_price_bundle_temp` AS `i`
124+ INNER JOIN `catalog_product_entity` AS `parent_product` ON parent_product.entity_id = i.entity_id AND
125+ (parent_product.created_in <= 1 AND parent_product.updated_in > 1)
126+ INNER JOIN `catalog_product_bundle_option` AS `bo` ON bo.parent_id = parent_product.row_id
127+ INNER JOIN `catalog_product_bundle_selection` AS `bs` ON bs.option_id = bo.option_id
128+ INNER JOIN `catalog_product_index_price_replica` AS `idx`
129+ ON bs.product_id = idx.entity_id AND i.customer_group_id = idx.customer_group_id AND
130+ i.website_id = idx.website_id
131+ INNER JOIN `cataloginventory_stock_status` AS `si` ON si.product_id = bs.product_id
132+ WHERE (i.price_type = 0)
133+ AND (si.stock_status = 1)
134+ ON DUPLICATE KEY UPDATE `entity_id` = VALUES(`entity_id`),
135+ `customer_group_id` = VALUES(`customer_group_id`),
136+ `website_id` = VALUES(`website_id`),
137+ `option_id` = VALUES(`option_id`),
138+ `selection_id` = VALUES(`selection_id`),
139+ `group_type` = VALUES(`group_type`),
140+ `is_required` = VALUES(`is_required`),
141+ `price` = VALUES(`price`),
142+ `tier_price` = VALUES(`tier_price`) " ;
143+ $ processedQuery = "INSERT INTO `catalog_product_index_price_bundle_sel_temp` (,,,,,,,,) SELECT `i`.`entity_id`,
144+ `i`.`customer_group_id`,
145+ `i`.`website_id`,
146+ `bo`.`option_id`,
147+ `bs`.`selection_id`,
148+ IF(bo.type = 'select' OR bo.type = 'radio', 0, 1) AS `group_type`,
149+ `bo`.`required` AS `is_required`,
150+ LEAST(IF(i.special_price > 0 AND i.special_price < 100,
151+ ROUND(idx.min_price * bs.selection_qty * (i.special_price / 100), 4), idx.min_price * bs.selection_qty),
152+ IFNULL((IF(i.tier_percent IS NOT NULL,
153+ ROUND((1 - i.tier_percent / 100) * idx.min_price * bs.selection_qty, 4), NULL)), idx.min_price *
154+ bs.selection_qty)) AS `price`,
155+ IF(i.tier_percent IS NOT NULL, ROUND((1 - i.tier_percent / 100) * idx.min_price * bs.selection_qty, 4),
156+ NULL) AS `tier_price`
157+ FROM `catalog_product_index_price_bundle_temp` AS `i`
158+ INNER JOIN `catalog_product_entity` AS `parent_product` ON parent_product.entity_id = i.entity_id AND
159+ (parent_product.created_in <= 1 AND parent_product.updated_in > 1)
160+ INNER JOIN `catalog_product_bundle_option` AS `bo` ON bo.parent_id = parent_product.row_id
161+ INNER JOIN `catalog_product_bundle_selection` AS `bs` ON bs.option_id = bo.option_id
162+ INNER JOIN `catalog_product_index_price_replica` AS `idx` USE INDEX (PRIMARY)
163+ ON bs.product_id = idx.entity_id AND i.customer_group_id = idx.customer_group_id AND
164+ i.website_id = idx.website_id
165+ INNER JOIN `cataloginventory_stock_status` AS `si` ON si.product_id = bs.product_id
166+ WHERE (i.price_type = 0)
167+ AND (si.stock_status = 1)
168+ ON DUPLICATE KEY UPDATE `entity_id` = VALUES(`entity_id`),
169+ `customer_group_id` = VALUES(`customer_group_id`),
170+ `website_id` = VALUES(`website_id`),
171+ `option_id` = VALUES(`option_id`),
172+ `selection_id` = VALUES(`selection_id`),
173+ `group_type` = VALUES(`group_type`),
174+ `is_required` = VALUES(`is_required`),
175+ `price` = VALUES(`price`),
176+ `tier_price` = VALUES(`tier_price`) ON DUPLICATE KEY UPDATE = VALUES(), = VALUES(), = VALUES(), = VALUES(), = VALUES(), = VALUES(), = VALUES(), = VALUES(), = VALUES() " ;
177+ //@codingStandardsIgnoreEnd
178+ $ this ->connectionMock ->expects ($ this ->exactly (3 ))
179+ ->method ('getCheckSql ' )
180+ ->withConsecutive (
181+ [
182+ 'i.special_price > 0 AND i.special_price < 100 ' ,
183+ 'ROUND( ' . $ price . ' * (i.special_price / 100), 4) ' ,
184+ $ price
185+ ],
186+ [
187+ 'i.tier_percent IS NOT NULL ' ,
188+ 'ROUND((1 - i.tier_percent / 100) * ' . $ price . ', 4) ' ,
189+ 'NULL '
190+ ],
191+ ["bo.type = 'select' OR bo.type = 'radio' " , '0 ' , '1 ' ]
192+ );
193+
194+ $ select = $ this ->createMock (\Magento \Framework \DB \Select::class);
195+ $ select ->expects ($ this ->once ())->method ('from ' )->willReturn ($ select );
196+ $ select ->expects ($ this ->exactly (5 ))->method ('join ' )->willReturn ($ select );
197+ $ select ->expects ($ this ->exactly (2 ))->method ('where ' )->willReturn ($ select );
198+ $ select ->expects ($ this ->once ())->method ('columns ' )->willReturn ($ select );
199+ $ select ->expects ($ this ->any ())->method ('__toString ' )->willReturn ($ selectQuery );
200+
201+ $ this ->connectionMock ->expects ($ this ->once ())->method ('getIfNullSql ' );
202+ $ this ->connectionMock ->expects ($ this ->once ())->method ('getLeastSql ' );
203+ $ this ->connectionMock ->expects ($ this ->any ())
204+ ->method ('select ' )
205+ ->willReturn ($ select );
206+ $ this ->connectionMock ->expects ($ this ->exactly (9 ))->method ('quoteIdentifier ' );
207+ $ this ->connectionMock ->expects ($ this ->once ())->method ('query ' )->with ($ processedQuery );
208+
209+ $ pool = $ this ->createMock (EntityMetadataInterface::class);
210+ $ pool ->expects ($ this ->once ())->method ('getLinkField ' )->willReturn ($ entity );
211+ $ this ->metadataPool ->expects ($ this ->once ())
212+ ->method ('getMetadata ' )
213+ ->willReturn ($ pool );
214+
215+ $ this ->invokeMethodViaReflection ('calculateDynamicBundleSelectionPrice ' , []);
216+ }
217+
92218 /**
93219 * Tests create Bundle Price temporary table
94220 */
@@ -147,16 +273,18 @@ public function testGetBundleOptionTable(): void
147273 * Invoke private method via reflection
148274 *
149275 * @param string $methodName
276+ * @param array $args
150277 * @return string
278+ * @throws \ReflectionException
151279 */
152- private function invokeMethodViaReflection (string $ methodName ): string
280+ private function invokeMethodViaReflection (string $ methodName, array $ args = [] ): string
153281 {
154282 $ method = new \ReflectionMethod (
155283 Price::class,
156284 $ methodName
157285 );
158286 $ method ->setAccessible (true );
159287
160- return (string )$ method ->invoke ($ this ->priceModel );
288+ return (string )$ method ->invoke ($ this ->priceModel , $ args );
161289 }
162290}
0 commit comments