示例#1
0
    def test_offer(self):
        # Our offer is for 100%, so all lines should end up with a price of 0.
        offer = factories.ProgramOfferFactory(
            site=self.site,
            benefit=factories.PercentageDiscountBenefitWithoutRangeFactory(value=100)
        )
        basket = factories.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_enrollment_api(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)

        # 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)
        basket.vouchers.add(voucher)

        # Apply offers and verify that voucher-based offer takes precedence over program offer
        Applicator().apply(basket, basket.owner)
        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)
示例#2
0
    def test_is_satisfied_no_enrollments(self):
        """ The method should return True if the basket contains one course run seat corresponding to each
        course in the program. """
        offer = factories.ProgramOfferFactory(partner=self.partner,
                                              condition=self.condition)
        basket = BasketFactory(site=self.site, owner=UserFactory())
        program = self.mock_program_detail_endpoint(
            self.condition.program_uuid,
            self.site_configuration.discovery_api_url)

        # Extract one audit and one verified seat for each course
        audit_seats = []
        verified_seats = []

        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:
                    verified_seats.append(seat)
                else:
                    audit_seats.append(seat)

        self.mock_user_data(basket.owner.username)
        # Empty baskets should never be satisfied
        basket.flush()
        self.assertTrue(basket.is_empty)
        self.assertFalse(self.condition.is_satisfied(offer, basket))

        # Adding seats of all the courses with the wrong seat type should NOT satisfy the condition.
        basket.flush()
        for seat in audit_seats:
            basket.add_product(seat)
        self.assertFalse(self.condition.is_satisfied(offer, basket))

        # All courses must be represented in the basket.
        # NOTE: We add all but the first verified seat to ensure complete branch coverage of the method.
        basket.flush()
        for verified_seat in verified_seats[1:len(verified_seats)]:
            basket.add_product(verified_seat)
        self.assertFalse(self.condition.is_satisfied(offer, basket))

        # The condition should be satisfied if one valid course run from each course is in the basket.
        basket.add_product(verified_seats[0])
        self.assertTrue(self.condition.is_satisfied(offer, basket))

        # If the user is enrolled with the wrong seat type for courses missing from their basket that are
        # needed for the program, the condition should NOT be satisfied
        basket.flush()
        for verified_seat in verified_seats[1:len(verified_seats)]:
            basket.add_product(verified_seat)
        self.assertFalse(self.condition.is_satisfied(offer, basket))
示例#3
0
    def test_save_edit(self):
        """ Previously-created ConditionalOffer, Benefit, and Condition instances should be updated. """
        offer = factories.ProgramOfferFactory()
        data = self.generate_data(program_uuid=offer.condition.program_uuid,
                                  benefit_type=Benefit.FIXED)
        self.mock_program_detail_endpoint(data['program_uuid'])
        form = ProgramOfferForm(request=self.request,
                                data=data,
                                instance=offer)
        form.is_valid()
        form.save()

        offer.refresh_from_db()
        self.assert_program_offer_conditions(offer, data['program_uuid'],
                                             data['benefit_value'],
                                             data['benefit_type'])
