/oscar/apps/offer/tests.py

https://github.com/RuuPiE/django-oscar · Python · 343 lines · 249 code · 90 blank · 4 comment · 7 complexity · bcbc56a7820b180fc1a25eb3e2fe1cf8 MD5 · raw file

  1. from decimal import Decimal
  2. import datetime
  3. from django.test import TestCase
  4. from oscar.apps.offer.models import (Range, CountCondition, ValueCondition,
  5. CoverageCondition, ConditionalOffer,
  6. PercentageDiscountBenefit, FixedPriceBenefit,
  7. MultibuyDiscountBenefit, AbsoluteDiscountBenefit)
  8. from oscar.apps.basket.models import Basket
  9. from oscar.test.helpers import create_product
  10. class RangeTest(TestCase):
  11. def setUp(self):
  12. self.prod = create_product()
  13. def test_all_products_range(self):
  14. range = Range.objects.create(name="All products", includes_all_products=True)
  15. self.assertTrue(range.contains_product(self.prod))
  16. def test_all_products_range_with_exception(self):
  17. range = Range.objects.create(name="All products", includes_all_products=True)
  18. range.excluded_products.add(self.prod)
  19. self.assertFalse(range.contains_product(self.prod))
  20. def test_empty_list(self):
  21. range = Range.objects.create(name="All products")
  22. self.assertFalse(range.contains_product(self.prod))
  23. def test_whitelisting(self):
  24. range = Range.objects.create(name="All products")
  25. range.included_products.add(self.prod)
  26. self.assertTrue(range.contains_product(self.prod))
  27. def test_blacklisting(self):
  28. range = Range.objects.create(name="All products", includes_all_products=True)
  29. range.excluded_products.add(self.prod)
  30. self.assertFalse(range.contains_product(self.prod))
  31. def test_included_classes(self):
  32. range = Range.objects.create(name="All products", includes_all_products=False)
  33. range.classes.add(self.prod.product_class)
  34. self.assertTrue(range.contains_product(self.prod))
  35. def test_included_class_with_exception(self):
  36. range = Range.objects.create(name="All products", includes_all_products=False)
  37. range.classes.add(self.prod.product_class)
  38. range.excluded_products.add(self.prod)
  39. self.assertFalse(range.contains_product(self.prod))
  40. class OfferTest(TestCase):
  41. def setUp(self):
  42. self.range = Range.objects.create(name="All products range", includes_all_products=True)
  43. self.basket = Basket.objects.create()
  44. class CountConditionTest(OfferTest):
  45. def setUp(self):
  46. super(CountConditionTest, self).setUp()
  47. self.cond = CountCondition(range=self.range, type="Count", value=2)
  48. def test_empty_basket_fails_condition(self):
  49. self.assertFalse(self.cond.is_satisfied(self.basket))
  50. def test_matching_quantity_basket_passes_condition(self):
  51. self.basket.add_product(create_product(), 2)
  52. self.assertTrue(self.cond.is_satisfied(self.basket))
  53. def test_greater_quantity_basket_passes_condition(self):
  54. self.basket.add_product(create_product(), 3)
  55. self.assertTrue(self.cond.is_satisfied(self.basket))
  56. def test_consumption(self):
  57. self.basket.add_product(create_product(), 3)
  58. self.cond.consume_items(self.basket)
  59. self.assertEquals(1, self.basket.all_lines()[0].quantity_without_discount)
  60. def test_is_satisfied_accounts_for_consumed_items(self):
  61. self.basket.add_product(create_product(), 3)
  62. self.cond.consume_items(self.basket)
  63. self.assertFalse(self.cond.is_satisfied(self.basket))
  64. class ValueConditionTest(OfferTest):
  65. def setUp(self):
  66. super(ValueConditionTest, self).setUp()
  67. self.cond = ValueCondition(range=self.range, type="Value", value=Decimal('10.00'))
  68. self.item = create_product(price=Decimal('5.00'))
  69. self.expensive_item = create_product(price=Decimal('15.00'))
  70. def test_empty_basket_fails_condition(self):
  71. self.assertFalse(self.cond.is_satisfied(self.basket))
  72. def test_less_value_basket_fails_condition(self):
  73. self.basket.add_product(self.item, 1)
  74. self.assertFalse(self.cond.is_satisfied(self.basket))
  75. def test_matching_basket_passes_condition(self):
  76. self.basket.add_product(self.item, 2)
  77. self.assertTrue(self.cond.is_satisfied(self.basket))
  78. def test_greater_than_basket_passes_condition(self):
  79. self.basket.add_product(self.item, 3)
  80. self.assertTrue(self.cond.is_satisfied(self.basket))
  81. def test_consumption(self):
  82. self.basket.add_product(self.item, 3)
  83. self.cond.consume_items(self.basket)
  84. self.assertEquals(1, self.basket.all_lines()[0].quantity_without_discount)
  85. def test_consumption_with_high_value_product(self):
  86. self.basket.add_product(self.expensive_item, 1)
  87. self.cond.consume_items(self.basket)
  88. self.assertEquals(0, self.basket.all_lines()[0].quantity_without_discount)
  89. def test_is_consumed_respects_quantity_consumed(self):
  90. self.basket.add_product(self.expensive_item, 1)
  91. self.assertTrue(self.cond.is_satisfied(self.basket))
  92. self.cond.consume_items(self.basket)
  93. self.assertFalse(self.cond.is_satisfied(self.basket))
  94. class CoverageConditionTest(TestCase):
  95. def setUp(self):
  96. self.products = [create_product(Decimal('5.00')), create_product(Decimal('10.00'))]
  97. self.range = Range.objects.create(name="Some products")
  98. for product in self.products:
  99. self.range.included_products.add(product)
  100. self.range.included_products.add(product)
  101. self.basket = Basket.objects.create()
  102. self.cond = CoverageCondition(range=self.range, type="Coverage", value=2)
  103. def test_empty_basket_fails(self):
  104. self.assertFalse(self.cond.is_satisfied(self.basket))
  105. def test_single_item_fails(self):
  106. self.basket.add_product(self.products[0])
  107. self.assertFalse(self.cond.is_satisfied(self.basket))
  108. def test_duplicate_item_fails(self):
  109. self.basket.add_product(self.products[0])
  110. self.basket.add_product(self.products[0])
  111. self.assertFalse(self.cond.is_satisfied(self.basket))
  112. def test_covering_items_pass(self):
  113. self.basket.add_product(self.products[0])
  114. self.basket.add_product(self.products[1])
  115. self.assertTrue(self.cond.is_satisfied(self.basket))
  116. def test_covering_items_are_consumed(self):
  117. self.basket.add_product(self.products[0])
  118. self.basket.add_product(self.products[1])
  119. self.cond.consume_items(self.basket)
  120. self.assertEquals(0, self.basket.num_items_without_discount)
  121. def test_consumed_items_checks_affected_items(self):
  122. # Create new offer
  123. range = Range.objects.create(name="All products", includes_all_products=True)
  124. cond = CoverageCondition(range=range, type="Coverage", value=2)
  125. # Get 4 distinct products in the basket
  126. self.products.extend([create_product(Decimal('15.00')), create_product(Decimal('20.00'))])
  127. for product in self.products:
  128. self.basket.add_product(product)
  129. self.assertTrue(cond.is_satisfied(self.basket))
  130. cond.consume_items(self.basket)
  131. self.assertEquals(2, self.basket.num_items_without_discount)
  132. self.assertTrue(cond.is_satisfied(self.basket))
  133. cond.consume_items(self.basket)
  134. self.assertEquals(0, self.basket.num_items_without_discount)
  135. class PercentageDiscountBenefitTest(OfferTest):
  136. def setUp(self):
  137. super(PercentageDiscountBenefitTest, self).setUp()
  138. self.benefit = PercentageDiscountBenefit(range=self.range, type="PercentageDiscount", value=Decimal('15.00'))
  139. self.item = create_product(price=Decimal('5.00'))
  140. def test_no_discount_for_empty_basket(self):
  141. self.assertEquals(Decimal('0.00'), self.benefit.apply(self.basket))
  142. def test_discount_for_single_item_basket(self):
  143. self.basket.add_product(self.item, 1)
  144. self.assertEquals(Decimal('0.15') * Decimal('5.00'), self.benefit.apply(self.basket))
  145. def test_discount_for_multi_item_basket(self):
  146. self.basket.add_product(self.item, 3)
  147. self.assertEquals(Decimal('3') * Decimal('0.15') * Decimal('5.00'), self.benefit.apply(self.basket))
  148. def test_discount_for_multi_item_basket_with_max_affected_items_set(self):
  149. self.basket.add_product(self.item, 3)
  150. self.benefit.max_affected_items = 1
  151. self.assertEquals(Decimal('0.15') * Decimal('5.00'), self.benefit.apply(self.basket))
  152. def test_discount_can_only_be_applied_once(self):
  153. self.basket.add_product(self.item, 3)
  154. first_discount = self.benefit.apply(self.basket)
  155. second_discount = self.benefit.apply(self.basket)
  156. self.assertEquals(Decimal('0.00'), second_discount)
  157. def test_discount_can_be_applied_several_times_when_max_is_set(self):
  158. self.basket.add_product(self.item, 3)
  159. self.benefit.max_affected_items = 1
  160. for i in range(1, 4):
  161. self.assertTrue(self.benefit.apply(self.basket) > 0)
  162. class AbsoluteDiscountBenefitTest(OfferTest):
  163. def setUp(self):
  164. super(AbsoluteDiscountBenefitTest, self).setUp()
  165. self.benefit = AbsoluteDiscountBenefit(range=self.range, type="Absolute", value=Decimal('10.00'))
  166. self.item = create_product(price=Decimal('5.00'))
  167. def test_no_discount_for_empty_basket(self):
  168. self.assertEquals(Decimal('0.00'), self.benefit.apply(self.basket))
  169. def test_discount_for_single_item_basket(self):
  170. self.basket.add_product(self.item, 1)
  171. self.assertEquals(Decimal('5.00'), self.benefit.apply(self.basket))
  172. def test_discount_for_multi_item_basket(self):
  173. self.basket.add_product(self.item, 3)
  174. self.assertEquals(Decimal('10.00'), self.benefit.apply(self.basket))
  175. def test_discount_for_multi_item_basket_with_max_affected_items_set(self):
  176. self.basket.add_product(self.item, 3)
  177. self.benefit.max_affected_items = 1
  178. self.assertEquals(Decimal('5.00'), self.benefit.apply(self.basket))
  179. def test_discount_can_only_be_applied_once(self):
  180. # Add 3 items to make total 15.00
  181. self.basket.add_product(self.item, 3)
  182. first_discount = self.benefit.apply(self.basket)
  183. self.assertEquals(Decimal('10.00'), first_discount)
  184. second_discount = self.benefit.apply(self.basket)
  185. self.assertEquals(Decimal('5.00'), second_discount)
  186. class MultibuyDiscountBenefitTest(OfferTest):
  187. def setUp(self):
  188. super(MultibuyDiscountBenefitTest, self).setUp()
  189. self.benefit = MultibuyDiscountBenefit(range=self.range, type="Multibuy", value=1)
  190. self.item = create_product(price=Decimal('5.00'))
  191. def test_no_discount_for_empty_basket(self):
  192. self.assertEquals(Decimal('0.00'), self.benefit.apply(self.basket))
  193. def test_discount_for_single_item_basket(self):
  194. self.basket.add_product(self.item, 1)
  195. self.assertEquals(Decimal('5.00'), self.benefit.apply(self.basket))
  196. def test_discount_for_multi_item_basket(self):
  197. self.basket.add_product(self.item, 3)
  198. self.assertEquals(Decimal('5.00'), self.benefit.apply(self.basket))
  199. def test_discount_does_not_consume_item_if_in_condition_range(self):
  200. self.basket.add_product(self.item, 1)
  201. first_discount = self.benefit.apply(self.basket)
  202. self.assertEquals(Decimal('5.00'), first_discount)
  203. second_discount = self.benefit.apply(self.basket)
  204. self.assertEquals(Decimal('5.00'), second_discount)
  205. def test_product_does_consume_item_if_not_in_condition_range(self):
  206. # Set up condition using a different range from benefit
  207. range = Range.objects.create(name="Small range")
  208. other_product = create_product(price=Decimal('15.00'))
  209. range.included_products.add(other_product)
  210. cond = ValueCondition(range=range, type="Value", value=Decimal('10.00'))
  211. self.basket.add_product(self.item, 1)
  212. self.benefit.apply(self.basket, cond)
  213. line = self.basket.all_lines()[0]
  214. self.assertEqual(line.quantity_without_discount, 0)
  215. class FixedPriceBenefitTest(OfferTest):
  216. def setUp(self):
  217. super(FixedPriceBenefitTest, self).setUp()
  218. self.benefit = FixedPriceBenefit(range=self.range, type="FixedPrice", value=Decimal('10.00'))
  219. def test_correct_discount_is_returned(self):
  220. products = [create_product(Decimal('8.00')), create_product(Decimal('4.00'))]
  221. range = Range.objects.create(name="Dummy range")
  222. for product in products:
  223. range.included_products.add(product)
  224. range.included_products.add(product)
  225. basket = Basket.objects.create()
  226. [basket.add_product(p) for p in products]
  227. condition = CoverageCondition(range=range, type="Coverage", value=2)
  228. discount = self.benefit.apply(basket, condition)
  229. self.assertEquals(Decimal('2.00'), discount)
  230. def test_no_discount_is_returned_when_value_is_greater_than_product_total(self):
  231. products = [create_product(Decimal('4.00')), create_product(Decimal('4.00'))]
  232. range = Range.objects.create(name="Dummy range")
  233. for product in products:
  234. range.included_products.add(product)
  235. range.included_products.add(product)
  236. basket = Basket.objects.create()
  237. [basket.add_product(p) for p in products]
  238. condition = CoverageCondition(range=range, type="Coverage", value=2)
  239. discount = self.benefit.apply(basket, condition)
  240. self.assertEquals(Decimal('0.00'), discount)
  241. class ConditionalOfferTest(TestCase):
  242. def test_is_active(self):
  243. start = datetime.date(2011, 01, 01)
  244. test = datetime.date(2011, 01, 10)
  245. end = datetime.date(2011, 02, 01)
  246. offer = ConditionalOffer(start_date=start, end_date=end)
  247. self.assertTrue(offer.is_active(test))
  248. def test_is_inactive(self):
  249. start = datetime.date(2011, 01, 01)
  250. test = datetime.date(2011, 03, 10)
  251. end = datetime.date(2011, 02, 01)
  252. offer = ConditionalOffer(start_date=start, end_date=end)
  253. self.assertFalse(offer.is_active(test))