예제 #1
0
    def test_voucher_offers_listing_for_a_single_course_voucher(self):
        """ Verify the endpoint returns offers data when a single product is in voucher range. """
        course, seat = self.create_course_and_seat()
        self.mock_dynamic_catalog_single_course_runs_api(course)
        new_range = RangeFactory(products=[seat, ])
        new_range.catalog = Catalog.objects.create(partner=self.partner)
        new_range.catalog.stock_records.add(StockRecord.objects.get(product=seat))
        voucher, __ = prepare_voucher(_range=new_range, benefit_value=10)
        request = self.prepare_offers_listing_request(voucher.code)
        response = self.endpointView(request)
        self.assertEqual(response.status_code, 200)

        new_range.remove_product(seat)
        response = self.endpointView(request)
        self.assertEqual(response.status_code, 404)
예제 #2
0
    def prepare_voucher(self, range_=None, start_datetime=None, benefit_value=100):
        """ Create a voucher and add an offer to it that contains a created product. """
        if range_ is None:
            product = ProductFactory(title='Test product')
            range_ = RangeFactory(products=[product, ])
        else:
            product = range_.all_products()[0]

        if start_datetime is None:
            start_datetime = now() - datetime.timedelta(days=1)

        voucher = VoucherFactory(code='COUPONTEST', start_datetime=start_datetime, usage=Voucher.SINGLE_USE)
        benefit = BenefitFactory(range=range_, value=benefit_value)
        offer = ConditionalOfferFactory(benefit=benefit)
        voucher.offers.add(offer)
        return voucher, product
예제 #3
0
    def test_multiple_vouchers(self):
        """ Verify only the last entered voucher is contained in the basket. """
        product = ProductFactory(stockrecords__price_excl_tax=100)
        new_range = RangeFactory(products=[
            product,
        ])
        voucher1, __ = prepare_voucher(code='TEST1',
                                       _range=new_range,
                                       benefit_value=10)
        basket = prepare_basket(self.request, [product], voucher1)
        self.assertEqual(basket.vouchers.count(), 1)
        self.assertEqual(basket.vouchers.first(), voucher1)

        voucher2, __ = prepare_voucher(code='TEST2',
                                       _range=new_range,
                                       benefit_value=20)
        new_basket = prepare_basket(self.request, [product], voucher2)
        self.assertEqual(basket, new_basket)
        self.assertEqual(new_basket.vouchers.count(), 1)
        self.assertEqual(new_basket.vouchers.first(), voucher2)
예제 #4
0
    def test_prepare_basket_applies_valid_voucher_argument(self):
        """
            Tests that prepare_basket applies a valid voucher passed as an
            an argument, even when there is also a valid voucher already on
            the basket.
        """
        product = ProductFactory(stockrecords__partner__short_code='test1', stockrecords__price_excl_tax=100)
        new_range = RangeFactory(products=[product])
        new_voucher, __ = prepare_voucher(code='xyz', _range=new_range, benefit_value=10)
        existing_voucher, __ = prepare_voucher(code='test', _range=new_range, benefit_value=50)

        basket = BasketFactory(owner=self.request.user, site=self.request.site)
        basket.vouchers.add(existing_voucher)
        self.assertEqual(basket.vouchers.count(), 1)

        basket = prepare_basket(self.request, [product], new_voucher)
        self.assertIsNotNone(basket)
        self.assertEqual(basket.vouchers.count(), 1)
        self.assertEqual(basket.vouchers.first().code, 'XYZ')
        self.assertEqual(basket.total_discount, 10.00)
예제 #5
0
    def test_prepare_basket_applies_existing_basket_valid_voucher(self):
        """
        Tests that prepare_basket applies an existing basket voucher that is valid
        for multiple products when used to purchase any of those products.
        """
        product = ProductFactory(stockrecords__partner__short_code='test1', stockrecords__price_excl_tax=100)

        new_range = RangeFactory(products=[product])
        voucher, __ = prepare_voucher(_range=new_range, benefit_value=10)

        basket = BasketFactory(owner=self.request.user, site=self.request.site)
        basket.vouchers.add(voucher)
        self.assertEqual(basket.vouchers.count(), 1)

        basket = prepare_basket(self.request, [product])
        self.assertIsNotNone(basket)
        self.assertEqual(basket.vouchers.count(), 1)
        self.assertEqual(basket.lines.first().product, product)
        self.assertIsNotNone(basket.applied_offers())
        self.assertEqual(basket.total_discount, 10.00)