示例#4
0
    def test_is_satisfied_with_enrollments(self):
        """ The condition should be satisfied if one valid course run from each course is in either the
        basket or the user's enrolled courses and the site has enabled partial program offers. """
        offer = factories.ProgramOfferFactory(partner=self.partner, condition=self.condition)
        basket = BasketFactory(site=self.site, owner=UserFactory())
        program = self.mock_program_detail_endpoint(
            self.condition.program_uuid, self.site_configuration.discovery_api_url
        )

        # Extract one verified seat for each course
        verified_seats = []
        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:
                    verified_seats.append(seat)

        # Add verified enrollments for the first two program courses to the mock user data
        enrollments = [
            {'mode': 'verified', 'course_details': {'course_id': program['courses'][0]['course_runs'][0]['key']}},
            {'mode': 'verified', 'course_details': {'course_id': program['courses'][1]['course_runs'][0]['key']}}
        ]
        self.mock_user_data(basket.owner.username, owned_products=enrollments)

        # If the user has not added all of the remaining courses in the program to their basket,
        # the condition should not be satisfied
        basket.flush()
        for seat in verified_seats[2:len(verified_seats) - 1]:
            basket.add_product(seat)
        self.assertFalse(self.condition.is_satisfied(offer, basket))

        # When all courses in the program that the user is not already enrolled in are in their basket
        # and the site allows partial program completion, the condition should be satisfied
        basket.add_product(verified_seats[-1])
        self.assertTrue(self.condition.is_satisfied(offer, basket))

        # If the site does not allow partial program completion and the user does not have all of the program
        # courses in their basket, the condition should not be satisfied
        basket.site.siteconfiguration.enable_partial_program = False
        self.assertFalse(self.condition.is_satisfied(offer, basket))

        # Verify the user enrollments are cached
        basket.site.siteconfiguration.enable_partial_program = True
        httpretty.disable()
        with mock.patch('ecommerce.programs.conditions.get_program',
                        return_value=program):
            self.assertTrue(self.condition.is_satisfied(offer, basket))
示例#5
0
    def test_is_satisfied_with_exception_for_enrollments(self):
        """ The method should return True despite having an error at the enrollment check, given 1 course run seat
        corresponding to each course in the program. """
        offer = factories.ProgramOfferFactory(site=self.site, condition=self.condition)
        basket = factories.BasketFactory(site=self.site, owner=factories.UserFactory())
        program = self.mock_program_detail_endpoint(
            self.condition.program_uuid,
            self.site_configuration.discovery_api_url
        )
        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:
                    basket.add_product(seat)

        self.mock_user_data(basket.owner.username, mocked_api='enrollments', owned_products=None, response_code=400)
        self.assertTrue(self.condition.is_satisfied(offer, basket))
示例#6
0
    def test_is_satisfied_with_non_active_program(self):
        """
        Is satisfied should return false if program is not active.
        """
        offer = factories.ProgramOfferFactory(partner=self.partner,
                                              condition=self.condition)
        basket = BasketFactory(site=self.site, owner=UserFactory())
        program = self.mock_program_detail_endpoint(
            self.condition.program_uuid,
            self.site_configuration.discovery_api_url,
            status="retired")
        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:
                    basket.add_product(seat)
                    break

        self.assertFalse(self.condition.is_satisfied(offer, basket))
示例#7
0
    def test_save_edit(self):
        """ Previously-created ConditionalOffer, Benefit, and Condition instances should be updated. """
        offer = factories.ProgramOfferFactory()
        data = self.generate_data(program_uuid=offer.condition.program_uuid,
                                  benefit_type=Benefit.FIXED)
        self.mock_program_detail_endpoint(
            data['program_uuid'], self.site_configuration.discovery_api_url)
        form = ProgramOfferForm(request=self.request,
                                data=data,
                                instance=offer)
        form.is_valid()
        form.save()

        offer.refresh_from_db()
        expected_name = data[
            'current_date'] + ' Discount for the Test Program MicroMockers Program'
        self.assert_program_offer_conditions(offer, data['program_uuid'],
                                             data['benefit_value'],
                                             data['benefit_type'],
                                             expected_name)
    def test_is_satisfied(self):
        """ The method should return True if the basket contains one course run seat corresponding to each
        course in the program. """
        offer = factories.ProgramOfferFactory(condition=self.condition)
        basket = factories.BasketFactory(site=self.site)
        program = self.mock_program_detail_endpoint(
            self.condition.program_uuid)

        # Extract one audit and one verified seat for each course
        audit_seats = []
        verified_seats = []

        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:
                    verified_seats.append(seat)
                else:
                    audit_seats.append(seat)

        # Empty baskets should never be satisfied
        basket.flush()
        self.assertTrue(basket.is_empty)
        self.assertFalse(self.condition.is_satisfied(offer, basket))

        # Adding seats of all the courses with the wrong seat type should NOT satisfy the condition.
        basket.flush()
        for seat in audit_seats:
            basket.add_product(seat)
        self.assertFalse(self.condition.is_satisfied(offer, basket))

        # All courses must be represented in the basket.
        # NOTE: We add all but the first verified seat to ensure complete branch coverage of the method.
        basket.flush()
        for verified_seat in verified_seats[:len(verified_seats) - 1]:
            basket.add_product(verified_seat)
        self.assertFalse(self.condition.is_satisfied(offer, basket))

        # The condition should be satisfied if one valid course run from each course is in the basket.
        basket.add_product(verified_seats[len(verified_seats) - 1])
        self.assertTrue(self.condition.is_satisfied(offer, basket))
