CalculatorTest.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Bundle\Test\Unit\Pricing\Adjustment;
  7. use Magento\Bundle\Model\ResourceModel\Selection\Collection;
  8. use \Magento\Bundle\Pricing\Adjustment\Calculator;
  9. use Magento\Bundle\Model\Product\Price as ProductPrice;
  10. use Magento\Bundle\Pricing\Price;
  11. use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
  12. /**
  13. * Test for \Magento\Bundle\Pricing\Adjustment\Calculator
  14. *
  15. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  16. */
  17. class CalculatorTest extends \PHPUnit\Framework\TestCase
  18. {
  19. /**
  20. * @var \Magento\Framework\Pricing\SaleableInterface|\PHPUnit_Framework_MockObject_MockObject
  21. */
  22. protected $saleableItem;
  23. /**
  24. * @var \Magento\Framework\Pricing\Price\PriceInterface[]|\PHPUnit_Framework_MockObject_MockObject[]
  25. */
  26. protected $priceMocks = [];
  27. /**
  28. * @var float
  29. */
  30. protected $baseAmount = 50.;
  31. /**
  32. * @var \Magento\Framework\Pricing\Adjustment\Calculator|\PHPUnit_Framework_MockObject_MockObject
  33. */
  34. protected $baseCalculator;
  35. /**
  36. * @var \PHPUnit_Framework_MockObject_MockObject
  37. */
  38. protected $amountFactory;
  39. /**
  40. * @var \PHPUnit_Framework_MockObject_MockObject
  41. */
  42. protected $selectionFactory;
  43. /**
  44. * @var \PHPUnit_Framework_MockObject_MockObject
  45. */
  46. protected $taxData;
  47. /**
  48. * @var \PHPUnit_Framework_MockObject_MockObject
  49. */
  50. private $selectionPriceListProvider;
  51. /**
  52. * @var Calculator
  53. */
  54. protected $model;
  55. protected function setUp()
  56. {
  57. $this->saleableItem = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
  58. ->setMethods(['getPriceInfo', 'getPriceType', '__wakeup', 'getStore', 'getTypeInstance'])
  59. ->disableOriginalConstructor()
  60. ->getMock();
  61. $priceCurrency = $this->getMockBuilder(\Magento\Framework\Pricing\PriceCurrencyInterface::class)->getMock();
  62. $priceInfo = $this->createMock(\Magento\Framework\Pricing\PriceInfo\Base::class);
  63. $priceInfo->expects($this->any())->method('getPrice')->will(
  64. $this->returnCallback(
  65. function ($type) {
  66. if (!isset($this->priceMocks[$type])) {
  67. throw new \PHPUnit\Framework\ExpectationFailedException('Unexpected type of price model');
  68. }
  69. return $this->priceMocks[$type];
  70. }
  71. )
  72. );
  73. $this->saleableItem->expects($this->any())->method('getPriceInfo')->will($this->returnValue($priceInfo));
  74. $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)
  75. ->disableOriginalConstructor()
  76. ->getMock();
  77. $priceCurrency->expects($this->any())->method('round')->will($this->returnArgument(0));
  78. $this->saleableItem->expects($this->any())->method('getStore')->will($this->returnValue($store));
  79. $this->baseCalculator = $this->createMock(\Magento\Framework\Pricing\Adjustment\Calculator::class);
  80. $this->amountFactory = $this->createMock(\Magento\Framework\Pricing\Amount\AmountFactory::class);
  81. $this->selectionFactory = $this->getMockBuilder(\Magento\Bundle\Pricing\Price\BundleSelectionFactory::class)
  82. ->disableOriginalConstructor()
  83. ->getMock();
  84. $this->selectionFactory->expects($this->any())->method('create')->will($this->returnArgument(1));
  85. $this->taxData = $this->getMockBuilder(\Magento\Tax\Helper\Data::class)
  86. ->disableOriginalConstructor()
  87. ->getMock();
  88. $this->selectionPriceListProvider = $this->getMockBuilder(
  89. \Magento\Bundle\Pricing\Adjustment\SelectionPriceListProviderInterface::class
  90. )->getMock();
  91. $this->model = (new ObjectManager($this))->getObject(
  92. \Magento\Bundle\Pricing\Adjustment\Calculator::class,
  93. [
  94. 'calculator' => $this->baseCalculator,
  95. 'amountFactory' => $this->amountFactory,
  96. 'bundleSelectionFactory' => $this->selectionFactory,
  97. 'taxHelper' => $this->taxData,
  98. 'priceCurrency' => $priceCurrency,
  99. 'selectionPriceListProvider' => $this->selectionPriceListProvider
  100. ]
  101. );
  102. }
  103. public function testEmptySelectionPriceList()
  104. {
  105. $option = $this->createPartialMock(\Magento\Bundle\Model\Option::class, ['getSelections', '__wakeup']);
  106. $option->expects($this->any())->method('getSelections')
  107. ->will($this->returnValue(null));
  108. $bundleProduct = $this->createMock(\Magento\Catalog\Model\Product::class);
  109. $this->assertSame([], $this->model->createSelectionPriceList($option, $bundleProduct));
  110. }
  111. /**
  112. * @dataProvider dataProviderForGetterAmount
  113. */
  114. public function testGetterAmount($amountForBundle, $optionList, $expectedResult)
  115. {
  116. $searchMin = $expectedResult['isMinAmount'];
  117. $this->baseCalculator->expects($this->atLeastOnce())->method('getAmount')
  118. ->with($this->baseAmount, $this->saleableItem)
  119. ->will($this->returnValue($this->createAmountMock($amountForBundle)));
  120. $options = [];
  121. foreach ($optionList as $optionData) {
  122. $options[] = $this->createOptionMock($optionData);
  123. }
  124. $optionSelections = [];
  125. foreach ($options as $option) {
  126. $optionSelections = array_merge($optionSelections, $option->getSelections());
  127. }
  128. $this->selectionPriceListProvider->expects($this->any())->method('getPriceList')->willReturn($optionSelections);
  129. $price = $this->createMock(\Magento\Bundle\Pricing\Price\BundleOptionPrice::class);
  130. $this->priceMocks[Price\BundleOptionPrice::PRICE_CODE] = $price;
  131. // Price type of saleable items
  132. $this->saleableItem->expects($this->any())->method('getPriceType')->will(
  133. $this->returnValue(
  134. ProductPrice::PRICE_TYPE_DYNAMIC
  135. )
  136. );
  137. $this->amountFactory->expects($this->atLeastOnce())->method('create')
  138. ->with($expectedResult['fullAmount'], $expectedResult['adjustments']);
  139. if ($searchMin) {
  140. $this->model->getAmount($this->baseAmount, $this->saleableItem);
  141. } else {
  142. $this->model->getMaxAmount($this->baseAmount, $this->saleableItem);
  143. }
  144. }
  145. /**
  146. * @return array
  147. */
  148. public function dataProviderForGetterAmount()
  149. {
  150. return [
  151. // first case with minimal amount
  152. 'case with getting minimal amount' => $this->getCaseWithMinAmount(),
  153. // second case with maximum amount
  154. 'case with getting maximum amount' => $this->getCaseWithMaxAmount(),
  155. // third case without saleable items
  156. 'case without saleable items' => $this->getCaseWithoutSaleableItems(),
  157. // fourth case without require options
  158. 'case without required options' => $this->getCaseMinAmountWithoutRequiredOptions(),
  159. ];
  160. }
  161. protected function tearDown()
  162. {
  163. $this->priceMocks = [];
  164. }
  165. /**
  166. * Create amount mock
  167. *
  168. * @param array $amountData
  169. * @return \Magento\Framework\Pricing\Amount\Base|\PHPUnit_Framework_MockObject_MockObject
  170. */
  171. protected function createAmountMock($amountData)
  172. {
  173. /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Pricing\Amount\Base $amount */
  174. $amount = $this->getMockBuilder(\Magento\Framework\Pricing\Amount\Base::class)
  175. ->setMethods(['getAdjustmentAmounts', 'getValue', '__wakeup'])
  176. ->disableOriginalConstructor()
  177. ->getMock();
  178. $amount->expects($this->any())->method('getAdjustmentAmounts')
  179. ->will($this->returnValue($amountData['adjustmentsAmounts']));
  180. $amount->expects($this->any())->method('getValue')->will($this->returnValue($amountData['amount']));
  181. return $amount;
  182. }
  183. /**
  184. * Create option mock
  185. *
  186. * @param array $optionData
  187. * @return \Magento\Bundle\Model\Option|\PHPUnit_Framework_MockObject_MockObject
  188. */
  189. protected function createOptionMock($optionData)
  190. {
  191. /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Bundle\Model\Option $option */
  192. $option = $this->createPartialMock(\Magento\Bundle\Model\Option::class, ['isMultiSelection', '__wakeup']);
  193. $option->expects($this->any())->method('isMultiSelection')
  194. ->will($this->returnValue($optionData['isMultiSelection']));
  195. $selections = [];
  196. foreach ($optionData['selections'] as $selectionData) {
  197. $selections[] = $this->createSelectionMock($selectionData);
  198. }
  199. foreach ($optionData['data'] as $key => $value) {
  200. $option->setData($key, $value);
  201. }
  202. $option->setData('selections', $selections);
  203. return $option;
  204. }
  205. /**
  206. * Create selection product mock
  207. *
  208. * @param array $selectionData
  209. * @return \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject
  210. */
  211. protected function createSelectionMock($selectionData)
  212. {
  213. /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Catalog\Model\Product $selection */
  214. $selection = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
  215. ->setMethods(['isSalable', 'getQuantity', 'getAmount', 'getProduct', '__wakeup'])
  216. ->disableOriginalConstructor()
  217. ->getMock();
  218. // All items are saleable
  219. $selection->expects($this->any())->method('isSalable')->will($this->returnValue(true));
  220. foreach ($selectionData['data'] as $key => $value) {
  221. $selection->setData($key, $value);
  222. }
  223. $amountMock = $this->createAmountMock($selectionData['amount']);
  224. $selection->expects($this->any())->method('getAmount')->will($this->returnValue($amountMock));
  225. $selection->expects($this->any())->method('getQuantity')->will($this->returnValue(1));
  226. $innerProduct = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
  227. ->setMethods(['getSelectionCanChangeQty', '__wakeup'])
  228. ->disableOriginalConstructor()
  229. ->getMock();
  230. $innerProduct->expects($this->any())->method('getSelectionCanChangeQty')->will($this->returnValue(false));
  231. $selection->expects($this->any())->method('getProduct')->will($this->returnValue($innerProduct));
  232. return $selection;
  233. }
  234. /**
  235. * Array for data provider dataProviderForGetterAmount for case 'case with getting minimal amount'
  236. *
  237. * @return array
  238. */
  239. protected function getCaseWithMinAmount()
  240. {
  241. return [
  242. 'amountForBundle' => [
  243. 'adjustmentsAmounts' => ['tax' => 102],
  244. 'amount' => 782,
  245. ],
  246. 'optionList' => [
  247. // first option with single choice of product
  248. [
  249. 'isMultiSelection' => false,
  250. 'data' => [
  251. 'title' => 'test option 1',
  252. 'default_title' => 'test option 1',
  253. 'type' => 'select',
  254. 'option_id' => '1',
  255. 'position' => '0',
  256. 'required' => '1',
  257. ],
  258. 'selections' => [
  259. 'selection with the lowest price' => [
  260. 'data' => ['price' => 50.],
  261. 'amount' => [
  262. 'adjustmentsAmounts' => ['tax' => 8, 'weee' => 10],
  263. 'amount' => 8,
  264. ],
  265. ],
  266. ]
  267. ],
  268. ],
  269. 'expectedResult' => [
  270. 'isMinAmount' => true,
  271. 'fullAmount' => 790.,
  272. 'adjustments' => ['tax' => 110, 'weee' => 10],
  273. ]
  274. ];
  275. }
  276. /**
  277. * Array for data provider dataProviderForGetterAmount for case 'case with getting maximum amount'
  278. *
  279. * @return array
  280. */
  281. protected function getCaseWithMaxAmount()
  282. {
  283. return [
  284. 'amountForBundle' => [
  285. 'adjustmentsAmounts' => ['tax' => 102],
  286. 'amount' => 782,
  287. ],
  288. 'optionList' => [
  289. // first option with single choice of product
  290. [
  291. 'isMultiSelection' => false,
  292. 'data' => [
  293. 'title' => 'test option 1',
  294. 'default_title' => 'test option 1',
  295. 'type' => 'select',
  296. 'option_id' => '1',
  297. 'position' => '0',
  298. 'required' => '1',
  299. ],
  300. 'selections' => [
  301. 'first product selection' => [
  302. 'data' => ['price' => 50.],
  303. 'amount' => [
  304. 'adjustmentsAmounts' => ['tax' => 8, 'weee' => 10],
  305. 'amount' => 8,
  306. ],
  307. ],
  308. ]
  309. ],
  310. // second option with multiselection
  311. [
  312. 'isMultiSelection' => true,
  313. 'data' => [
  314. 'title' => 'test option 2',
  315. 'default_title' => 'test option 2',
  316. 'type' => 'select',
  317. 'option_id' => '2',
  318. 'position' => '1',
  319. 'required' => '1',
  320. ],
  321. 'selections' => [
  322. 'first product selection' => [
  323. 'data' => ['price' => 20.],
  324. 'amount' => [
  325. 'adjustmentsAmounts' => ['tax' => 8],
  326. 'amount' => 8,
  327. ],
  328. ],
  329. 'second product selection' => [
  330. 'data' => ['price' => 110.],
  331. 'amount' => [
  332. 'adjustmentsAmounts' => ['tax' => 28],
  333. 'amount' => 28,
  334. ],
  335. ],
  336. 'third product selection' => [
  337. 'data' => ['price' => 50.],
  338. 'amount' => [
  339. 'adjustmentsAmounts' => ['tax' => 18],
  340. 'amount' => 18,
  341. ],
  342. ],
  343. ]
  344. ],
  345. ],
  346. 'expectedResult' => [
  347. 'isMinAmount' => false,
  348. 'fullAmount' => 844.,
  349. 'adjustments' => ['tax' => 164, 'weee' => 10],
  350. ]
  351. ];
  352. }
  353. /**
  354. * Array for data provider dataProviderForGetterAmount for case 'case without saleable items'
  355. *
  356. * @return array
  357. */
  358. protected function getCaseWithoutSaleableItems()
  359. {
  360. return [
  361. 'amountForBundle' => [
  362. 'adjustmentsAmounts' => ['tax' => 102],
  363. 'amount' => 782,
  364. ],
  365. 'optionList' => [
  366. // first option with single choice of product
  367. [
  368. 'isMultiSelection' => false,
  369. 'data' => [
  370. 'title' => 'test option 1',
  371. 'default_title' => 'test option 1',
  372. 'type' => 'select',
  373. 'option_id' => '1',
  374. 'position' => '0',
  375. 'required' => '1',
  376. ],
  377. 'selections' => []
  378. ],
  379. ],
  380. 'expectedResult' => [
  381. 'isMinAmount' => true,
  382. 'fullAmount' => 782.,
  383. 'adjustments' => ['tax' => 102],
  384. ]
  385. ];
  386. }
  387. /**
  388. * Array for data provider dataProviderForGetterAmount for case 'case without required options'
  389. *
  390. * @return array
  391. */
  392. protected function getCaseMinAmountWithoutRequiredOptions()
  393. {
  394. return [
  395. 'amountForBundle' => [
  396. 'adjustmentsAmounts' => [],
  397. 'amount' => null,
  398. ],
  399. 'optionList' => [
  400. // first option
  401. [
  402. 'isMultiSelection' => false,
  403. 'data' => [
  404. 'title' => 'test option 1',
  405. 'default_title' => 'test option 1',
  406. 'type' => 'select',
  407. 'option_id' => '1',
  408. 'position' => '0',
  409. 'required' => '0',
  410. ],
  411. 'selections' => [
  412. 'first product selection' => [
  413. 'data' => ['price' => 20.],
  414. 'amount' => [
  415. 'adjustmentsAmounts' => ['tax' => 8],
  416. 'amount' => 8,
  417. ],
  418. ],
  419. ]
  420. ],
  421. // second option
  422. [
  423. 'isMultiSelection' => false,
  424. 'data' => [
  425. 'title' => 'test option 2',
  426. 'default_title' => 'test option 2',
  427. 'type' => 'select',
  428. 'option_id' => '2',
  429. 'position' => '1',
  430. 'required' => '0',
  431. ],
  432. 'selections' => [
  433. ]
  434. ],
  435. ],
  436. 'expectedResult' => [
  437. 'isMinAmount' => true,
  438. 'fullAmount' => 8.,
  439. 'adjustments' => ['tax' => 8],
  440. ]
  441. ];
  442. }
  443. public function testGetAmountWithoutOption()
  444. {
  445. $amount = 1;
  446. $result = 5;
  447. /** @var $calculatorMock Calculator|PHPUnit_Framework_MockObject_MockObject */
  448. $calculatorMock = $this->getMockBuilder(\Magento\Bundle\Pricing\Adjustment\Calculator::class)
  449. ->disableOriginalConstructor()
  450. ->setMethods(['calculateBundleAmount'])
  451. ->getMock();
  452. $calculatorMock->expects($this->once())
  453. ->method('calculateBundleAmount')
  454. ->with($amount, $this->saleableItem, [])
  455. ->will($this->returnValue($result));
  456. $this->assertEquals($result, $calculatorMock->getAmountWithoutOption($amount, $this->saleableItem));
  457. }
  458. public function testGetMinRegularAmount()
  459. {
  460. $amount = 1;
  461. $expectedResult = 5;
  462. $exclude = 'false';
  463. /** @var $calculatorMock Calculator|PHPUnit_Framework_MockObject_MockObject */
  464. $calculatorMock = $this->getMockBuilder(\Magento\Bundle\Pricing\Adjustment\Calculator::class)
  465. ->disableOriginalConstructor()
  466. ->setMethods(['getOptionsAmount'])
  467. ->getMock();
  468. $calculatorMock->expects($this->once())
  469. ->method('getOptionsAmount')
  470. ->with($this->saleableItem, $exclude, true, $amount, true)
  471. ->will($this->returnValue($expectedResult));
  472. $result = $calculatorMock->getMinRegularAmount($amount, $this->saleableItem, $exclude);
  473. $this->assertEquals($expectedResult, $result, 'Incorrect result');
  474. }
  475. public function testGetMaxRegularAmount()
  476. {
  477. $amount = 1;
  478. $expectedResult = 5;
  479. $exclude = 'false';
  480. /** @var $calculatorMock Calculator|PHPUnit_Framework_MockObject_MockObject */
  481. $calculatorMock = $this->getMockBuilder(\Magento\Bundle\Pricing\Adjustment\Calculator::class)
  482. ->disableOriginalConstructor()
  483. ->setMethods(['getOptionsAmount'])
  484. ->getMock();
  485. $calculatorMock->expects($this->once())
  486. ->method('getOptionsAmount')
  487. ->with($this->saleableItem, $exclude, false, $amount, true)
  488. ->will($this->returnValue($expectedResult));
  489. $result = $calculatorMock->getMaxRegularAmount($amount, $this->saleableItem, $exclude);
  490. $this->assertEquals($expectedResult, $result, 'Incorrect result');
  491. }
  492. /**
  493. * @dataProvider getOptionsAmountDataProvider
  494. */
  495. public function testGetOptionsAmount($searchMin, $useRegularPrice)
  496. {
  497. $amount = 1;
  498. $expectedResult = 5;
  499. $exclude = 'false';
  500. /** @var $calculatorMock Calculator|PHPUnit_Framework_MockObject_MockObject */
  501. $calculatorMock = $this->getMockBuilder(\Magento\Bundle\Pricing\Adjustment\Calculator::class)
  502. ->disableOriginalConstructor()
  503. ->setMethods(['calculateBundleAmount', 'getSelectionAmounts'])
  504. ->getMock();
  505. $selections[] = $this->getMockBuilder(\Magento\Bundle\Pricing\Price\BundleSelectionPrice::class)
  506. ->disableOriginalConstructor()
  507. ->getMock();
  508. $calculatorMock->expects($this->once())
  509. ->method('getSelectionAmounts')
  510. ->with($this->saleableItem, $searchMin, $useRegularPrice)
  511. ->will($this->returnValue($selections));
  512. $calculatorMock->expects($this->once())
  513. ->method('calculateBundleAmount')
  514. ->with($amount, $this->saleableItem, $selections, $exclude)
  515. ->will($this->returnValue($expectedResult));
  516. $result = $calculatorMock->getOptionsAmount(
  517. $this->saleableItem,
  518. $exclude,
  519. $searchMin,
  520. $amount,
  521. $useRegularPrice
  522. );
  523. $this->assertEquals($expectedResult, $result, 'Incorrect result');
  524. }
  525. /**
  526. * @return array
  527. */
  528. public function getOptionsAmountDataProvider()
  529. {
  530. return [
  531. 'true, true' => [
  532. 'searchMin' => true,
  533. 'useRegularPrice' => true,
  534. ],
  535. 'true, false' => [
  536. 'searchMin' => true,
  537. 'useRegularPrice' => false,
  538. ],
  539. 'false, true' => [
  540. 'searchMin' => false,
  541. 'useRegularPrice' => true,
  542. ],
  543. 'false, false' => [
  544. 'searchMin' => false,
  545. 'useRegularPrice' => false,
  546. ],
  547. ];
  548. }
  549. }