예제 #6
0
 def prepare_course_information(self):
     """ Helper function to prepare an API endpoint that provides course information. """
     course = CourseFactory()
     seat = course.create_or_update_seat('verified', True, 50, self.partner)
     sr = StockRecord.objects.get(product=seat)
     catalog = Catalog.objects.create(name='Test catalog', partner=self.partner)
     catalog.stock_records.add(sr)
     range_ = RangeFactory(catalog=catalog)
     course_info = {
         "media": {
             "course_image": {
                 "uri": "/asset-v1:edX+DemoX+Demo_Course+type@asset+block@images_course_image.jpg"
             }
         },
         "name": "edX Demonstration Course",
     }
     course_info_json = json.dumps(course_info)
     course_url = get_lms_url('api/courses/v1/courses/{}/'.format(course.id))
     httpretty.register_uri(httpretty.GET, course_url, body=course_info_json, content_type='application/json')
     return range_
예제 #7
0
 def test_apply_voucher_on_basket_and_check_discount_with_multiple_vouchers(
         self):
     """
     Tests apply_voucher_on_basket_and_check_discount when called with a basket already
     containing a valid voucher it only checks the new voucher.
     """
     basket = BasketFactory(owner=self.request.user, site=self.request.site)
     product = ProductFactory(stockrecords__partner__short_code='test1',
                              stockrecords__price_excl_tax=10)
     invalid_voucher, __ = prepare_voucher(code='TEST1')
     valid_voucher, __ = prepare_voucher(
         code='TEST2', _range=RangeFactory(products=[product]))
     basket.add_product(product, 1)
     basket.vouchers.add(valid_voucher)
     applied, msg = apply_voucher_on_basket_and_check_discount(
         invalid_voucher, self.request, basket)
     self.assertEqual(applied, False)
     self.assertEqual(
         msg, 'Basket does not qualify for coupon code {code}.'.format(
             code=invalid_voucher.code))
예제 #8
0
    def test_prepare_basket_with_voucher(self):
        """ Verify a basket is returned and contains a voucher and the voucher is applied. """
        # Prepare a product with price of 100 and a voucher with 10% discount for that product.
        product = ProductFactory(stockrecords__price_excl_tax=100)
        new_range = RangeFactory(products=[
            product,
        ])
        voucher, product = prepare_voucher(_range=new_range, benefit_value=10)

        stock_record = StockRecord.objects.get(product=product)
        self.assertEqual(stock_record.price_excl_tax, 100.00)

        basket = prepare_basket(self.request, product, voucher)
        self.assertIsNotNone(basket)
        self.assertEqual(basket.status, Basket.OPEN)
        self.assertEqual(basket.lines.count(), 1)
        self.assertEqual(basket.lines.first().product, product)
        self.assertEqual(basket.vouchers.count(), 1)
        self.assertIsNotNone(basket.applied_offers())
        self.assertEqual(basket.total_discount, 10.00)
        self.assertEqual(basket.total_excl_tax, 90.00)
예제 #9
0
    def prepare_url_for_credit_seat(self, code='CREDIT', enterprise_customer=None):
        """Helper method for creating a credit seat and construct the URL to its offer landing page.

        Returns:
            URL to its offer landing page.
        """
        __, credit_seat = self.create_course_and_seat(seat_type='credit')
        self.credit_seat = credit_seat
        # Make sure to always pair `course_seat_types` and `catalog_query` parameters because
        # if one of them is missing it could result in a SEGFAULT error when running tests
        # with migrations enabled.
        range_kwargs = {
            'products': [credit_seat],
            'course_seat_types': 'credit',
            'catalog_query': '*:*',
            'enterprise_customer': enterprise_customer,
        }
        _range = RangeFactory(**range_kwargs)
        prepare_voucher(code=code, _range=_range)

        return format_url(path=self.path, params={'code': code})
예제 #10
0
    def test_with_expired_voucher(self):
        """ Test creates order when called with basket with expired voucher"""
        basket = create_basket()
        product = ProductFactory(stockrecords__price_excl_tax=100,
                                 stockrecords__partner=self.partner,
                                 stockrecords__price_currency='USD')
        voucher, product = prepare_voucher(
            code='TEST101', _range=RangeFactory(products=[product]))
        self.request.user = UserFactory()
        basket.add_product(product)
        basket.vouchers.add(voucher)
        basket.status = 'Frozen'
        basket.save()

        PaymentProcessorResponse.objects.create(basket=basket,
                                                transaction_id='PAY-123',
                                                processor_name='paypal',
                                                response={'state': 'approved'})
        with mock.patch('oscar.apps.offer.applicator.Applicator.apply',
                        side_effect=ValueError):
            assert FulfillFrozenBaskets().fulfill_basket(basket.id, self.site)