示例#9
0
    def test_is_satisfied_with_entitlements(self):
        """
        The condition should be satisfied if, for each course in the program, their is either an entitlement sku in the
        basket or the user already has an entitlement for the course and the site has enabled partial program offers.
        """
        offer = factories.ProgramOfferFactory(partner=self.partner,
                                              condition=self.condition)
        basket = factories.BasketFactory(site=self.site,
                                         owner=factories.UserFactory())
        program = self.mock_program_detail_endpoint(
            self.condition.program_uuid,
            self.site_configuration.discovery_api_url)
        entitlements_response = {
            "count":
            0,
            "num_pages":
            1,
            "current_page":
            1,
            "results": [{
                'mode': 'verified',
                'course_uuid': '268afbfc-cc1e-415b-a5d8-c58d955bcfc3'
            }, {
                'mode': 'verified',
                'course_uuid': '268afbfc-cc1e-415b-a5d8-c58d955bcfc4'
            }],
            "next":
            None,
            "start":
            0,
            "previous":
            None
        }

        # Extract one verified seat for each course
        verified_entitlements = []
        course_uuids = set([course['uuid'] for course in program['courses']])
        for parent_entitlement in Product.objects.filter(
                product_class__name=COURSE_ENTITLEMENT_PRODUCT_CLASS_NAME,
                structure=Product.PARENT):
            for entitlement in Product.objects.filter(
                    parent=parent_entitlement):
                if entitlement.attr.UUID in course_uuids and entitlement.attr.certificate_type == 'verified':
                    verified_entitlements.append(entitlement)

        self.mock_user_data(basket.owner.username,
                            mocked_api='entitlements',
                            owned_products=entitlements_response)
        self.mock_user_data(basket.owner.username)
        # If the user has not added all of the remaining courses in program to their basket,
        # the condition should not be satisfied
        basket.flush()
        for entitlement in verified_entitlements[2:len(verified_entitlements) -
                                                 1]:
            basket.add_product(entitlement)
        self.assertFalse(self.condition.is_satisfied(offer, basket))

        # When all courses in the program that the user is not already enrolled in are in their basket
        # and the site allows partial program completion, the condition should be satisfied
        basket.add_product(verified_entitlements[-1])
        self.assertTrue(self.condition.is_satisfied(offer, basket))

        # If the site does not allow partial program completion and the user does not have all of the program
        # courses in their basket, the condition should not be satisfied
        basket.site.siteconfiguration.enable_partial_program = False
        self.assertFalse(self.condition.is_satisfied(offer, basket))

        # Verify the user enrollments are cached
        basket.site.siteconfiguration.enable_partial_program = True
        httpretty.disable()
        with mock.patch('ecommerce.programs.conditions.get_program',
                        return_value=program):
            self.assertTrue(self.condition.is_satisfied(offer, basket))
示例#10
0
    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)
示例#11
0
 def test_is_satisfied_site_mismatch(self):
     """ Ensure the condition returns False if the offer site does not match the basket site. """
     offer = factories.ProgramOfferFactory(site=SiteConfigurationFactory().site, condition=self.condition)
     basket = factories.BasketFactory(site=self.site, owner=factories.UserFactory())
     basket.add_product(self.test_product)
     self.assertFalse(self.condition.is_satisfied(offer, basket))
示例#12
0
 def test_clean_with_conflicting_program_uuid(self):
     """ If an offer already exists for the given program, an error should be raised. """
     offer = factories.ProgramOfferFactory()
     data = self.generate_data(program_uuid=offer.condition.program_uuid)
     self.assert_form_errors(data, {'program_uuid': ['An offer already exists for this program.']})