| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- # pylint: disable=redefined-outer-name
- import pytest
-
- from oscar.apps.offer import models
- from oscar.apps.offer.applicator import Applicator
- from oscar.test.factories import (
- BasketFactory,
- ConditionalOfferFactory,
- ProductFactory,
- VoucherFactory,
- )
-
-
- @pytest.fixture
- def filled_basket():
- basket = BasketFactory()
- product1 = ProductFactory()
- product2 = ProductFactory()
- basket.add_product(product1, quantity=10)
- basket.add_product(product2, quantity=20)
- return basket
-
-
- @pytest.fixture
- def single_offer():
- return ConditionalOfferFactory(
- condition__range__includes_all_products=True,
- condition__value=1,
- benefit__range__includes_all_products=True,
- benefit__max_affected_items=1,
- name="offer1",
- exclusive=False,
- )
-
-
- @pytest.fixture
- def multi_offers():
- offer1 = ConditionalOfferFactory(
- condition__range__includes_all_products=True,
- benefit__range__includes_all_products=True,
- name="offer1",
- exclusive=False,
- )
- offer2 = ConditionalOfferFactory(
- condition__range__includes_all_products=True,
- benefit__range__includes_all_products=True,
- name="offer2",
- exclusive=False,
- )
- offer3 = ConditionalOfferFactory(
- condition__range__includes_all_products=True,
- benefit__range__includes_all_products=True,
- name="offer3",
- exclusive=False,
- )
- return offer1, offer2, offer3
-
-
- @pytest.mark.django_db
- class TestLineOfferConsumer:
- def test_consumed_no_offer(self, filled_basket):
- for line in filled_basket.all_lines():
- assert line.discounts.num_consumed() == 0
-
- def test_available_with_offer(self):
- basket = BasketFactory()
- product1 = ProductFactory()
- product2 = ProductFactory()
- basket.add_product(product1, quantity=1)
- basket.add_product(product2, quantity=10)
-
- benefit = models.Benefit(
- type=models.Benefit.PERCENTAGE,
- value=10,
- max_affected_items=5,
- )
- benefit.save()
-
- offer1 = ConditionalOfferFactory(name="offer1", benefit=benefit)
- lines = basket.all_lines()
- assert lines[0].discounts.available(offer1) == 1
- assert lines[1].discounts.available(offer1) == 10
-
- def test_consumed_with_offer(self, filled_basket):
- offer1 = ConditionalOfferFactory(name="offer1")
- offer2 = ConditionalOfferFactory(name="offer2")
- offer1.exclusive = False
- offer2.exclusive = False
-
- for line in filled_basket.all_lines():
- assert line.discounts.num_consumed(offer1) == 0
- assert line.discounts.num_consumed(offer2) == 0
-
- line1 = filled_basket.all_lines()[0]
- line2 = filled_basket.all_lines()[1]
-
- line1.discounts.consume(1, offer1)
- assert line1.discounts.num_consumed() == 1
- assert line1.discounts.num_consumed(offer1) == 1
- assert line1.discounts.num_consumed(offer2) == 0
-
- line1.discounts.consume(9, offer1)
- assert line1.discounts.num_consumed() == line1.quantity
- assert line1.discounts.num_consumed(offer1) == line1.quantity
- assert line1.discounts.num_consumed(offer2) == 0
-
- line1.discounts.consume(99, offer1)
- assert line1.discounts.num_consumed(offer1) == line1.quantity
- assert line1.discounts.num_consumed(offer2) == 0
-
- line1.discounts.consume(1, offer2)
- line2.discounts.consume(1, offer2)
-
- assert line1.discounts.num_consumed(offer2) == 1
- assert line2.discounts.num_consumed(offer2) == 1
-
- def test_consume(self, filled_basket):
- line = filled_basket.all_lines()[0]
- line.consume(1)
- assert line.quantity_with_discount == 1
- line.consume(99)
- assert line.quantity_with_discount == 10
-
- def test_consumed_with_exclusive_offer_1(self, filled_basket):
- offer1 = ConditionalOfferFactory(name="offer1")
- offer2 = ConditionalOfferFactory(name="offer2")
- offer3 = ConditionalOfferFactory(name="offer3")
- offer1.exclusive = True
- offer2.exclusive = False
- offer3.exclusive = False
-
- for line in filled_basket.all_lines():
- assert line.discounts.num_consumed(offer1) == 0
- assert line.discounts.num_consumed(offer2) == 0
-
- line1, line2 = list(filled_basket.all_lines())
-
- # exclusive offer consumes one item on line1
- line1.discounts.consume(1, offer1)
-
- # offer1 is exclusive so that blocks other offers
- assert line1.is_available_for_offer_discount(offer2) is False
-
- line1.discounts.consume(99, offer1)
- # ran out of room for offer1
- assert line1.is_available_for_offer_discount(offer1) is False
- # offer2 was never an option
- assert line1.is_available_for_offer_discount(offer2) is False
-
- # exclusivity is per line so line2 is available for offer2
- line2.discounts.consume(1, offer2)
- # nope: exclusive and non-exclusive don't mix
- assert line2.is_available_for_offer_discount(offer1) is False
-
- line2.discounts.consume(99, offer2)
- # ran out of room for offer2
- assert line2.is_available_for_offer_discount(offer1) is False
- # but still room for offer3!
- assert line2.is_available_for_offer_discount(offer3) is True
-
- def test_consumed_with_exclusive_offer_2(self, filled_basket):
- offer1 = ConditionalOfferFactory(name="offer1")
- offer2 = ConditionalOfferFactory(name="offer2")
- offer3 = ConditionalOfferFactory(name="offer3")
- offer1.exclusive = True
- offer2.exclusive = False
- offer3.exclusive = False
-
- for line in filled_basket.all_lines():
- assert line.discounts.num_consumed(offer1) == 0
- assert line.discounts.num_consumed(offer2) == 0
-
- line1, line2 = list(filled_basket.all_lines())
-
- # exclusive offer consumes one item on line1
- line1.discounts.consume(1, offer1)
- remaining1 = line1.quantity - 1
-
- assert line1.quantity_with_offer_discount(offer1) == 1
- assert line1.quantity_with_offer_discount(offer2) == 0
- assert line1.quantity_with_offer_discount(offer3) == 0
-
- assert line1.quantity_without_offer_discount(offer1) == remaining1
- assert line1.quantity_without_offer_discount(offer2) == 0
- assert line1.quantity_without_offer_discount(offer3) == 0
-
- # exclusive offer consumes all items on line1
- line1.discounts.consume(remaining1, offer1)
- assert line1.quantity_with_offer_discount(offer1) == line1.quantity
- assert line1.quantity_with_offer_discount(offer2) == 0
- assert line1.quantity_with_offer_discount(offer3) == 0
-
- assert line1.quantity_without_offer_discount(offer1) == 0
- assert line1.quantity_without_offer_discount(offer2) == 0
- assert line1.quantity_without_offer_discount(offer3) == 0
-
- # non-exclusive offer consumes one item on line2
- line2.discounts.consume(1, offer2)
- remaining2 = line2.quantity - 1
-
- assert line2.quantity_with_offer_discount(offer1) == 0
- assert line2.quantity_with_offer_discount(offer2) == 1
- assert line2.quantity_with_offer_discount(offer3) == 0
-
- assert line2.quantity_without_offer_discount(offer1) == 0
- assert line2.quantity_without_offer_discount(offer2) == remaining2
- assert line2.quantity_without_offer_discount(offer3) == line2.quantity
-
- # non-exclusive offer consumes all items on line2
- line2.discounts.consume(remaining2, offer2)
-
- assert line2.quantity_with_offer_discount(offer1) == 0
- assert line2.quantity_with_offer_discount(offer2) == line2.quantity
- assert line2.quantity_with_offer_discount(offer3) == 0
-
- assert line2.quantity_without_offer_discount(offer1) == 0
- assert line2.quantity_without_offer_discount(offer2) == 0
- assert line2.quantity_without_offer_discount(offer3) == line2.quantity
-
- # pylint: disable=unused-argument
- def test_consumed_by_application(self, filled_basket, single_offer):
- basket = filled_basket
- Applicator().apply(basket)
- assert len(basket.offer_applications.offer_discounts) == 1
- assert [x.discounts.num_consumed() for x in basket.all_lines()] == [1, 0]
-
- def test_apply_multiple_vouchers(self, filled_basket):
- offer1 = ConditionalOfferFactory(
- condition__range__includes_all_products=True,
- benefit__range__includes_all_products=True,
- name="offer1",
- exclusive=True,
- )
- voucher1 = VoucherFactory(name="voucher1", code="VOUCHER1")
- voucher1.offers.add(offer1)
- offer2 = ConditionalOfferFactory(
- condition__range__includes_all_products=True,
- benefit__range__includes_all_products=True,
- name="offer2",
- exclusive=True,
- )
- voucher2 = VoucherFactory(name="voucher2", code="VOUCHER2")
- voucher2.offers.add(offer2)
- offer1.exclusive = True
- offer2.exclusive = True
-
- assert len(filled_basket.offer_applications) == 0
- Applicator().apply_offers(basket=filled_basket, offers=[offer2, offer1])
- filled_basket.refresh_from_db()
- # Only one should be applied because they're both exclusive.
- assert len(filled_basket.offer_applications) == 1
-
- def test_consumed_with_combined_offer(self, filled_basket):
- offer1 = ConditionalOfferFactory(name="offer1")
- offer2 = ConditionalOfferFactory(name="offer2")
- offer3 = ConditionalOfferFactory(name="offer3")
- offer4 = ConditionalOfferFactory(name="offer4")
- offer1.exclusive = True
- offer2.exclusive = False
- offer3.exclusive = False
- offer4.exclusive = False
- offer2.combinations.add(offer3)
- assert offer3 in offer2.combined_offers
- assert offer2 in offer3.combined_offers
-
- for line in filled_basket.all_lines():
- assert line.discounts.num_consumed(offer1) == 0
- assert line.discounts.num_consumed(offer2) == 0
- assert line.discounts.num_consumed(offer3) == 0
-
- line1 = filled_basket.all_lines()[0]
-
- # combinable offer consumes one item of line1
- line1.discounts.consume(1, offer2)
- remaining1 = line1.quantity - 1
-
- assert line1.quantity_with_offer_discount(offer1) == 0
- assert line1.quantity_with_offer_discount(offer2) == 1
- assert line1.quantity_with_offer_discount(offer3) == 0
- assert line1.quantity_with_offer_discount(offer4) == 0
-
- assert line1.quantity_without_offer_discount(offer1) == 0
- assert line1.quantity_without_offer_discount(offer2) == remaining1
- assert line1.quantity_without_offer_discount(offer3) == line1.quantity
- assert line1.quantity_without_offer_discount(offer4) == 0
-
- # combinable offer consumes one item of line1
- line1.discounts.consume(1, offer3)
- assert line1.quantity_with_offer_discount(offer1) == 0
- assert line1.quantity_with_offer_discount(offer2) == 1
- assert line1.quantity_with_offer_discount(offer3) == 1
- assert line1.quantity_with_offer_discount(offer4) == 0
-
- assert line1.quantity_without_offer_discount(offer1) == 0
- assert line1.quantity_without_offer_discount(offer2) == remaining1
- assert line1.quantity_without_offer_discount(offer3) == remaining1
- assert line1.quantity_without_offer_discount(offer4) == 0
-
- # combinable offer consumes all items of line1
- line1.discounts.consume(remaining1, offer2)
-
- assert line1.quantity_with_offer_discount(offer1) == 0
- assert line1.quantity_with_offer_discount(offer2) == line1.quantity
- assert line1.quantity_with_offer_discount(offer3) == 1
- assert line1.quantity_with_offer_discount(offer4) == 0
-
- assert line1.quantity_without_offer_discount(offer1) == 0
- assert line1.quantity_without_offer_discount(offer2) == 0
- assert line1.quantity_without_offer_discount(offer3) == remaining1
- assert line1.quantity_without_offer_discount(offer4) == 0
|