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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. from decimal import Decimal as D
  2. import mock
  3. import six
  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. class TestConditionProxyModels(TestCase):
  168. def test_name_and_description(self):
  169. """
  170. Tests that the condition proxy classes all return a name and
  171. description. Unfortunately, the current implementations means
  172. a valid range and value are required.
  173. This test became necessary because the complex name/description logic
  174. broke with the python_2_unicode_compatible decorator.
  175. """
  176. range = factories.RangeFactory()
  177. for type, __ in models.Condition.TYPE_CHOICES:
  178. condition = models.Condition(type=type, range=range, value=5)
  179. self.assertTrue(all([
  180. condition.name,
  181. condition.description,
  182. six.text_type(condition)]))
  183. class TestCustomCondition(TestCase):
  184. def setUp(self):
  185. self.condition = custom.create_condition(BasketOwnerCalledBarry)
  186. self.offer = models.ConditionalOffer(condition=self.condition)
  187. self.basket = Basket()
  188. def test_is_not_satisfied_by_non_match(self):
  189. self.basket.owner = factories.UserFactory(first_name="Alan")
  190. self.assertFalse(self.offer.is_condition_satisfied(self.basket))
  191. def test_is_satisfied_by_match(self):
  192. self.basket.owner = factories.UserFactory(first_name="Barry")
  193. self.assertTrue(self.offer.is_condition_satisfied(self.basket))