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_utils.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. # pylint: disable=redefined-outer-name
  2. import pytest
  3. from oscar.apps.offer import models
  4. from oscar.apps.offer.applicator import Applicator
  5. from oscar.test.factories import (
  6. BasketFactory,
  7. ConditionalOfferFactory,
  8. ProductFactory,
  9. VoucherFactory,
  10. )
  11. @pytest.fixture
  12. def filled_basket():
  13. basket = BasketFactory()
  14. product1 = ProductFactory()
  15. product2 = ProductFactory()
  16. basket.add_product(product1, quantity=10)
  17. basket.add_product(product2, quantity=20)
  18. return basket
  19. @pytest.fixture
  20. def single_offer():
  21. return ConditionalOfferFactory(
  22. condition__range__includes_all_products=True,
  23. condition__value=1,
  24. benefit__range__includes_all_products=True,
  25. benefit__max_affected_items=1,
  26. name="offer1",
  27. exclusive=False,
  28. )
  29. @pytest.fixture
  30. def multi_offers():
  31. offer1 = ConditionalOfferFactory(
  32. condition__range__includes_all_products=True,
  33. benefit__range__includes_all_products=True,
  34. name="offer1",
  35. exclusive=False,
  36. )
  37. offer2 = ConditionalOfferFactory(
  38. condition__range__includes_all_products=True,
  39. benefit__range__includes_all_products=True,
  40. name="offer2",
  41. exclusive=False,
  42. )
  43. offer3 = ConditionalOfferFactory(
  44. condition__range__includes_all_products=True,
  45. benefit__range__includes_all_products=True,
  46. name="offer3",
  47. exclusive=False,
  48. )
  49. return offer1, offer2, offer3
  50. @pytest.mark.django_db
  51. class TestLineOfferConsumer:
  52. def test_consumed_no_offer(self, filled_basket):
  53. for line in filled_basket.all_lines():
  54. assert line.discounts.num_consumed() == 0
  55. def test_available_with_offer(self):
  56. basket = BasketFactory()
  57. product1 = ProductFactory()
  58. product2 = ProductFactory()
  59. basket.add_product(product1, quantity=1)
  60. basket.add_product(product2, quantity=10)
  61. benefit = models.Benefit(
  62. type=models.Benefit.PERCENTAGE,
  63. value=10,
  64. max_affected_items=5,
  65. )
  66. benefit.save()
  67. offer1 = ConditionalOfferFactory(name="offer1", benefit=benefit)
  68. lines = basket.all_lines()
  69. assert lines[0].discounts.available(offer1) == 1
  70. assert lines[1].discounts.available(offer1) == 10
  71. def test_consumed_with_offer(self, filled_basket):
  72. offer1 = ConditionalOfferFactory(name="offer1")
  73. offer2 = ConditionalOfferFactory(name="offer2")
  74. offer1.exclusive = False
  75. offer2.exclusive = False
  76. for line in filled_basket.all_lines():
  77. assert line.discounts.num_consumed(offer1) == 0
  78. assert line.discounts.num_consumed(offer2) == 0
  79. line1 = filled_basket.all_lines()[0]
  80. line2 = filled_basket.all_lines()[1]
  81. line1.discounts.consume(1, offer1)
  82. assert line1.discounts.num_consumed() == 1
  83. assert line1.discounts.num_consumed(offer1) == 1
  84. assert line1.discounts.num_consumed(offer2) == 0
  85. line1.discounts.consume(9, offer1)
  86. assert line1.discounts.num_consumed() == line1.quantity
  87. assert line1.discounts.num_consumed(offer1) == line1.quantity
  88. assert line1.discounts.num_consumed(offer2) == 0
  89. line1.discounts.consume(99, offer1)
  90. assert line1.discounts.num_consumed(offer1) == line1.quantity
  91. assert line1.discounts.num_consumed(offer2) == 0
  92. line1.discounts.consume(1, offer2)
  93. line2.discounts.consume(1, offer2)
  94. assert line1.discounts.num_consumed(offer2) == 1
  95. assert line2.discounts.num_consumed(offer2) == 1
  96. def test_consume(self, filled_basket):
  97. line = filled_basket.all_lines()[0]
  98. line.consume(1)
  99. assert line.quantity_with_discount == 1
  100. line.consume(99)
  101. assert line.quantity_with_discount == 10
  102. def test_consumed_with_exclusive_offer_1(self, filled_basket):
  103. offer1 = ConditionalOfferFactory(name="offer1")
  104. offer2 = ConditionalOfferFactory(name="offer2")
  105. offer3 = ConditionalOfferFactory(name="offer3")
  106. offer1.exclusive = True
  107. offer2.exclusive = False
  108. offer3.exclusive = False
  109. for line in filled_basket.all_lines():
  110. assert line.discounts.num_consumed(offer1) == 0
  111. assert line.discounts.num_consumed(offer2) == 0
  112. line1, line2 = list(filled_basket.all_lines())
  113. # exclusive offer consumes one item on line1
  114. line1.discounts.consume(1, offer1)
  115. # offer1 is exclusive so that blocks other offers
  116. assert line1.is_available_for_offer_discount(offer2) is False
  117. line1.discounts.consume(99, offer1)
  118. # ran out of room for offer1
  119. assert line1.is_available_for_offer_discount(offer1) is False
  120. # offer2 was never an option
  121. assert line1.is_available_for_offer_discount(offer2) is False
  122. # exclusivity is per line so line2 is available for offer2
  123. line2.discounts.consume(1, offer2)
  124. # nope: exclusive and non-exclusive don't mix
  125. assert line2.is_available_for_offer_discount(offer1) is False
  126. line2.discounts.consume(99, offer2)
  127. # ran out of room for offer2
  128. assert line2.is_available_for_offer_discount(offer1) is False
  129. # but still room for offer3!
  130. assert line2.is_available_for_offer_discount(offer3) is True
  131. def test_consumed_with_exclusive_offer_2(self, filled_basket):
  132. offer1 = ConditionalOfferFactory(name="offer1")
  133. offer2 = ConditionalOfferFactory(name="offer2")
  134. offer3 = ConditionalOfferFactory(name="offer3")
  135. offer1.exclusive = True
  136. offer2.exclusive = False
  137. offer3.exclusive = False
  138. for line in filled_basket.all_lines():
  139. assert line.discounts.num_consumed(offer1) == 0
  140. assert line.discounts.num_consumed(offer2) == 0
  141. line1, line2 = list(filled_basket.all_lines())
  142. # exclusive offer consumes one item on line1
  143. line1.discounts.consume(1, offer1)
  144. remaining1 = line1.quantity - 1
  145. assert line1.quantity_with_offer_discount(offer1) == 1
  146. assert line1.quantity_with_offer_discount(offer2) == 0
  147. assert line1.quantity_with_offer_discount(offer3) == 0
  148. assert line1.quantity_without_offer_discount(offer1) == remaining1
  149. assert line1.quantity_without_offer_discount(offer2) == 0
  150. assert line1.quantity_without_offer_discount(offer3) == 0
  151. # exclusive offer consumes all items on line1
  152. line1.discounts.consume(remaining1, offer1)
  153. assert line1.quantity_with_offer_discount(offer1) == line1.quantity
  154. assert line1.quantity_with_offer_discount(offer2) == 0
  155. assert line1.quantity_with_offer_discount(offer3) == 0
  156. assert line1.quantity_without_offer_discount(offer1) == 0
  157. assert line1.quantity_without_offer_discount(offer2) == 0
  158. assert line1.quantity_without_offer_discount(offer3) == 0
  159. # non-exclusive offer consumes one item on line2
  160. line2.discounts.consume(1, offer2)
  161. remaining2 = line2.quantity - 1
  162. assert line2.quantity_with_offer_discount(offer1) == 0
  163. assert line2.quantity_with_offer_discount(offer2) == 1
  164. assert line2.quantity_with_offer_discount(offer3) == 0
  165. assert line2.quantity_without_offer_discount(offer1) == 0
  166. assert line2.quantity_without_offer_discount(offer2) == remaining2
  167. assert line2.quantity_without_offer_discount(offer3) == line2.quantity
  168. # non-exclusive offer consumes all items on line2
  169. line2.discounts.consume(remaining2, offer2)
  170. assert line2.quantity_with_offer_discount(offer1) == 0
  171. assert line2.quantity_with_offer_discount(offer2) == line2.quantity
  172. assert line2.quantity_with_offer_discount(offer3) == 0
  173. assert line2.quantity_without_offer_discount(offer1) == 0
  174. assert line2.quantity_without_offer_discount(offer2) == 0
  175. assert line2.quantity_without_offer_discount(offer3) == line2.quantity
  176. # pylint: disable=unused-argument
  177. def test_consumed_by_application(self, filled_basket, single_offer):
  178. basket = filled_basket
  179. Applicator().apply(basket)
  180. assert len(basket.offer_applications.offer_discounts) == 1
  181. assert [x.discounts.num_consumed() for x in basket.all_lines()] == [1, 0]
  182. def test_apply_multiple_vouchers(self, filled_basket):
  183. offer1 = ConditionalOfferFactory(
  184. condition__range__includes_all_products=True,
  185. benefit__range__includes_all_products=True,
  186. name="offer1",
  187. exclusive=True,
  188. )
  189. voucher1 = VoucherFactory(name="voucher1", code="VOUCHER1")
  190. voucher1.offers.add(offer1)
  191. offer2 = ConditionalOfferFactory(
  192. condition__range__includes_all_products=True,
  193. benefit__range__includes_all_products=True,
  194. name="offer2",
  195. exclusive=True,
  196. )
  197. voucher2 = VoucherFactory(name="voucher2", code="VOUCHER2")
  198. voucher2.offers.add(offer2)
  199. offer1.exclusive = True
  200. offer2.exclusive = True
  201. assert len(filled_basket.offer_applications) == 0
  202. Applicator().apply_offers(basket=filled_basket, offers=[offer2, offer1])
  203. filled_basket.refresh_from_db()
  204. # Only one should be applied because they're both exclusive.
  205. assert len(filled_basket.offer_applications) == 1
  206. def test_consumed_with_combined_offer(self, filled_basket):
  207. offer1 = ConditionalOfferFactory(name="offer1")
  208. offer2 = ConditionalOfferFactory(name="offer2")
  209. offer3 = ConditionalOfferFactory(name="offer3")
  210. offer4 = ConditionalOfferFactory(name="offer4")
  211. offer1.exclusive = True
  212. offer2.exclusive = False
  213. offer3.exclusive = False
  214. offer4.exclusive = False
  215. offer2.combinations.add(offer3)
  216. assert offer3 in offer2.combined_offers
  217. assert offer2 in offer3.combined_offers
  218. for line in filled_basket.all_lines():
  219. assert line.discounts.num_consumed(offer1) == 0
  220. assert line.discounts.num_consumed(offer2) == 0
  221. assert line.discounts.num_consumed(offer3) == 0
  222. line1 = filled_basket.all_lines()[0]
  223. # combinable offer consumes one item of line1
  224. line1.discounts.consume(1, offer2)
  225. remaining1 = line1.quantity - 1
  226. assert line1.quantity_with_offer_discount(offer1) == 0
  227. assert line1.quantity_with_offer_discount(offer2) == 1
  228. assert line1.quantity_with_offer_discount(offer3) == 0
  229. assert line1.quantity_with_offer_discount(offer4) == 0
  230. assert line1.quantity_without_offer_discount(offer1) == 0
  231. assert line1.quantity_without_offer_discount(offer2) == remaining1
  232. assert line1.quantity_without_offer_discount(offer3) == line1.quantity
  233. assert line1.quantity_without_offer_discount(offer4) == 0
  234. # combinable offer consumes one item of line1
  235. line1.discounts.consume(1, offer3)
  236. assert line1.quantity_with_offer_discount(offer1) == 0
  237. assert line1.quantity_with_offer_discount(offer2) == 1
  238. assert line1.quantity_with_offer_discount(offer3) == 1
  239. assert line1.quantity_with_offer_discount(offer4) == 0
  240. assert line1.quantity_without_offer_discount(offer1) == 0
  241. assert line1.quantity_without_offer_discount(offer2) == remaining1
  242. assert line1.quantity_without_offer_discount(offer3) == remaining1
  243. assert line1.quantity_without_offer_discount(offer4) == 0
  244. # combinable offer consumes all items of line1
  245. line1.discounts.consume(remaining1, offer2)
  246. assert line1.quantity_with_offer_discount(offer1) == 0
  247. assert line1.quantity_with_offer_discount(offer2) == line1.quantity
  248. assert line1.quantity_with_offer_discount(offer3) == 1
  249. assert line1.quantity_with_offer_discount(offer4) == 0
  250. assert line1.quantity_without_offer_discount(offer1) == 0
  251. assert line1.quantity_without_offer_discount(offer2) == 0
  252. assert line1.quantity_without_offer_discount(offer3) == remaining1
  253. assert line1.quantity_without_offer_discount(offer4) == 0