예제 #11
0
    def test_course_information_error(self):
        """ Verify a response is returned when course information is not accessable. """
        course = CourseFactory()
        seat = course.create_or_update_seat('verified', True, 50, self.partner)
        _range = RangeFactory(products=[
            seat,
        ])
        prepare_voucher(code=COUPON_CODE, _range=_range)

        course_url = get_lms_url('api/courses/v1/courses/{}/'.format(
            course.id))
        httpretty.register_uri(httpretty.GET,
                               course_url,
                               status=404,
                               content_type=CONTENT_TYPE)

        response = self.client.get(self.path_with_code)
        response_text = (
            'Could not get course information. '
            '[Client Error 404: http://lms.testserver.fake/api/courses/v1/courses/{}/]'
        ).format(course.id)
        self.assertEqual(response.context['error'], _(response_text))
예제 #12
0
    def test_success_with_cybersource(self):
        """ Test basket with cybersource payment basket."""
        product_price = 100
        percentage_discount = 10
        product = ProductFactory(stockrecords__price_excl_tax=product_price)
        voucher, product = prepare_voucher(
            _range=RangeFactory(products=[product]),
            benefit_value=percentage_discount)
        self.request.user = UserFactory()
        basket = prepare_basket(self.request, [product], voucher)
        basket.status = 'Frozen'
        basket.save()

        card_number = '4111111111111111'
        response = {
            'req_card_number': card_number,
            'req_card_type': CARD_TYPES['visa']['cybersource_code'],
            u'decision': u'ACCEPT',
        }
        PaymentProcessorResponse.objects.create(basket=basket,
                                                transaction_id='abc',
                                                processor_name='cybersource',
                                                response=response)
        assert FulfillFrozenBaskets().fulfill_basket(basket.id, self.site)

        order = Order.objects.get(number=basket.order_number)
        assert order.status == 'Complete'

        total = product_price * (100 - percentage_discount) / 100.
        Source.objects.get(source_type__name=Cybersource.NAME,
                           currency=order.currency,
                           amount_allocated=total,
                           amount_debited=total,
                           label=card_number,
                           card_type='visa')
        PaymentEvent.objects.get(event_type__name=PaymentEventTypeName.PAID,
                                 amount=total,
                                 processor_name=Cybersource.NAME)
예제 #13
0
    def test_prepare_basket_with_bundle_voucher(self):
        """
        Test prepare_basket clears vouchers for a bundle
        """
        product = ProductFactory(stockrecords__price_excl_tax=100)
        new_range = RangeFactory(products=[
            product,
        ])
        voucher, __ = prepare_voucher(_range=new_range, benefit_value=10)

        request = self.request
        basket = prepare_basket(request, [product], voucher)
        self.assertTrue(basket.vouchers.all())
        request.GET = {'bundle': TEST_BUNDLE_ID}
        program_data = {
            'marketing_slug': 'program-slug',
            'title': 'program title',
            'type': 'micromasters'
        }
        with mock.patch('ecommerce.extensions.basket.utils.get_program',
                        return_value=program_data):
            basket = prepare_basket(request, [product])
        self.assertFalse(basket.vouchers.all())
예제 #14
0
    def test_course_information_error(self):
        """ Verify a response is returned when course information is not accessable. """
        course = CourseFactory()
        seat = course.create_or_update_seat('verified', True, 50, self.partner)
        range_ = RangeFactory(products=[
            seat,
        ])
        self.prepare_voucher(range_=range_)

        course_url = get_lms_url('api/courses/v1/courses/{}/'.format(
            course.id))
        httpretty.register_uri(httpretty.GET,
                               course_url,
                               status=404,
                               content_type='application/json')

        url = self.offer_url + '?code={}'.format('COUPONTEST')
        response = self.client.get(url)
        response_text = (
            'Could not get course information. '
            '[Client Error 404: http://127.0.0.1:8000/api/courses/v1/courses/{}/]'
        ).format(course.id)
        self.assertEqual(response.context['error'], _(response_text))
    def test_provider_fields(self, benefit_type, discount):
        code = 'TEST'
        seat = self.course.create_or_update_seat(
            'credit',
            True,
            self.price,
            self.provider,
            credit_hours=self.credit_hours)
        new_range = RangeFactory(products=[
            seat,
        ])
        prepare_voucher(code=code,
                        _range=new_range,
                        benefit_value=100,
                        benefit_type=benefit_type)
        self._mock_eligibility_api(body=self.eligibilities)
        self._mock_providers_api(body=self.provider_data)

        response = self.client.get('{}?code={}'.format(self.path, code))
        self.assertEqual(response.status_code, 200)
        provider_info = response.context['providers'][0]

        self.assertEqual(provider_info['new_price'], '0.00')
        self.assertEqual(provider_info['discount'], discount)
