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_condition.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. import pytest
  2. from decimal import Decimal as D
  3. import mock
  4. import six
  5. from django.test import TestCase
  6. from oscar.apps.basket.models import Basket
  7. from oscar.apps.offer import custom, models
  8. from oscar.test import factories
  9. from oscar.test.basket import add_product
  10. from tests._site.model_tests_app.models import BasketOwnerCalledBarry
  11. class TestCountCondition(TestCase):
  12. def setUp(self):
  13. self.range = models.Range.objects.create(
  14. name="All products range", includes_all_products=True)
  15. self.basket = factories.create_basket(empty=True)
  16. self.condition = models.CountCondition(
  17. range=self.range, type="Count", value=2)
  18. self.offer = mock.Mock()
  19. def test_is_not_satisfied_by_empty_basket(self):
  20. self.assertFalse(self.condition.is_satisfied(self.offer, self.basket))
  21. def test_not_discountable_product_fails_condition(self):
  22. prod1, prod2 = factories.create_product(), factories.create_product()
  23. prod2.is_discountable = False
  24. prod2.save()
  25. add_product(self.basket, product=prod1)
  26. add_product(self.basket, product=prod2)
  27. self.assertFalse(self.condition.is_satisfied(self.offer, self.basket))
  28. def test_empty_basket_fails_partial_condition(self):
  29. self.assertFalse(self.condition.is_partially_satisfied(self.offer, self.basket))
  30. def test_smaller_quantity_basket_passes_partial_condition(self):
  31. add_product(self.basket)
  32. self.assertTrue(self.condition.is_partially_satisfied(self.offer, self.basket))
  33. def test_smaller_quantity_basket_upsell_message(self):
  34. add_product(self.basket)
  35. self.assertTrue('Buy 1 more product from ' in
  36. self.condition.get_upsell_message(self.offer, self.basket))
  37. def test_matching_quantity_basket_fails_partial_condition(self):
  38. add_product(self.basket, quantity=2)
  39. self.assertFalse(self.condition.is_partially_satisfied(self.offer, self.basket))
  40. def test_matching_quantity_basket_passes_condition(self):
  41. add_product(self.basket, quantity=2)
  42. self.assertTrue(self.condition.is_satisfied(self.offer, self.basket))
  43. def test_greater_quantity_basket_passes_condition(self):
  44. add_product(self.basket, quantity=3)
  45. self.assertTrue(self.condition.is_satisfied(self.offer, self.basket))
  46. def test_consumption(self):
  47. add_product(self.basket, quantity=3)
  48. self.condition.consume_items(self.offer, self.basket, [])
  49. self.assertEqual(1, self.basket.all_lines()[0].quantity_without_discount)
  50. def test_is_satisfied_accounts_for_consumed_items(self):
  51. add_product(self.basket, quantity=3)
  52. self.condition.consume_items(self.offer, self.basket, [])
  53. self.assertFalse(self.condition.is_satisfied(self.offer, self.basket))
  54. class TestValueCondition(TestCase):
  55. def setUp(self):
  56. self.range = models.Range.objects.create(
  57. name="All products range", includes_all_products=True)
  58. self.basket = factories.create_basket(empty=True)
  59. self.condition = models.ValueCondition(
  60. range=self.range, type="Value", value=D('10.00'))
  61. self.offer = mock.Mock()
  62. self.item = factories.create_product(price=D('5.00'))
  63. self.expensive_item = factories.create_product(price=D('15.00'))
  64. def test_empty_basket_fails_condition(self):
  65. self.assertFalse(self.condition.is_satisfied(self.offer, self.basket))
  66. def test_empty_basket_fails_partial_condition(self):
  67. self.assertFalse(self.condition.is_partially_satisfied(self.offer, self.basket))
  68. def test_less_value_basket_fails_condition(self):
  69. add_product(self.basket, D('5'))
  70. self.assertFalse(self.condition.is_satisfied(self.offer, self.basket))
  71. def test_not_discountable_item_fails_condition(self):
  72. product = factories.create_product(is_discountable=False)
  73. add_product(self.basket, D('15'), product=product)
  74. self.assertFalse(self.condition.is_satisfied(self.offer, self.basket))
  75. def test_upsell_message(self):
  76. add_product(self.basket, D('5'))
  77. self.assertTrue('Spend' in self.condition.get_upsell_message(self.offer, self.basket))
  78. def test_matching_basket_fails_partial_condition(self):
  79. add_product(self.basket, D('5'), 2)
  80. self.assertFalse(self.condition.is_partially_satisfied(self.offer, self.basket))
  81. def test_less_value_basket_passes_partial_condition(self):
  82. add_product(self.basket, D('5'), 1)
  83. self.assertTrue(self.condition.is_partially_satisfied(self.offer, self.basket))
  84. def test_matching_basket_passes_condition(self):
  85. add_product(self.basket, D('5'), 2)
  86. self.assertTrue(self.condition.is_satisfied(self.offer, self.basket))
  87. def test_greater_than_basket_passes_condition(self):
  88. add_product(self.basket, D('5'), 3)
  89. self.assertTrue(self.condition.is_satisfied(self.offer, self.basket))
  90. def test_consumption(self):
  91. add_product(self.basket, D('5'), 3)
  92. self.condition.consume_items(self.offer, self.basket, [])
  93. self.assertEqual(1, self.basket.all_lines()[0].quantity_without_discount)
  94. def test_consumption_with_high_value_product(self):
  95. add_product(self.basket, D('15'), 1)
  96. self.condition.consume_items(self.offer, self.basket, [])
  97. self.assertEqual(0, self.basket.all_lines()[0].quantity_without_discount)
  98. def test_is_consumed_respects_quantity_consumed(self):
  99. add_product(self.basket, D('15'), 1)
  100. self.assertTrue(self.condition.is_satisfied(self.offer, self.basket))
  101. self.condition.consume_items(self.offer, self.basket, [])
  102. self.assertFalse(self.condition.is_satisfied(self.offer, self.basket))
  103. class TestCoverageCondition(TestCase):
  104. def setUp(self):
  105. self.products = [factories.create_product(), factories.create_product()]
  106. self.range = models.Range.objects.create(name="Some products")
  107. for product in self.products:
  108. self.range.add_product(product)
  109. self.range.add_product(product)
  110. self.basket = factories.create_basket(empty=True)
  111. self.condition = models.CoverageCondition(
  112. range=self.range, type="Coverage", value=2)
  113. self.offer = mock.Mock()
  114. def test_empty_basket_fails(self):
  115. self.assertFalse(self.condition.is_satisfied(self.offer, self.basket))
  116. def test_empty_basket_fails_partial_condition(self):
  117. self.assertFalse(self.condition.is_partially_satisfied(self.offer, self.basket))
  118. def test_single_item_fails(self):
  119. add_product(self.basket, product=self.products[0])
  120. self.assertFalse(self.condition.is_satisfied(self.offer, self.basket))
  121. def test_not_discountable_item_fails(self):
  122. self.products[0].is_discountable = False
  123. self.products[0].save()
  124. add_product(self.basket, product=self.products[0])
  125. add_product(self.basket, product=self.products[1])
  126. self.assertFalse(self.condition.is_satisfied(self.offer, self.basket))
  127. def test_single_item_passes_partial_condition(self):
  128. add_product(self.basket, product=self.products[0])
  129. self.assertTrue(self.condition.is_partially_satisfied(self.offer, self.basket))
  130. def test_upsell_message(self):
  131. add_product(self.basket, product=self.products[0])
  132. self.assertTrue(
  133. 'Buy 1 more' in self.condition.get_upsell_message(self.offer, self.basket))
  134. def test_duplicate_item_fails(self):
  135. add_product(self.basket, quantity=2, product=self.products[0])
  136. self.assertFalse(self.condition.is_satisfied(self.offer, self.basket))
  137. def test_duplicate_item_passes_partial_condition(self):
  138. add_product(self.basket, quantity=2, product=self.products[0])
  139. self.assertTrue(self.condition.is_partially_satisfied(self.offer, self.basket))
  140. def test_covering_items_pass(self):
  141. add_product(self.basket, product=self.products[0])
  142. add_product(self.basket, product=self.products[1])
  143. self.assertTrue(self.condition.is_satisfied(self.offer, self.basket))
  144. def test_covering_items_fail_partial_condition(self):
  145. add_product(self.basket, product=self.products[0])
  146. add_product(self.basket, product=self.products[1])
  147. self.assertFalse(self.condition.is_partially_satisfied(self.offer, self.basket))
  148. def test_covering_items_are_consumed(self):
  149. add_product(self.basket, product=self.products[0])
  150. add_product(self.basket, product=self.products[1])
  151. self.condition.consume_items(self.offer, self.basket, [])
  152. self.assertEqual(0, self.basket.num_items_without_discount)
  153. def test_consumed_items_checks_affected_items(self):
  154. # Create new offer
  155. range = models.Range.objects.create(name="All products", includes_all_products=True)
  156. cond = models.CoverageCondition(range=range, type="Coverage", value=2)
  157. # Get 4 distinct products in the basket
  158. self.products.extend(
  159. [factories.create_product(), factories.create_product()])
  160. for product in self.products:
  161. add_product(self.basket, product=product)
  162. self.assertTrue(cond.is_satisfied(self.offer, self.basket))
  163. cond.consume_items(self.offer, self.basket, [])
  164. self.assertEqual(2, self.basket.num_items_without_discount)
  165. self.assertTrue(cond.is_satisfied(self.offer, self.basket))
  166. cond.consume_items(self.offer, self.basket, [])
  167. self.assertEqual(0, self.basket.num_items_without_discount)
  168. @pytest.fixture()
  169. def range():
  170. return factories.RangeFactory()
  171. @pytest.mark.django_db
  172. class TestConditionProxyModels(object):
  173. def test_name_and_description(self, range):
  174. """
  175. Tests that the condition proxy classes all return a name and
  176. description. Unfortunately, the current implementations means
  177. a valid range and value are required.
  178. This test became necessary because the complex name/description logic
  179. broke with the python_2_unicode_compatible decorator.
  180. """
  181. for type, __ in models.Condition.TYPE_CHOICES:
  182. condition = models.Condition(type=type, range=range, value=5)
  183. assert all([
  184. condition.name,
  185. condition.description,
  186. six.text_type(condition)])
  187. def test_proxy(self, range):
  188. for type, __ in models.Condition.TYPE_CHOICES:
  189. condition = models.Condition(type=type, range=range, value=5)
  190. proxy = condition.proxy()
  191. assert condition.type == proxy.type
  192. assert condition.range == proxy.range
  193. assert condition.value == proxy.value
  194. class TestCustomCondition(TestCase):
  195. def setUp(self):
  196. self.condition = custom.create_condition(BasketOwnerCalledBarry)
  197. self.offer = models.ConditionalOffer(condition=self.condition)
  198. self.basket = Basket()
  199. def test_is_not_satisfied_by_non_match(self):
  200. self.basket.owner = factories.UserFactory(first_name="Alan")
  201. self.assertFalse(self.offer.is_condition_satisfied(self.basket))
  202. def test_is_satisfied_by_match(self):
  203. self.basket.owner = factories.UserFactory(first_name="Barry")
  204. self.assertTrue(self.offer.is_condition_satisfied(self.basket))