Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

test_condition.py 11KB

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