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.

absolute_benefit_tests.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. from decimal import Decimal as D
  2. from django.core import exceptions
  3. from django.test import TestCase
  4. import mock
  5. from oscar.apps.offer import models
  6. from oscar.apps.offer.utils import Applicator
  7. from oscar.test.basket import add_product, add_products
  8. from oscar.test import factories
  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.offer.applies_to_tax_exclusive_prices = False
  54. self.benefit = models.AbsoluteDiscountBenefit.objects.create(
  55. range=range,
  56. type=models.Benefit.FIXED,
  57. value=D('3.00'))
  58. self.basket = factories.create_basket(empty=True)
  59. def test_applies_correctly_to_empty_basket(self):
  60. result = self.benefit.apply(self.basket, self.condition, self.offer)
  61. self.assertEqual(D('0.00'), result.discount)
  62. self.assertEqual(0, self.basket.num_items_with_discount)
  63. self.assertEqual(0, self.basket.num_items_without_discount)
  64. def test_applies_correctly_to_basket_which_matches_condition_with_one_line(self):
  65. add_product(self.basket, price=D('12.00'), quantity=2)
  66. result = self.benefit.apply(self.basket, self.condition, self.offer)
  67. self.assertEqual(D('3.00'), result.discount)
  68. self.assertEqual(2, self.basket.num_items_with_discount)
  69. self.assertEqual(0, self.basket.num_items_without_discount)
  70. # Check the discount is applied equally to each item in the line
  71. line = self.basket.all_lines()[0]
  72. prices = line.get_price_breakdown()
  73. self.assertEqual(1, len(prices))
  74. self.assertEqual(D('10.50'), prices[0][0])
  75. def test_applies_correctly_to_basket_which_matches_condition_with_multiple_lines(self):
  76. # Use a basket with 2 lines
  77. add_products(self.basket, [
  78. (D('12.00'), 1), (D('12.00'), 1)])
  79. result = self.benefit.apply(self.basket, self.condition, self.offer)
  80. self.assertTrue(result.is_successful)
  81. self.assertFalse(result.is_final)
  82. self.assertEqual(D('3.00'), result.discount)
  83. self.assertEqual(2, self.basket.num_items_with_discount)
  84. self.assertEqual(0, self.basket.num_items_without_discount)
  85. # Check the discount is applied equally to each line
  86. for line in self.basket.all_lines():
  87. self.assertEqual(D('1.50'), line.discount_value)
  88. def test_applies_correctly_to_basket_which_matches_condition_with_multiple_lines_and_lower_total_value(self):
  89. # Use a basket with 2 lines
  90. add_products(self.basket, [
  91. (D('1.00'), 1), (D('1.50'), 1)])
  92. result = self.benefit.apply(self.basket, self.condition, self.offer)
  93. self.assertTrue(result.is_successful)
  94. self.assertFalse(result.is_final)
  95. self.assertEqual(D('2.50'), result.discount)
  96. self.assertEqual(2, self.basket.num_items_with_discount)
  97. self.assertEqual(0, self.basket.num_items_without_discount)
  98. def test_applies_correctly_to_basket_which_exceeds_condition(self):
  99. add_products(self.basket, [
  100. (D('12.00'), 2), (D('10.00'), 2)])
  101. result = self.benefit.apply(self.basket, self.condition, self.offer)
  102. self.assertEqual(D('3.00'), result.discount)
  103. self.assertEqual(4, self.basket.num_items_with_discount)
  104. self.assertEqual(0, self.basket.num_items_without_discount)
  105. def test_applies_correctly_to_basket_which_exceeds_condition_with_smaller_prices_than_discount(self):
  106. add_products(self.basket, [
  107. (D('2.00'), 2), (D('4.00'), 2)])
  108. result = self.benefit.apply(self.basket, self.condition, self.offer)
  109. self.assertEqual(D('3.00'), result.discount)
  110. self.assertEqual(4, self.basket.num_items_with_discount)
  111. self.assertEqual(0, self.basket.num_items_without_discount)
  112. def test_applies_correctly_to_basket_which_exceeds_condition_with_smaller_prices_than_discount_and_higher_prices_first(self):
  113. add_products(self.basket, [
  114. (D('2.00'), 2), (D('4.00'), 2)])
  115. result = self.benefit.apply(self.basket, self.condition, self.offer)
  116. self.assertEqual(D('3.00'), result.discount)
  117. self.assertEqual(4, self.basket.num_items_with_discount)
  118. self.assertEqual(0, self.basket.num_items_without_discount)
  119. class TestAnAbsoluteDiscount(TestCase):
  120. def setUp(self):
  121. range = models.Range.objects.create(
  122. name="All products", includes_all_products=True)
  123. self.condition = models.CountCondition.objects.create(
  124. range=range,
  125. type=models.Condition.COUNT,
  126. value=2)
  127. self.benefit = models.AbsoluteDiscountBenefit.objects.create(
  128. range=range,
  129. type=models.Benefit.FIXED,
  130. value=D('4.00'))
  131. self.offer = mock.Mock()
  132. self.offer.applies_to_tax_exclusive_prices = False
  133. self.basket = factories.create_basket(empty=True)
  134. def test_applies_correctly_when_discounts_need_rounding(self):
  135. # Split discount across 3 lines
  136. for price in [D('2.00'), D('2.00'), D('2.00')]:
  137. add_product(self.basket, price)
  138. result = self.benefit.apply(self.basket, self.condition, self.offer)
  139. self.assertEqual(D('4.00'), result.discount)
  140. # Check the discount is applied equally to each line
  141. line_discounts = [line.discount_value for line in self.basket.all_lines()]
  142. self.assertEqual(len(line_discounts), 3)
  143. for i, v in enumerate([D('1.33'), D('1.33'), D('1.34')]):
  144. self.assertEqual(line_discounts[i], v)
  145. class TestAnAbsoluteDiscountWithMaxItemsSetAppliedWithCountCondition(TestCase):
  146. def setUp(self):
  147. range = models.Range.objects.create(
  148. name="All products", includes_all_products=True)
  149. self.condition = models.CountCondition.objects.create(
  150. range=range,
  151. type=models.Condition.COUNT,
  152. value=2)
  153. self.benefit = models.AbsoluteDiscountBenefit.objects.create(
  154. range=range,
  155. type=models.Benefit.FIXED,
  156. value=D('3.00'),
  157. max_affected_items=1)
  158. self.offer = mock.Mock()
  159. self.offer.applies_to_tax_exclusive_prices = False
  160. self.basket = factories.create_basket(empty=True)
  161. def test_applies_correctly_to_empty_basket(self):
  162. result = self.benefit.apply(self.basket, self.condition, self.offer)
  163. self.assertEqual(D('0.00'), result.discount)
  164. self.assertEqual(0, self.basket.num_items_with_discount)
  165. self.assertEqual(0, self.basket.num_items_without_discount)
  166. def test_applies_correctly_to_basket_which_matches_condition(self):
  167. add_product(self.basket, D('12.00'), 2)
  168. result = self.benefit.apply(self.basket, self.condition, self.offer)
  169. self.assertEqual(D('3.00'), result.discount)
  170. self.assertEqual(2, self.basket.num_items_with_discount)
  171. self.assertEqual(0, self.basket.num_items_without_discount)
  172. def test_applies_correctly_to_basket_which_exceeds_condition(self):
  173. add_products(self.basket, [(D('12.00'), 2), (D('10.00'), 2)])
  174. result = self.benefit.apply(self.basket, self.condition, self.offer)
  175. self.assertEqual(D('3.00'), result.discount)
  176. self.assertEqual(2, self.basket.num_items_with_discount)
  177. self.assertEqual(2, self.basket.num_items_without_discount)
  178. def test_applies_correctly_to_basket_which_exceeds_condition_but_with_smaller_prices_than_discount(self):
  179. add_products(self.basket, [(D('2.00'), 2), (D('1.00'), 2)])
  180. result = self.benefit.apply(self.basket, self.condition, self.offer)
  181. self.assertEqual(D('1.00'), result.discount)
  182. self.assertEqual(2, self.basket.num_items_with_discount)
  183. self.assertEqual(2, self.basket.num_items_without_discount)
  184. class TestAnAbsoluteDiscountAppliedWithValueCondition(TestCase):
  185. def setUp(self):
  186. range = models.Range.objects.create(
  187. name="All products", includes_all_products=True)
  188. self.condition = models.ValueCondition.objects.create(
  189. range=range,
  190. type=models.Condition.VALUE,
  191. value=D('10.00'))
  192. self.benefit = models.AbsoluteDiscountBenefit.objects.create(
  193. range=range,
  194. type=models.Benefit.FIXED,
  195. value=D('3.00'))
  196. self.offer = mock.Mock()
  197. self.offer.applies_to_tax_exclusive_prices = False
  198. self.basket = factories.create_basket(empty=True)
  199. def test_applies_correctly_to_empty_basket(self):
  200. result = self.benefit.apply(self.basket, self.condition, self.offer)
  201. self.assertEqual(D('0.00'), result.discount)
  202. self.assertEqual(0, self.basket.num_items_with_discount)
  203. self.assertEqual(0, self.basket.num_items_without_discount)
  204. def test_applies_correctly_to_single_item_basket_which_matches_condition(self):
  205. add_products(self.basket, [(D('10.00'), 1)])
  206. result = self.benefit.apply(self.basket, self.condition, self.offer)
  207. self.assertEqual(D('3.00'), result.discount)
  208. self.assertEqual(1, self.basket.num_items_with_discount)
  209. self.assertEqual(0, self.basket.num_items_without_discount)
  210. def test_applies_correctly_to_multi_item_basket_which_matches_condition(self):
  211. add_products(self.basket, [(D('5.00'), 2)])
  212. result = self.benefit.apply(self.basket, self.condition, self.offer)
  213. self.assertEqual(D('3.00'), result.discount)
  214. self.assertEqual(2, self.basket.num_items_with_discount)
  215. self.assertEqual(0, self.basket.num_items_without_discount)
  216. def test_applies_correctly_to_multi_item_basket_which_exceeds_condition(self):
  217. add_products(self.basket, [(D('4.00'), 3)])
  218. result = self.benefit.apply(self.basket, self.condition, self.offer)
  219. self.assertEqual(D('3.00'), result.discount)
  220. self.assertEqual(3, self.basket.num_items_with_discount)
  221. self.assertEqual(0, self.basket.num_items_without_discount)
  222. def test_applies_correctly_to_multi_item_basket_which_exceeds_condition_but_matches_boundary(self):
  223. add_products(self.basket, [(D('5.00'), 3)])
  224. result = self.benefit.apply(self.basket, self.condition, self.offer)
  225. self.assertEqual(D('3.00'), result.discount)
  226. self.assertEqual(3, self.basket.num_items_with_discount)
  227. self.assertEqual(0, self.basket.num_items_without_discount)
  228. class TestAnAbsoluteDiscountWithMaxItemsSetAppliedWithValueCondition(TestCase):
  229. def setUp(self):
  230. range = models.Range.objects.create(
  231. name="All products", includes_all_products=True)
  232. self.condition = models.ValueCondition.objects.create(
  233. range=range,
  234. type=models.Condition.VALUE,
  235. value=D('10.00'))
  236. self.benefit = models.AbsoluteDiscountBenefit.objects.create(
  237. range=range,
  238. type=models.Benefit.FIXED,
  239. value=D('3.00'),
  240. max_affected_items=1)
  241. self.offer = mock.Mock()
  242. self.offer.applies_to_tax_exclusive_prices = False
  243. self.basket = factories.create_basket(empty=True)
  244. def test_applies_correctly_to_empty_basket(self):
  245. result = self.benefit.apply(self.basket, self.condition, self.offer)
  246. self.assertEqual(D('0.00'), result.discount)
  247. self.assertEqual(0, self.basket.num_items_with_discount)
  248. self.assertEqual(0, self.basket.num_items_without_discount)
  249. def test_applies_correctly_to_single_item_basket_which_matches_condition(self):
  250. add_products(self.basket, [(D('10.00'), 1)])
  251. result = self.benefit.apply(self.basket, self.condition, self.offer)
  252. self.assertEqual(D('3.00'), result.discount)
  253. self.assertEqual(1, self.basket.num_items_with_discount)
  254. self.assertEqual(0, self.basket.num_items_without_discount)
  255. def test_applies_correctly_to_multi_item_basket_which_matches_condition(self):
  256. add_products(self.basket, [(D('5.00'), 2)])
  257. result = self.benefit.apply(self.basket, self.condition, self.offer)
  258. self.assertEqual(D('3.00'), result.discount)
  259. self.assertEqual(2, self.basket.num_items_with_discount)
  260. self.assertEqual(0, self.basket.num_items_without_discount)
  261. def test_applies_correctly_to_multi_item_basket_which_exceeds_condition(self):
  262. add_products(self.basket, [(D('4.00'), 3)])
  263. result = self.benefit.apply(self.basket, self.condition, self.offer)
  264. self.assertEqual(D('3.00'), result.discount)
  265. self.assertEqual(3, self.basket.num_items_with_discount)
  266. self.assertEqual(0, self.basket.num_items_without_discount)
  267. def test_applies_correctly_to_multi_item_basket_which_exceeds_condition_but_matches_boundary(self):
  268. add_products(self.basket, [(D('5.00'), 3)])
  269. result = self.benefit.apply(self.basket, self.condition, self.offer)
  270. self.assertEqual(D('3.00'), result.discount)
  271. self.assertEqual(2, self.basket.num_items_with_discount)
  272. self.assertEqual(1, self.basket.num_items_without_discount)
  273. def test_applies_correctly_to_multi_item_basket_which_matches_condition_but_with_lower_prices_than_discount(self):
  274. add_products(self.basket, [(D('2.00'), 6)])
  275. result = self.benefit.apply(self.basket, self.condition, self.offer)
  276. self.assertEqual(D('2.00'), result.discount)
  277. self.assertEqual(5, self.basket.num_items_with_discount)
  278. self.assertEqual(1, self.basket.num_items_without_discount)
  279. class TestAnAbsoluteDiscountBenefit(TestCase):
  280. def test_requires_a_benefit_value(self):
  281. rng = models.Range.objects.create(
  282. name="", includes_all_products=True)
  283. benefit = models.Benefit.objects.create(
  284. type=models.Benefit.FIXED, range=rng
  285. )
  286. with self.assertRaises(exceptions.ValidationError):
  287. benefit.clean()