You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_absolute_benefit.py 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. from decimal import Decimal as D
  2. from unittest import mock
  3. from django.core.exceptions import ValidationError
  4. from django.test import TestCase
  5. from oscar.apps.offer import models
  6. from oscar.apps.offer.utils import Applicator
  7. from oscar.test import factories
  8. from oscar.test.basket import add_product, add_products
  9. class TestAnAbsoluteDiscountAppliedWithCountConditionOnDifferentRange(TestCase):
  10. def setUp(self):
  11. self.condition_product = factories.ProductFactory()
  12. condition_range = factories.RangeFactory()
  13. condition_range.add_product(self.condition_product)
  14. self.condition = models.CountCondition.objects.create(
  15. range=condition_range,
  16. type=models.Condition.COUNT,
  17. value=2)
  18. self.benefit_product = factories.ProductFactory()
  19. benefit_range = factories.RangeFactory()
  20. benefit_range.add_product(self.benefit_product)
  21. self.benefit = models.AbsoluteDiscountBenefit.objects.create(
  22. range=benefit_range,
  23. type=models.Benefit.FIXED,
  24. value=D('3.00'))
  25. self.offer = models.ConditionalOffer(
  26. id=1, condition=self.condition, benefit=self.benefit)
  27. self.basket = factories.create_basket(empty=True)
  28. self.applicator = Applicator()
  29. def test_succcessful_application_consumes_correctly(self):
  30. add_product(self.basket, product=self.condition_product, quantity=2)
  31. add_product(self.basket, product=self.benefit_product, quantity=1)
  32. self.applicator.apply_offers(self.basket, [self.offer])
  33. discounts = self.basket.offer_applications.offer_discounts
  34. self.assertEqual(len(discounts), 1)
  35. self.assertEqual(discounts[0]['freq'], 1)
  36. def test_condition_is_consumed_correctly(self):
  37. # Testing an error case reported on the mailing list
  38. add_product(self.basket, product=self.condition_product, quantity=3)
  39. add_product(self.basket, product=self.benefit_product, quantity=2)
  40. self.applicator.apply_offers(self.basket, [self.offer])
  41. discounts = self.basket.offer_applications.offer_discounts
  42. self.assertEqual(len(discounts), 1)
  43. self.assertEqual(discounts[0]['freq'], 1)
  44. class TestAnAbsoluteDiscountAppliedWithCountCondition(TestCase):
  45. def setUp(self):
  46. range = models.Range.objects.create(
  47. name="All products", includes_all_products=True)
  48. self.condition = models.CountCondition.objects.create(
  49. range=range,
  50. type=models.Condition.COUNT,
  51. value=2)
  52. self.offer = mock.Mock()
  53. self.benefit = models.AbsoluteDiscountBenefit.objects.create(
  54. range=range,
  55. type=models.Benefit.FIXED,
  56. value=D('3.00'))
  57. self.basket = factories.create_basket(empty=True)
  58. def test_applies_correctly_to_empty_basket(self):
  59. result = self.benefit.apply(self.basket, self.condition, self.offer)
  60. self.assertEqual(D('0.00'), result.discount)
  61. self.assertEqual(0, self.basket.num_items_with_discount)
  62. self.assertEqual(0, self.basket.num_items_without_discount)
  63. def test_applies_correctly_to_basket_which_matches_condition_with_one_line(self):
  64. add_product(self.basket, price=D('12.00'), quantity=2)
  65. result = self.benefit.apply(self.basket, self.condition, self.offer)
  66. self.assertEqual(D('3.00'), result.discount)
  67. self.assertEqual(2, self.basket.num_items_with_discount)
  68. self.assertEqual(0, self.basket.num_items_without_discount)
  69. # Check the discount is applied equally to each item in the line
  70. line = self.basket.all_lines()[0]
  71. prices = line.get_price_breakdown()
  72. self.assertEqual(1, len(prices))
  73. self.assertEqual(D('10.50'), prices[0][0])
  74. def test_applies_correctly_to_basket_which_matches_condition_with_multiple_lines(self):
  75. # Use a basket with 2 lines
  76. add_products(self.basket, [
  77. (D('12.00'), 1), (D('12.00'), 1)])
  78. result = self.benefit.apply(self.basket, self.condition, self.offer)
  79. self.assertTrue(result.is_successful)
  80. self.assertFalse(result.is_final)
  81. self.assertEqual(D('3.00'), result.discount)
  82. self.assertEqual(2, self.basket.num_items_with_discount)
  83. self.assertEqual(0, self.basket.num_items_without_discount)
  84. # Check the discount is applied equally to each line
  85. for line in self.basket.all_lines():
  86. self.assertEqual(D('1.50'), line.discount_value)
  87. def test_applies_correctly_to_basket_which_matches_condition_with_multiple_lines_and_lower_total_value(self):
  88. # Use a basket with 2 lines
  89. add_products(self.basket, [
  90. (D('1.00'), 1), (D('1.50'), 1)])
  91. result = self.benefit.apply(self.basket, self.condition, self.offer)
  92. self.assertTrue(result.is_successful)
  93. self.assertFalse(result.is_final)
  94. self.assertEqual(D('2.50'), result.discount)
  95. self.assertEqual(2, self.basket.num_items_with_discount)
  96. self.assertEqual(0, self.basket.num_items_without_discount)
  97. def test_applies_correctly_to_basket_which_exceeds_condition(self):
  98. add_products(self.basket, [
  99. (D('12.00'), 2), (D('10.00'), 2)])
  100. result = self.benefit.apply(self.basket, self.condition, self.offer)
  101. self.assertEqual(D('3.00'), result.discount)
  102. self.assertEqual(4, self.basket.num_items_with_discount)
  103. self.assertEqual(0, self.basket.num_items_without_discount)
  104. def test_applies_correctly_to_basket_which_exceeds_condition_with_smaller_prices_than_discount(self):
  105. add_products(self.basket, [
  106. (D('2.00'), 2), (D('4.00'), 2)])
  107. result = self.benefit.apply(self.basket, self.condition, self.offer)
  108. self.assertEqual(D('3.00'), result.discount)
  109. self.assertEqual(4, self.basket.num_items_with_discount)
  110. self.assertEqual(0, self.basket.num_items_without_discount)
  111. def test_applies_basket_exceeding_condition_smaller_prices_than_discount_higher_prices_first(self):
  112. add_products(self.basket, [
  113. (D('2.00'), 2), (D('4.00'), 2)])
  114. result = self.benefit.apply(self.basket, self.condition, self.offer)
  115. self.assertEqual(D('3.00'), result.discount)
  116. self.assertEqual(4, self.basket.num_items_with_discount)
  117. self.assertEqual(0, self.basket.num_items_without_discount)
  118. class TestAnAbsoluteDiscount(TestCase):
  119. def setUp(self):
  120. range = models.Range.objects.create(
  121. name="All products", includes_all_products=True)
  122. self.condition = models.CountCondition.objects.create(
  123. range=range,
  124. type=models.Condition.COUNT,
  125. value=2)
  126. self.benefit = models.AbsoluteDiscountBenefit.objects.create(
  127. range=range,
  128. type=models.Benefit.FIXED,
  129. value=D('4.00'))
  130. self.offer = mock.Mock()
  131. self.basket = factories.create_basket(empty=True)
  132. def test_applies_correctly_when_discounts_need_rounding(self):
  133. # Split discount across 3 lines
  134. for price in [D('2.00'), D('2.00'), D('2.00')]:
  135. add_product(self.basket, price)
  136. result = self.benefit.apply(self.basket, self.condition, self.offer)
  137. self.assertEqual(D('4.00'), result.discount)
  138. # Check the discount is applied equally to each line
  139. line_discounts = [line.discount_value for line in self.basket.all_lines()]
  140. self.assertEqual(len(line_discounts), 3)
  141. for i, v in enumerate([D('1.33'), D('1.33'), D('1.34')]):
  142. self.assertEqual(line_discounts[i], v)
  143. class TestAnAbsoluteDiscountWithMaxItemsSetAppliedWithCountCondition(TestCase):
  144. def setUp(self):
  145. range = models.Range.objects.create(
  146. name="All products", includes_all_products=True)
  147. self.condition = models.CountCondition.objects.create(
  148. range=range,
  149. type=models.Condition.COUNT,
  150. value=2)
  151. self.benefit = models.AbsoluteDiscountBenefit.objects.create(
  152. range=range,
  153. type=models.Benefit.FIXED,
  154. value=D('3.00'),
  155. max_affected_items=1)
  156. self.offer = mock.Mock()
  157. self.basket = factories.create_basket(empty=True)
  158. def test_applies_correctly_to_empty_basket(self):
  159. result = self.benefit.apply(self.basket, self.condition, self.offer)
  160. self.assertEqual(D('0.00'), result.discount)
  161. self.assertEqual(0, self.basket.num_items_with_discount)
  162. self.assertEqual(0, self.basket.num_items_without_discount)
  163. def test_applies_correctly_to_basket_which_matches_condition(self):
  164. add_product(self.basket, D('12.00'), 2)
  165. result = self.benefit.apply(self.basket, self.condition, self.offer)
  166. self.assertEqual(D('3.00'), result.discount)
  167. self.assertEqual(1, self.basket.num_items_with_discount)
  168. self.assertEqual(1, self.basket.num_items_without_discount)
  169. def test_applies_correctly_to_basket_which_exceeds_condition(self):
  170. add_products(self.basket, [(D('12.00'), 2), (D('10.00'), 2)])
  171. result = self.benefit.apply(self.basket, self.condition, self.offer)
  172. self.assertEqual(D('3.00'), result.discount)
  173. self.assertEqual(1, self.basket.num_items_with_discount)
  174. self.assertEqual(3, self.basket.num_items_without_discount)
  175. def test_applies_correctly_to_basket_which_exceeds_condition_but_with_smaller_prices_than_discount(self):
  176. add_products(self.basket, [(D('2.00'), 2), (D('1.00'), 2)])
  177. result = self.benefit.apply(self.basket, self.condition, self.offer)
  178. self.assertEqual(D('1.00'), result.discount)
  179. self.assertEqual(1, self.basket.num_items_with_discount)
  180. self.assertEqual(3, self.basket.num_items_without_discount)
  181. class TestAnAbsoluteDiscountAppliedWithValueCondition(TestCase):
  182. def setUp(self):
  183. range = models.Range.objects.create(
  184. name="All products", includes_all_products=True)
  185. self.condition = models.ValueCondition.objects.create(
  186. range=range,
  187. type=models.Condition.VALUE,
  188. value=D('10.00'))
  189. self.benefit = models.AbsoluteDiscountBenefit.objects.create(
  190. range=range,
  191. type=models.Benefit.FIXED,
  192. value=D('3.00'))
  193. self.offer = mock.Mock()
  194. self.basket = factories.create_basket(empty=True)
  195. def test_applies_correctly_to_empty_basket(self):
  196. result = self.benefit.apply(self.basket, self.condition, self.offer)
  197. self.assertEqual(D('0.00'), result.discount)
  198. self.assertEqual(0, self.basket.num_items_with_discount)
  199. self.assertEqual(0, self.basket.num_items_without_discount)
  200. def test_applies_correctly_to_single_item_basket_which_matches_condition(self):
  201. add_products(self.basket, [(D('10.00'), 1)])
  202. result = self.benefit.apply(self.basket, self.condition, self.offer)
  203. self.assertEqual(D('3.00'), result.discount)
  204. self.assertEqual(1, self.basket.num_items_with_discount)
  205. self.assertEqual(0, self.basket.num_items_without_discount)
  206. def test_applies_correctly_to_multi_item_basket_which_matches_condition(self):
  207. add_products(self.basket, [(D('5.00'), 2)])
  208. result = self.benefit.apply(self.basket, self.condition, self.offer)
  209. self.assertEqual(D('3.00'), result.discount)
  210. self.assertEqual(2, self.basket.num_items_with_discount)
  211. self.assertEqual(0, self.basket.num_items_without_discount)
  212. def test_applies_correctly_to_multi_item_basket_which_exceeds_condition(self):
  213. add_products(self.basket, [(D('4.00'), 3)])
  214. result = self.benefit.apply(self.basket, self.condition, self.offer)
  215. self.assertEqual(D('3.00'), result.discount)
  216. self.assertEqual(3, self.basket.num_items_with_discount)
  217. self.assertEqual(0, self.basket.num_items_without_discount)
  218. def test_applies_correctly_to_multi_item_basket_which_exceeds_condition_but_matches_boundary(self):
  219. add_products(self.basket, [(D('5.00'), 3)])
  220. result = self.benefit.apply(self.basket, self.condition, self.offer)
  221. self.assertEqual(D('3.00'), result.discount)
  222. self.assertEqual(3, self.basket.num_items_with_discount)
  223. self.assertEqual(0, self.basket.num_items_without_discount)
  224. class TestAnAbsoluteDiscountWithMaxItemsSetAppliedWithValueCondition(TestCase):
  225. def setUp(self):
  226. range = models.Range.objects.create(
  227. name="All products", includes_all_products=True)
  228. self.condition = models.ValueCondition.objects.create(
  229. range=range,
  230. type=models.Condition.VALUE,
  231. value=D('10.00'))
  232. self.benefit = models.AbsoluteDiscountBenefit.objects.create(
  233. range=range,
  234. type=models.Benefit.FIXED,
  235. value=D('3.00'),
  236. max_affected_items=1)
  237. self.offer = mock.Mock()
  238. self.basket = factories.create_basket(empty=True)
  239. def test_applies_correctly_to_empty_basket(self):
  240. result = self.benefit.apply(self.basket, self.condition, self.offer)
  241. self.assertEqual(D('0.00'), result.discount)
  242. self.assertEqual(0, self.basket.num_items_with_discount)
  243. self.assertEqual(0, self.basket.num_items_without_discount)
  244. def test_applies_correctly_to_single_item_basket_which_matches_condition(self):
  245. add_products(self.basket, [(D('10.00'), 1)])
  246. result = self.benefit.apply(self.basket, self.condition, self.offer)
  247. self.assertEqual(D('3.00'), result.discount)
  248. self.assertEqual(1, self.basket.num_items_with_discount)
  249. self.assertEqual(0, self.basket.num_items_without_discount)
  250. def test_applies_correctly_to_multi_item_basket_which_matches_condition(self):
  251. add_products(self.basket, [(D('5.00'), 2)])
  252. result = self.benefit.apply(self.basket, self.condition, self.offer)
  253. self.assertEqual(D('3.00'), result.discount)
  254. self.assertEqual(1, self.basket.num_items_with_discount)
  255. self.assertEqual(1, self.basket.num_items_without_discount)
  256. def test_applies_correctly_to_multi_item_basket_which_exceeds_condition(self):
  257. add_products(self.basket, [(D('4.00'), 3)])
  258. result = self.benefit.apply(self.basket, self.condition, self.offer)
  259. self.assertEqual(D('3.00'), result.discount)
  260. self.assertEqual(1, self.basket.num_items_with_discount)
  261. self.assertEqual(2, self.basket.num_items_without_discount)
  262. def test_applies_correctly_to_multi_item_basket_which_exceeds_condition_but_matches_boundary(self):
  263. add_products(self.basket, [(D('5.00'), 3)])
  264. result = self.benefit.apply(self.basket, self.condition, self.offer)
  265. self.assertEqual(D('3.00'), result.discount)
  266. self.assertEqual(1, self.basket.num_items_with_discount)
  267. self.assertEqual(2, self.basket.num_items_without_discount)
  268. def test_applies_correctly_to_multi_item_basket_which_matches_condition_but_with_lower_prices_than_discount(self):
  269. add_products(self.basket, [(D('2.00'), 6)])
  270. result = self.benefit.apply(self.basket, self.condition, self.offer)
  271. self.assertEqual(D('2.00'), result.discount)
  272. self.assertEqual(1, self.basket.num_items_with_discount)
  273. self.assertEqual(5, self.basket.num_items_without_discount)
  274. class TestAnAbsoluteDiscountBenefit(TestCase):
  275. def test_requires_a_benefit_value(self):
  276. rng = models.Range.objects.create(
  277. name="", includes_all_products=True)
  278. benefit = models.Benefit(
  279. type=models.Benefit.FIXED, range=rng
  280. )
  281. with self.assertRaises(ValidationError):
  282. benefit.clean()
  283. def test_requires_a_range(self):
  284. benefit = models.Benefit(
  285. type=models.Benefit.FIXED, value=10
  286. )
  287. with self.assertRaises(ValidationError):
  288. benefit.clean()
  289. def test_non_negative_basket_lines_values(self):
  290. # absolute benefit is larger than the line price
  291. rng = models.Range.objects.create(
  292. name="", includes_all_products=True)
  293. benefit1 = models.Benefit.objects.create(
  294. type=models.Benefit.FIXED, range=rng, value=D('100')
  295. )
  296. benefit2 = models.Benefit.objects.create(
  297. type=models.Benefit.FIXED, range=rng, value=D('100')
  298. )
  299. condition = models.ValueCondition.objects.create(
  300. range=rng,
  301. type=models.Condition.VALUE,
  302. value=D('10'))
  303. models.ConditionalOffer.objects.create(
  304. name='offer1',
  305. offer_type=models.ConditionalOffer.SITE,
  306. benefit=benefit1,
  307. condition=condition,
  308. exclusive=False
  309. )
  310. models.ConditionalOffer.objects.create(
  311. name='offer2',
  312. offer_type=models.ConditionalOffer.SITE,
  313. benefit=benefit2,
  314. condition=condition,
  315. exclusive=False
  316. )
  317. basket = factories.create_basket(empty=True)
  318. add_products(basket, [(D('20'), 1)])
  319. Applicator().apply(basket)
  320. assert len(basket.offer_applications) == 2
  321. line = basket.all_lines().first()
  322. assert line.line_price_excl_tax_incl_discounts == D(0)
  323. assert line.line_price_incl_tax_incl_discounts == D(0)
  324. assert basket.total_incl_tax == 0