예제 #16
0
    def test_site(self):
        """ Verify the site is stored in the session. """
        user = self.create_user(is_staff=True)
        self.client.login(username=user.username, password=self.password)
        site_configuration = SiteConfigurationFactory()
        site = site_configuration.site

        self.assertEqual(ConditionalOffer.objects.count(), 0)

        # Start creating the offer by defining by setting the name and site
        metadata = {
            'name': 'Test Offer',
            'description': 'Blah!',
            'site': site.id,
        }
        metadata_url = reverse('dashboard:offer-metadata')
        response = self.client.post(metadata_url, metadata)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response['Location'],
                         reverse('dashboard:offer-benefit'))

        # Ensure the Site ID is stored in the session
        actual = json.loads(
            self.client.session['offer_wizard']['metadata'])['data']['site_id']
        self.assertEqual(actual, site.id)

        # Set the offer benfit data
        offer_range = RangeFactory()
        data = {
            'range': offer_range.id,
            'type': Benefit.PERCENTAGE,
            'value': 100
        }
        response = self.client.post(response['Location'], data)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response['Location'],
                         reverse('dashboard:offer-condition'))

        # Set the restrictions on the offer
        restrictions_url = reverse('dashboard:offer-restrictions')
        data = {'range': offer_range.id, 'type': Condition.COUNT, 'value': 1}
        response = self.client.post(response['Location'], data)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response['Location'], restrictions_url)

        # Reload the first page to exercise _fetch_form_kwargs, which should pull the site from the session
        response = self.client.get(metadata_url)
        self.assertEqual(response.status_code, 200)

        # Finish saving the offer
        data = {}
        response = self.client.post(restrictions_url, data)
        self.assertEqual(response.status_code, 302)

        self.assertEqual(ConditionalOffer.objects.count(), 1)
        offer = ConditionalOffer.objects.first()
        self.assertEqual(
            response['Location'],
            reverse('dashboard:offer-detail', kwargs={'pk': offer.pk}))

        # Ensure the offer is associated to the partner set in the first step of the wizard
        self.assertEqual(offer.partner, site_configuration.partner)
예제 #17
0
 def setUp(self):
     product = create_product(price=100)
     self.offer_range = RangeFactory(products=[product])
     self.offer_condition = ConditionFactory(range=self.offer_range,
                                             value=2)
    def setUp(self):
        """
        Create test data.
        """
        super(MigrateEnterpriseConditionalOffersTests, self).setUp()

        # Set up vouchers that relate to a range with a enterprise_customer
        uuid = '123e4567-e89b-12d3-a456-426655440000'
        range_with_ent_customer = RangeFactory(enterprise_customer=uuid)
        condition = ConditionFactory(range=range_with_ent_customer)
        benefit_percent = BenefitFactory(
            range=range_with_ent_customer,
            type='Percentage',
            value=10.00,
        )
        benefit_absolute = BenefitFactory(
            range=range_with_ent_customer,
            type='Absolute',
            value=47,
        )

        for i in range(2):
            code = '{}EntUserPercentBenefit'.format(i)
            voucher = VoucherFactory(code=code)
            offer_name = "Coupon [{}]-{}-{}".format(voucher.pk,
                                                    benefit_percent.type,
                                                    benefit_percent.value)
            conditional_offer = ConditionalOfferFactory(
                condition=condition,
                benefit=benefit_percent,
                name=offer_name,
            )
            voucher.offers.add(conditional_offer)

        for i in range(2):
            code = '{}EntUserAbsoluteBenefit'.format(i)
            voucher = VoucherFactory(code=code)
            offer_name = "Coupon [{}]-{}-{}".format(voucher.pk,
                                                    benefit_absolute.type,
                                                    benefit_absolute.value)
            conditional_offer = ConditionalOfferFactory(
                condition=condition,
                benefit=benefit_absolute,
                name=offer_name,
            )
            voucher.offers.add(conditional_offer)

        # Set up vouchers that do not relate to a range with an enterprise_customer
        range_no_ent_customer = RangeFactory()
        condition = ConditionFactory(range=range_no_ent_customer)
        benefit = BenefitFactory(
            range=range_no_ent_customer,
            type='Percentage',
            value=10.00,
        )

        for i in range(3):
            code = '{}NoEntUserPercentBenefit'.format(i)
            voucher = VoucherFactory(code=code)
            offer_name = "Coupon [{}]-{}-{}".format(voucher.pk, benefit.type,
                                                    benefit.value)
            conditional_offer = ConditionalOfferFactory(
                condition=condition,
                benefit=benefit,
                name=offer_name,
            )
            voucher.offers.add(conditional_offer)

        assert Voucher.objects.filter(
            offers__condition__range__enterprise_customer__isnull=False).count(
            ) == 4

        assert Voucher.objects.filter(
            offers__condition__range__enterprise_customer__isnull=True).count(
            ) == 3

        self.command = Command()
예제 #19
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)