DiscountTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\SalesRule\Test\Unit\Model\Quote;
  7. /**
  8. * Class DiscountTest
  9. *
  10. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  11. */
  12. class DiscountTest extends \PHPUnit\Framework\TestCase
  13. {
  14. /**
  15. * @var \Magento\SalesRule\Model\Quote\Discount
  16. */
  17. protected $discount;
  18. /**
  19. * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager
  20. */
  21. protected $objectManager;
  22. /**
  23. * @var \PHPUnit_Framework_MockObject_MockObject
  24. */
  25. protected $storeManagerMock;
  26. /**
  27. * @var \PHPUnit_Framework_MockObject_MockObject
  28. */
  29. protected $validatorMock;
  30. /**
  31. * @var \PHPUnit_Framework_MockObject_MockObject
  32. */
  33. protected $eventManagerMock;
  34. /**
  35. * @var \PHPUnit_Framework_MockObject_MockObject
  36. */
  37. protected $shippingAssignmentMock;
  38. /**
  39. * @var \PHPUnit_Framework_MockObject_MockObject
  40. */
  41. protected $addressMock;
  42. protected function setUp()
  43. {
  44. $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
  45. $this->storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManager::class);
  46. $this->validatorMock = $this->getMockBuilder(\Magento\SalesRule\Model\Validator::class)
  47. ->disableOriginalConstructor()
  48. ->setMethods(
  49. [
  50. 'canApplyRules',
  51. 'reset',
  52. 'init',
  53. 'initTotals',
  54. 'sortItemsByPriority',
  55. 'setSkipActionsValidation',
  56. 'process',
  57. 'processShippingAmount',
  58. 'canApplyDiscount',
  59. '__wakeup',
  60. ]
  61. )
  62. ->getMock();
  63. $this->eventManagerMock = $this->createMock(\Magento\Framework\Event\Manager::class);
  64. $priceCurrencyMock = $this->createMock(\Magento\Framework\Pricing\PriceCurrencyInterface::class);
  65. $priceCurrencyMock->expects($this->any())
  66. ->method('round')
  67. ->will($this->returnCallback(
  68. function ($argument) {
  69. return round($argument, 2);
  70. }
  71. ));
  72. $this->addressMock = $this->createPartialMock(
  73. \Magento\Quote\Model\Quote\Address::class,
  74. ['getQuote', 'getAllItems', 'getShippingAmount', '__wakeup', 'getCustomAttributesCodes']
  75. );
  76. $this->addressMock->expects($this->any())
  77. ->method('getCustomAttributesCodes')
  78. ->willReturn([]);
  79. $shipping = $this->createMock(\Magento\Quote\Api\Data\ShippingInterface::class);
  80. $shipping->expects($this->any())->method('getAddress')->willReturn($this->addressMock);
  81. $this->shippingAssignmentMock = $this->createMock(\Magento\Quote\Api\Data\ShippingAssignmentInterface::class);
  82. $this->shippingAssignmentMock->expects($this->any())->method('getShipping')->willReturn($shipping);
  83. /** @var \Magento\SalesRule\Model\Quote\Discount $discount */
  84. $this->discount = $this->objectManager->getObject(
  85. \Magento\SalesRule\Model\Quote\Discount::class,
  86. [
  87. 'storeManager' => $this->storeManagerMock,
  88. 'validator' => $this->validatorMock,
  89. 'eventManager' => $this->eventManagerMock,
  90. 'priceCurrency' => $priceCurrencyMock,
  91. ]
  92. );
  93. }
  94. public function testCollectItemNoDiscount()
  95. {
  96. $itemNoDiscount = $this->createPartialMock(
  97. \Magento\Quote\Model\Quote\Item::class,
  98. ['getNoDiscount', '__wakeup']
  99. );
  100. $itemNoDiscount->expects($this->once())->method('getNoDiscount')->willReturn(true);
  101. $this->validatorMock->expects($this->once())->method('sortItemsByPriority')
  102. ->with([$itemNoDiscount], $this->addressMock)
  103. ->willReturnArgument(0);
  104. $storeMock = $this->createPartialMock(\Magento\Store\Model\Store::class, ['getStore', '__wakeup']);
  105. $this->storeManagerMock->expects($this->any())->method('getStore')->willReturn($storeMock);
  106. $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class);
  107. $this->addressMock->expects($this->any())->method('getQuote')->willReturn($quoteMock);
  108. $this->shippingAssignmentMock->expects($this->any())->method('getItems')->willReturn([$itemNoDiscount]);
  109. $this->addressMock->expects($this->any())->method('getShippingAmount')->willReturn(true);
  110. $totalMock = $this->createMock(\Magento\Quote\Model\Quote\Address\Total::class);
  111. $this->assertInstanceOf(
  112. \Magento\SalesRule\Model\Quote\Discount::class,
  113. $this->discount->collect($quoteMock, $this->shippingAssignmentMock, $totalMock)
  114. );
  115. }
  116. public function testCollectItemHasParent()
  117. {
  118. $itemWithParentId = $this->createPartialMock(
  119. \Magento\Quote\Model\Quote\Item::class,
  120. ['getNoDiscount', 'getParentItem', '__wakeup']
  121. );
  122. $itemWithParentId->expects($this->once())->method('getNoDiscount')->willReturn(false);
  123. $itemWithParentId->expects($this->once())->method('getParentItem')->willReturn(true);
  124. $this->validatorMock->expects($this->any())->method('canApplyDiscount')->willReturn(true);
  125. $this->validatorMock->expects($this->any())->method('sortItemsByPriority')
  126. ->with([$itemWithParentId], $this->addressMock)
  127. ->willReturnArgument(0);
  128. $storeMock = $this->createPartialMock(\Magento\Store\Model\Store::class, ['getStore', '__wakeup']);
  129. $this->storeManagerMock->expects($this->any())->method('getStore')->willReturn($storeMock);
  130. $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class);
  131. $this->addressMock->expects($this->any())->method('getQuote')->willReturn($quoteMock);
  132. $this->addressMock->expects($this->any())->method('getShippingAmount')->willReturn(true);
  133. $this->shippingAssignmentMock->expects($this->any())->method('getItems')->willReturn([$itemWithParentId]);
  134. $totalMock = $this->createMock(\Magento\Quote\Model\Quote\Address\Total::class);
  135. $this->assertInstanceOf(
  136. \Magento\SalesRule\Model\Quote\Discount::class,
  137. $this->discount->collect($quoteMock, $this->shippingAssignmentMock, $totalMock)
  138. );
  139. }
  140. /**
  141. * @dataProvider collectItemHasChildrenDataProvider
  142. */
  143. public function testCollectItemHasChildren($childItemData, $parentData, $expectedChildData)
  144. {
  145. $childItems = [];
  146. foreach ($childItemData as $itemId => $itemData) {
  147. $item = $this->objectManager->getObject(\Magento\Quote\Model\Quote\Item::class)->setData($itemData);
  148. $childItems[$itemId] = $item;
  149. }
  150. $itemWithChildren = $this->getMockBuilder(\Magento\Quote\Model\Quote\Item::class)
  151. ->disableOriginalConstructor()
  152. ->setMethods(
  153. [
  154. 'getNoDiscount',
  155. 'getParentItem',
  156. 'getHasChildren',
  157. 'isChildrenCalculated',
  158. 'getChildren',
  159. '__wakeup',
  160. ]
  161. )
  162. ->getMock();
  163. $itemWithChildren->expects($this->once())->method('getNoDiscount')->willReturn(false);
  164. $itemWithChildren->expects($this->once())->method('getParentItem')->willReturn(false);
  165. $itemWithChildren->expects($this->once())->method('getHasChildren')->willReturn(true);
  166. $itemWithChildren->expects($this->once())->method('isChildrenCalculated')->willReturn(true);
  167. $itemWithChildren->expects($this->any())->method('getChildren')->willReturn($childItems);
  168. foreach ($parentData as $key => $value) {
  169. $itemWithChildren->setData($key, $value);
  170. }
  171. $this->validatorMock->expects($this->any())->method('canApplyDiscount')->willReturn(true);
  172. $this->validatorMock->expects($this->once())->method('sortItemsByPriority')
  173. ->with([$itemWithChildren], $this->addressMock)
  174. ->willReturnArgument(0);
  175. $this->validatorMock->expects($this->any())->method('canApplyRules')->willReturn(true);
  176. $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class)
  177. ->disableOriginalConstructor()
  178. ->setMethods(['getStore', '__wakeup'])
  179. ->getMock();
  180. $this->storeManagerMock->expects($this->any())->method('getStore')->willReturn($storeMock);
  181. $quoteMock = $this->getMockBuilder(\Magento\Quote\Model\Quote::class)
  182. ->disableOriginalConstructor()
  183. ->getMock();
  184. $this->addressMock->expects($this->any())->method('getQuote')->willReturn($quoteMock);
  185. $this->addressMock->expects($this->any())->method('getShippingAmount')->willReturn(true);
  186. $this->shippingAssignmentMock->expects($this->any())->method('getItems')->willReturn([$itemWithChildren]);
  187. $totalMock = $this->createMock(\Magento\Quote\Model\Quote\Address\Total::class);
  188. $this->assertInstanceOf(
  189. \Magento\SalesRule\Model\Quote\Discount::class,
  190. $this->discount->collect($quoteMock, $this->shippingAssignmentMock, $totalMock)
  191. );
  192. foreach ($expectedChildData as $itemId => $expectedItemData) {
  193. $childItem = $childItems[$itemId];
  194. foreach ($expectedItemData as $key => $value) {
  195. $this->assertEquals($value, $childItem->getData($key), 'Incorrect value for ' . $key);
  196. }
  197. }
  198. }
  199. /**
  200. * @return array
  201. */
  202. public function collectItemHasChildrenDataProvider()
  203. {
  204. $data = [
  205. // 3 items, each $100, testing that discount are distributed to item correctly
  206. [
  207. 'child_item_data' => [
  208. 'item1' => [
  209. 'base_row_total' => 0,
  210. ]
  211. ],
  212. 'parent_item_data' => [
  213. 'discount_amount' => 20,
  214. 'base_discount_amount' => 10,
  215. 'original_discount_amount' => 40,
  216. 'base_original_discount_amount' => 20,
  217. 'base_row_total' => 0,
  218. ],
  219. 'expected_child_item_data' => [
  220. 'item1' => [
  221. 'discount_amount' => 0,
  222. 'base_discount_amount' => 0,
  223. 'original_discount_amount' => 0,
  224. 'base_original_discount_amount' => 0,
  225. ]
  226. ],
  227. ],
  228. [
  229. // 3 items, each $100, testing that discount are distributed to item correctly
  230. 'child_item_data' => [
  231. 'item1' => [
  232. 'base_row_total' => 100,
  233. ],
  234. 'item2' => [
  235. 'base_row_total' => 100,
  236. ],
  237. 'item3' => [
  238. 'base_row_total' => 100,
  239. ],
  240. ],
  241. 'parent_item_data' => [
  242. 'discount_amount' => 20,
  243. 'base_discount_amount' => 10,
  244. 'original_discount_amount' => 40,
  245. 'base_original_discount_amount' => 20,
  246. 'base_row_total' => 300,
  247. ],
  248. 'expected_child_item_data' => [
  249. 'item1' => [
  250. 'discount_amount' => 6.67,
  251. 'base_discount_amount' => 3.33,
  252. 'original_discount_amount' => 13.33,
  253. 'base_original_discount_amount' => 6.67,
  254. ],
  255. 'item2' => [
  256. 'discount_amount' => 6.66,
  257. 'base_discount_amount' => 3.34,
  258. 'original_discount_amount' => 13.34,
  259. 'base_original_discount_amount' => 6.66,
  260. ],
  261. 'item3' => [
  262. 'discount_amount' => 6.67,
  263. 'base_discount_amount' => 3.33,
  264. 'original_discount_amount' => 13.33,
  265. 'base_original_discount_amount' => 6.67,
  266. ],
  267. ],
  268. ],
  269. ];
  270. return $data;
  271. }
  272. public function testCollectItemHasNoChildren()
  273. {
  274. $itemWithChildren = $this->getMockBuilder(\Magento\Quote\Model\Quote\Item::class)
  275. ->disableOriginalConstructor()
  276. ->setMethods(
  277. [
  278. 'getNoDiscount',
  279. 'getParentItem',
  280. 'getHasChildren',
  281. 'isChildrenCalculated',
  282. 'getChildren',
  283. '__wakeup',
  284. ]
  285. )
  286. ->getMock();
  287. $itemWithChildren->expects($this->once())->method('getNoDiscount')->willReturn(false);
  288. $itemWithChildren->expects($this->once())->method('getParentItem')->willReturn(false);
  289. $itemWithChildren->expects($this->once())->method('getHasChildren')->willReturn(false);
  290. $this->validatorMock->expects($this->any())->method('canApplyDiscount')->willReturn(true);
  291. $this->validatorMock->expects($this->once())->method('sortItemsByPriority')
  292. ->with([$itemWithChildren], $this->addressMock)
  293. ->willReturnArgument(0);
  294. $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class)
  295. ->disableOriginalConstructor()
  296. ->setMethods(['getStore', '__wakeup'])
  297. ->getMock();
  298. $this->storeManagerMock->expects($this->any())->method('getStore')->willReturn($storeMock);
  299. $quoteMock = $this->getMockBuilder(\Magento\Quote\Model\Quote::class)->disableOriginalConstructor()->getMock();
  300. $this->addressMock->expects($this->any())->method('getQuote')->willReturn($quoteMock);
  301. $this->addressMock->expects($this->any())->method('getShippingAmount')->willReturn(true);
  302. $this->shippingAssignmentMock->expects($this->any())->method('getItems')->willReturn([$itemWithChildren]);
  303. $totalMock = $this->createMock(\Magento\Quote\Model\Quote\Address\Total::class);
  304. $this->assertInstanceOf(
  305. \Magento\SalesRule\Model\Quote\Discount::class,
  306. $this->discount->collect($quoteMock, $this->shippingAssignmentMock, $totalMock)
  307. );
  308. }
  309. public function testFetch()
  310. {
  311. $discountAmount = 100;
  312. $discountDescription = 100;
  313. $expectedResult = [
  314. 'code' => 'discount',
  315. 'value' => 100,
  316. 'title' => __('Discount (%1)', $discountDescription)
  317. ];
  318. $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class);
  319. $totalMock = $this->createPartialMock(
  320. \Magento\Quote\Model\Quote\Address\Total::class,
  321. ['getDiscountAmount', 'getDiscountDescription']
  322. );
  323. $totalMock->expects($this->once())->method('getDiscountAmount')->willReturn($discountAmount);
  324. $totalMock->expects($this->once())->method('getDiscountDescription')->willReturn($discountDescription);
  325. $this->assertEquals($expectedResult, $this->discount->fetch($quoteMock, $totalMock));
  326. }
  327. }