class TestOfferApplicator(TestCase): def setUp(self): self.applicator = Applicator() self.basket = BasketFactory() rng = RangeFactory(includes_all_products=True) self.condition = ConditionFactory( range=rng, type=ConditionFactory._meta.model.VALUE, value=D('100'), proxy_class=None) self.benefit = BenefitFactory( range=rng, type=BenefitFactory._meta.model.FIXED, value=D('10')) def test_applies_offer_multiple_times_by_default(self): add_product(self.basket, D('100'), 5) offer = ConditionalOfferFactory( pk=1, condition=self.condition, benefit=self.benefit) self.applicator.apply self.applicator.apply_offers(self.basket, [offer]) line = self.basket.all_lines()[0] self.assertTrue(line.quantity_with_offer_discount(offer) == 5) def test_respects_maximum_applications_field(self): add_product(self.basket, D('100'), 5) offer = ConditionalOfferFactory( pk=1, condition=self.condition, benefit=self.benefit, max_basket_applications=1) self.applicator.apply_offers(self.basket, [offer]) line = self.basket.all_lines()[0] self.assertTrue(line.quantity_with_offer_discount(offer) == 5) applications = self.basket.offer_applications.applications self.assertTrue(applications[1]['freq'] == 1) def test_uses_offers_in_order_of_descending_priority(self): self.applicator.get_site_offers = Mock( return_value=[models.ConditionalOffer( name="offer1", condition=self.condition, benefit=self.benefit, priority=1)]) self.applicator.get_user_offers = Mock( return_value=[models.ConditionalOffer( name="offer2", condition=self.condition, benefit=self.benefit, priority=-1)]) offers = self.applicator.get_offers(self.basket) priorities = [offer.priority for offer in offers] self.assertEqual(sorted(priorities, reverse=True), priorities) def test_get_site_offers(self): models.ConditionalOffer.objects.create( name="globaloffer", condition=self.condition, benefit=self.benefit, offer_type=models.ConditionalOffer.SITE) models.ConditionalOffer.objects.create( name="sessionoffer", condition=self.condition, benefit=self.benefit, offer_type=models.ConditionalOffer.SESSION) site_offers = Applicator().get_site_offers() # Only one offer should be returned self.assertEqual(len(site_offers), 1) self.assertEqual(site_offers[0].name, "globaloffer")
class TestOfferApplicator(TestCase): def setUp(self): self.applicator = Applicator() self.basket = BasketFactory() rng = RangeFactory(includes_all_products=True) self.condition = ConditionFactory( range=rng, type=ConditionFactory._meta.model.VALUE, value=D('100'), proxy_class=None) self.benefit = BenefitFactory( range=rng, type=BenefitFactory._meta.model.FIXED, value=D('10')) def test_applies_offer_multiple_times_by_default(self): add_product(self.basket, D('100'), 5) offer = ConditionalOfferFactory( pk=1, condition=self.condition, benefit=self.benefit) self.applicator.apply self.applicator.apply_offers(self.basket, [offer]) line = self.basket.all_lines()[0] self.assertTrue(line.quantity_with_offer_discount(offer) == 5) def test_respects_maximum_applications_field(self): add_product(self.basket, D('100'), 5) offer = ConditionalOfferFactory( pk=1, condition=self.condition, benefit=self.benefit, max_basket_applications=1) self.applicator.apply_offers(self.basket, [offer]) line = self.basket.all_lines()[0] self.assertTrue(line.quantity_with_offer_discount(offer) == 5) applications = self.basket.offer_applications.applications self.assertTrue(applications[1]['freq'] == 1) def test_uses_offers_in_order_of_descending_priority(self): self.applicator.get_site_offers = Mock( return_value=[models.ConditionalOffer( name="offer1", condition=self.condition, benefit=self.benefit, priority=1)]) self.applicator.get_user_offers = Mock( return_value=[models.ConditionalOffer( name="offer2", condition=self.condition, benefit=self.benefit, priority=-1)]) offers = self.applicator.get_offers(self.basket) priorities = [offer.priority for offer in offers] self.assertEqual(sorted(priorities, reverse=True), priorities) def test_get_site_offers(self): models.ConditionalOffer.objects.create( name="globaloffer", condition=self.condition, benefit=self.benefit, offer_type=models.ConditionalOffer.SITE) models.ConditionalOffer.objects.create( name="sessionoffer", condition=self.condition, benefit=self.benefit, offer_type=models.ConditionalOffer.SESSION) site_offers = Applicator().get_site_offers() # Only one offer should be returned self.assertEqual(len(site_offers), 1) self.assertEqual(site_offers[0].name, "globaloffer")
def test_basket_lines_queryset_is_ordered(self): # This is needed to make sure a formset is not performing the query # again with an order_by clause (losing all calculated discounts) basket = BasketFactory() product = ProductFactory(title="A product") another_product = ProductFactory(title="Another product") basket.add_product(product) basket.add_product(another_product) queryset = basket.all_lines() self.assertTrue(queryset.ordered)
def test_basket_lines_queryset_is_ordered(self): # This is needed to make sure a formset is not performing the query # again with an order_by clause (losing all calculated discounts) basket = BasketFactory() product = ProductFactory(title="A product") another_product = ProductFactory(title="Another product") basket.add_product(product) basket.add_product(another_product) queryset = basket.all_lines() self.assertTrue(queryset.ordered)
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].consumer.available(offer1) == 1 assert lines[1].consumer.available(offer1) == 5
class JournalBundleConditionTests(TestCase, JournalMixin): def setUp(self): super(JournalBundleConditionTests, self).setUp() user = self.create_user(is_staff=True) self.client.login(username=user.username, password=self.password) self.condition = factories.JournalConditionFactory() self.offer = factories.JournalBundleOfferFactory( partner=self.partner, condition=self.condition) self.basket = BasketFactory(site=self.site, owner=UserFactory()) self.basket.add_product(self.create_product(self.client), 1) def test_name(self, mocked_journal_api_response): """ The name should contain the program's UUID. """ mocked_journal_api_response.return_value = None expected = 'Basket contains every product in bundle {}'.format( self.condition.journal_bundle_uuid) self.assertEqual(self.condition.name, expected) def test_is_satisfied_with_empty_basket(self, mocked_journal_api_response): """ Test the 'is_satisfied' with empty basket """ mocked_journal_api_response.return_value = None self.basket.flush() self.assertTrue(self.basket.is_empty) self.assertFalse(self.condition.is_satisfied(self.offer, self.basket)) def test_is_satisfied_with_exception(self, mocked_journal_api_response): """ Test the 'is_satisfied' with 'HttpNotFoundError' exception """ mocked_journal_api_response.side_effect = HttpNotFoundError self.assertFalse(self.condition.is_satisfied(self.offer, self.basket)) def test_is_satisfied_with_slumber_exception(self, mocked_journal_api_response): """ Test the 'is_satisfied' with 'SlumberBaseException' exception """ mocked_journal_api_response.side_effect = SlumberBaseException self.assertFalse(self.condition.is_satisfied(self.offer, self.basket)) def test_is_satisfied_with_timeout(self, mocked_journal_api_response): """ Test the 'is_satisfied' with 'Timeout' exception """ mocked_journal_api_response.side_effect = Timeout self.assertFalse(self.condition.is_satisfied(self.offer, self.basket)) def test_is_satisfied_without_journal_bundle(self, mocked_journal_api_response): """ Test the 'is_satisfied' without Journal bundle """ mocked_journal_api_response.return_value = None self.assertFalse(self.condition.is_satisfied(self.offer, self.basket)) def test_is_satisfied_without_courses(self, mocked_journal_api_response): """ Test the 'is_satisfied' without courses in Journal bundle """ mocked_journal_api_response.return_value = self.get_mocked_discovery_journal_bundle( empty_courses=True) self.assertFalse(self.condition.is_satisfied(self.offer, self.basket)) def test_is_satisfied_with_some_but_not_all_courses( self, mocked_journal_api_response): """ Test the 'is_satisfied' with only some of the courses in the Journal bundle """ mocked_journal_api_response.return_value = self.get_mocked_discovery_journal_bundle( multiple_courses=True) self.assertFalse(self.condition.is_satisfied(self.offer, self.basket)) def test_is_satisfied_with_dummy_product(self, mocked_journal_api_response): """ Test the 'is_satisfied' with dummy product in basket """ mocked_journal_api_response.return_value = self.get_mocked_discovery_journal_bundle( ) self.basket.flush() self.basket.add_product( self.create_product( self.client, data=self.get_data_for_create(sku="dummy-sku")), 1) self.assertFalse(self.condition.is_satisfied(self.offer, self.basket)) def test_is_satisfied_with_valid_data(self, mocked_journal_api_response): """ Test the 'is_satisfied' with valid Journal bundle """ mocked_journal_api_response.return_value = self.get_mocked_discovery_journal_bundle( empty_journals=True) self.assertTrue(self.condition.is_satisfied(self.offer, self.basket)) def test_get_applicable_lines(self, mocked_journal_api_response): """ Test the 'get_applicable_lines' with valid product in basket """ mocked_journal_api_response.return_value = self.get_mocked_discovery_journal_bundle( ) applicable_lines = [(line.product.stockrecords.first().price_excl_tax, line) for line in self.basket.all_lines()] self.assertEqual( self.condition.get_applicable_lines(self.offer, self.basket), applicable_lines) def test_get_applicable_lines_with_empty_basket( self, mocked_journal_api_response): """ Test the 'get_applicable_lines' with empty basket """ mocked_journal_api_response.return_value = self.get_mocked_discovery_journal_bundle( ) self.basket.flush() self.assertEqual( self.condition.get_applicable_lines(self.offer, self.basket), []) def test_get_applicable_lines_sku_not_in_basket( self, mocked_journal_api_response): """ Test the 'get_applicable_lines' where the sku is not in the basket """ mocked_journal_api_response.return_value = self.get_mocked_discovery_journal_bundle( ) self.basket.flush() self.basket.add_product( self.create_product( self.client, data=self.get_data_for_create(sku="dummy-sku")), 1) self.assertEqual( self.condition.get_applicable_lines(self.offer, self.basket), []) @mock.patch( "ecommerce.extensions.catalogue.models.Product.get_is_discountable") def test_get_applicable_lines_product_is_not_discountable( self, mocked_product_is_discountable, mocked_journal_api_response): """ Test the 'get_applicable_lines' where the product is not discountable """ mocked_journal_api_response.return_value = self.get_mocked_discovery_journal_bundle( ) mocked_product_is_discountable.return_value = False self.assertEqual( self.condition.get_applicable_lines(self.offer, self.basket), []) @mock.patch("oscar.apps.offer.utils.unit_price") def test_get_applicable_lines_no_price(self, mocked_unit_price, mocked_journal_api_response): """ Test the 'get_applicable_lines' where there is no price """ mocked_journal_api_response.return_value = self.get_mocked_discovery_journal_bundle( ) mocked_unit_price.return_value = None self.assertEqual( self.condition.get_applicable_lines(self.offer, self.basket), []) def test_get_applicable_lines_no_journal_bundle(self, mock_journal_api_response): """ Test 'get_applicable_lines' where the journal bundle is None """ mock_journal_api_response.return_value = None self.assertEqual( self.condition.get_applicable_lines(self.offer, self.basket), [])
def test_offer(self): # Our offer is for 100%, so all lines should end up with a price of 0. offer = factories.ProgramOfferFactory( partner=self.partner, benefit=factories.PercentageDiscountBenefitWithoutRangeFactory( value=100)) basket = BasketFactory(site=self.site, owner=self.create_user()) program_uuid = offer.condition.program_uuid program = self.mock_program_detail_endpoint( program_uuid, self.site_configuration.discovery_api_url) self.mock_user_data(basket.owner.username) # Add one course run seat from each course to the basket. products = [] for course in program['courses']: course_run = Course.objects.get(id=course['course_runs'][0]['key']) for seat in course_run.seat_products: if seat.attr.id_verification_required: products.append(seat) basket.add_product(seat) # No discounts should be applied, and each line should have a price of 100.00. self.assertEqual(len(basket.offer_applications), 0) self.assertEqual(basket.total_discount, 0) for line in basket.all_lines(): self.assertEqual(line.line_price_incl_tax_incl_discounts, Decimal(100)) # Apply the offers as Oscar will in a request basket.strategy = DefaultStrategy() Applicator().apply(basket, basket.owner, bundle_id=program_uuid) # Our discount should be applied, and each line should have a price of 0 lines = basket.all_lines() self.assertEqual(len(basket.offer_applications), 1) self.assertEqual(basket.total_discount, Decimal(100) * len(lines)) for line in lines: self.assertEqual(line.line_price_incl_tax_incl_discounts, 0) # Reset the basket and add a voucher. basket.reset_offer_applications() product_range = RangeFactory(products=products) voucher, __ = factories.prepare_voucher(_range=product_range, benefit_value=50) self.mock_account_api(self.request, basket.owner.username, data={'is_active': True}) self.client.login(username=basket.owner.username, password=self.password) self.client.post(reverse('basket:vouchers-add'), data={'code': voucher.code}) response = self.client.get(reverse('basket:summary')) basket = response.context['basket'] # Verify that voucher-based offer takes precedence over program offer. actual_offer_discounts = [ discount['offer'] for discount in basket.offer_discounts ] actual_voucher_discounts = [ discount['offer'] for discount in basket.voucher_discounts ] self.assertEqual(actual_offer_discounts, []) self.assertEqual(actual_voucher_discounts, [voucher.offers.first()]) lines = basket.all_lines() self.assertEqual(len(basket.offer_applications), 1) self.assertEqual(basket.total_discount, Decimal(50) * len(lines)) for line in lines: self.assertEqual(line.line_price_incl_tax_incl_discounts, 50)