def setUp(self): super(CheckoutPageTest, self).setUp() self.switch = toggle_switch('ENABLE_CREDIT_APP', True) user = self.create_user(is_superuser=False) self.create_access_token(user) self.client.login(username=user.username, password=self.password) self.course_name = 'credit course' self.provider = 'ASU' self.price = 100 self.thumbnail_url = 'http://www.edx.org/course.jpg' self.credit_hours = 2 self.eligibility_url = get_lms_url('/api/credit/v1/eligibility/') self.provider_url = get_lms_url('/api/credit/v1/providers/') # Create the course self.course = Course.objects.create(id='edx/Demo_Course/DemoX', name=self.course_name, thumbnail_url=self.thumbnail_url) self.provider_data = [{ 'enable_integration': False, 'description': 'Arizona State University', 'url': 'https://credit.example.com/', 'status_url': 'https://credit.example.com/status', 'thumbnail_url': 'http://edX/DemoX/asset/images_course_image.jpg', 'fulfillment_instructions': 'Sample fulfilment requirement.', 'display_name': 'Arizona State University', 'id': 'ASU' }] self.eligibilities = [{ 'deadline': '2016-10-28T09:56:44Z', 'course_key': 'edx/cs01/2015' }]
def get_context_data(self, **kwargs): context = super(CouponOfferView, self).get_context_data(**kwargs) code = self.request.GET.get('code', None) if code is not None: voucher, product = get_voucher(code=code) valid_voucher, msg = voucher_is_valid(voucher, product, self.request) if valid_voucher: api = EdxRestApiClient( get_lms_url('api/courses/v1/'), ) try: course = api.courses(product.course_id).get() except SlumberHttpBaseException as e: logger.exception('Could not get course information. [%s]', e) return { 'error': _('Could not get course information. [{error}]'.format(error=e)) } course['image_url'] = get_lms_url(course['media']['course_image']['uri']) stock_records = voucher.offers.first().benefit.range.catalog.stock_records.first() context.update({ 'course': course, 'code': code, 'price': stock_records.price_excl_tax, 'verified': (product.attr.certificate_type is 'verified') }) return context return { 'error': msg } return { 'error': _('This coupon code is invalid.') }
def get_context_data(self, **kwargs): context = super(CouponOfferView, self).get_context_data(**kwargs) footer = get_lms_footer() code = self.request.GET.get('code', None) if code is not None: voucher, product = get_voucher(code=code) valid_voucher, msg = voucher_is_valid(voucher, product, self.request) if valid_voucher: api = EdxRestApiClient(get_lms_url('api/courses/v1/'), ) try: course = api.courses(product.course_id).get() except SlumberHttpBaseException as e: logger.exception('Could not get course information. [%s]', e) return { 'error': _('Could not get course information. [{error}]'.format( error=e)), 'footer': footer } course['image_url'] = get_lms_url( course['media']['course_image']['uri']) stock_records = voucher.offers.first( ).benefit.range.catalog.stock_records.first() benefit_type = voucher.offers.first().benefit.type benefit_value = voucher.offers.first().benefit.value price = stock_records.price_excl_tax if benefit_type == 'Percentage': new_price = price - (price * (benefit_value / 100)) else: new_price = price - benefit_value if new_price < 0: new_price = 0.00 context.update({ 'benefit_type': benefit_type, 'benefit_value': benefit_value, 'course': course, 'code': code, 'price': price, 'new_price': "%.2f" % new_price, 'verified': (product.attr.certificate_type == 'verified'), 'footer': footer }) return context return {'error': msg, 'footer': footer} return {'error': _('This coupon code is invalid.'), 'footer': footer}
def get_context_data(self, **kwargs): context = super(CouponOfferView, self).get_context_data(**kwargs) footer = get_lms_footer() code = self.request.GET.get('code', None) if code is not None: voucher, product = get_voucher_from_code(code=code) valid_voucher, msg = voucher_is_valid(voucher, product, self.request) if valid_voucher: api = EdxRestApiClient( get_lms_url('api/courses/v1/'), ) try: course = api.courses(product.course_id).get() except SlumberHttpBaseException as e: logger.exception('Could not get course information. [%s]', e) return { 'error': _('Could not get course information. [{error}]'.format(error=e)), 'footer': footer } course['image_url'] = get_lms_url(course['media']['course_image']['uri']) benefit = voucher.offers.first().benefit stock_record = benefit.range.catalog.stock_records.first() price = stock_record.price_excl_tax context.update(get_voucher_discount_info(benefit, price)) if benefit.type == 'Percentage': new_price = price - (price * (benefit.value / 100)) else: new_price = price - benefit.value if new_price < 0: new_price = Decimal(0) context.update({ 'benefit': benefit, 'course': course, 'code': code, 'is_discount_value_percentage': benefit.type == 'Percentage', 'is_enrollment_code': benefit.type == Benefit.PERCENTAGE and benefit.value == 100.00, 'discount_value': "%.2f" % (price - new_price), 'price': price, 'new_price': "%.2f" % new_price, 'verified': (product.attr.certificate_type == 'verified'), 'verification_deadline': product.course.verification_deadline, 'footer': footer }) return context return { 'error': msg, 'footer': footer } return { 'error': _('This coupon code is invalid.'), 'footer': footer }
def _publish_creditcourse(self, course_id, access_token): """Creates or updates a CreditCourse object on the LMS.""" url = get_lms_url('api/credit/v1/courses/') data = { 'course_key': course_id, 'enabled': True } headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + access_token } kwargs = { 'url': url, 'data': json.dumps(data), 'headers': headers, 'timeout': self.timeout } response = requests.post(**kwargs) if response.status_code == 400: # The CreditCourse already exists. Try updating it. kwargs['url'] += course_id.strip('/') + '/' response = requests.put(**kwargs) return response
def get_provider_data(provider_id): """Get the provider information for provider id provider. Args: provider_id(str): Identifier for the provider Returns: dict """ provider_info_url = get_lms_url('api/credit/v1/providers/{}'.format(provider_id)) timeout = settings.PROVIDER_DATA_PROCESSING_TIMEOUT headers = { 'Content-Type': 'application/json', 'X-Edx-Api-Key': settings.EDX_API_KEY } try: response = requests.get(provider_info_url, headers=headers, timeout=timeout) if response.status_code == 200: return response.json() else: logger.error( 'Failed retrieve provider information for %s provider. Provider API returned status code %d. Error: %s', provider_id, response.status_code, response.text) return None except requests.exceptions.ConnectionError: logger.exception('Connection error occurred during getting data for %s provider', provider_id) return None except requests.Timeout: logger.exception('Failed to retrieve data for %s provider, connection timeout', provider_id) return None
def test_proper_code(self): """ Verify that proper information is returned when a valid code is provided. """ 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) self.prepare_voucher(range_=range_) 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') url = self.offer_url + '?code={}'.format('COUPONTEST') response = self.client.get(url) self.assertEqual(response.context['course']['name'], _('edX Demonstration Course')) self.assertEqual(response.context['code'], _('COUPONTEST'))
def send_course_purchase_email(sender, order=None, **kwargs): # pylint: disable=unused-argument """Send course purchase notification email when a course is purchased.""" if waffle.switch_is_active('ENABLE_NOTIFICATIONS'): # We do not currently support email sending for orders with more than one item. if len(order.lines.all()) == ORDER_LINE_COUNT: product = order.lines.first().product provider_id = getattr(product.attr, 'credit_provider', None) if not provider_id: logger.error( 'Failed to send credit receipt notification. Credit seat product [%s] has not provider.', product.id ) return elif product.get_product_class().name == 'Seat': provider_data = get_provider_data(provider_id) if provider_data: send_notification( order.user, 'CREDIT_RECEIPT', { 'course_title': product.title, 'receipt_page_url': get_lms_url( '/commerce/checkout/receipt/?basket_id={}'.format(order.basket.id) ), 'credit_hours': product.attr.credit_hours, 'credit_provider': provider_data['display_name'], } ) else: logger.info('Currently support receipt emails for order with one item.')
def send_course_purchase_email(sender, order=None, **kwargs): # pylint: disable=unused-argument """Send course purchase notification email when a course is purchased.""" if waffle.switch_is_active('ENABLE_NOTIFICATIONS'): # We do not currently support email sending for orders with more than one item. if len(order.lines.all()) == ORDER_LINE_COUNT: product = order.lines.first().product provider_id = getattr(product.attr, 'credit_provider', None) if not provider_id: logger.error( 'Failed to send credit receipt notification. Credit seat product [%s] has not provider.', product.id) return elif product.get_product_class().name == 'Seat': provider_data = get_provider_data(provider_id) if provider_data: send_notification( order.user, 'CREDIT_RECEIPT', { 'course_title': product.title, 'receipt_page_url': get_lms_url( '/commerce/checkout/receipt/?orderNum={}'. format(order.number)), 'credit_hours': product.attr.credit_hours, 'credit_provider': provider_data['display_name'], }) else: logger.info( 'Currently support receipt emails for order with one item.')
def credit_api_client(self): """ Returns an instance of the Credit API client. """ return EdxRestApiClient( get_lms_url('api/credit/v1/'), oauth_access_token=self.request.user.access_token )
def get_lms_footer(): """ Retrieve LMS footer via branding API. Returns: str: HTML representation of the footer. """ try: response = requests.get( get_lms_url('api/branding/v1/footer'), data={'language': 'en'} ) if response.status_code == 200: return response.text else: logger.error( 'Failed retrieve provider information for %s provider. Provider API returned status code %d. Error: %s', settings.LMS_URL_ROOT, response.status_code, response.text) return None except requests.exceptions.ConnectionError: logger.exception('Connection error occurred during getting data for %s provider', settings.LMS_URL_ROOT) return None except requests.Timeout: logger.exception('Failed to retrieve data for %s provider, connection timeout', settings.LMS_URL_ROOT) return None
def get_lms_footer(): """ Retrieve LMS footer via branding API. Returns: str: HTML representation of the footer. """ try: response = requests.get(get_lms_url('api/branding/v1/footer'), data={'language': 'en'}) if response.status_code == 200: return response.text else: logger.error( 'Failed retrieve provider information for %s provider. Provider API returned status code %d. Error: %s', settings.LMS_URL_ROOT, response.status_code, response.text) return None except requests.exceptions.ConnectionError: logger.exception( 'Connection error occurred during getting data for %s provider', settings.LMS_URL_ROOT) return None except requests.Timeout: logger.exception( 'Failed to retrieve data for %s provider, connection timeout', settings.LMS_URL_ROOT) return None
def test_proper_code(self): """ Verify that proper information is returned when a valid code is provided. """ 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) self.prepare_voucher(range_=range_) 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') url = self.offer_url + '?code={}'.format('COUPONTEST') response = self.client.get(url) self.assertEqual(response.context['course']['name'], _('edX Demonstration Course')) self.assertEqual(response.context['code'], _('COUPONTEST'))
def test_post_checkout_callback(self): """ When the post_checkout signal is emitted, the receiver should attempt to fulfill the newly-placed order and send receipt email. """ httpretty.register_uri( httpretty.GET, get_lms_url('api/credit/v1/providers/ASU'), body='{"display_name": "Hogwarts"}', content_type="application/json" ) toggle_switch('ENABLE_NOTIFICATIONS', True) user = UserFactory() course = Course.objects.create(id='edX/DemoX/Demo_Course', name='Demo Course') partner = self.create_partner('edx') seat = course.create_or_update_seat('credit', False, 50, partner, 'ASU', None, 2) basket = BasketFactory() basket.add_product(seat, 1) order = factories.create_order(number=1, basket=basket, user=user) send_course_purchase_email(None, order=order) self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].subject, 'Order Receipt') self.assertEqual( mail.outbox[0].body, '\nPayment confirmation for: {course_title}' '\n\nDear {full_name},' '\n\nThank you for purchasing {credit_hours} credit hours from {credit_provider} for {course_title}. ' 'A charge will appear on your credit or debit card statement with a company name of "{platform_name}".' '\n\nTo receive your course credit, you must also request credit at the {credit_provider} website. ' 'For a link to request credit from {credit_provider}, or to see the status of your credit request, ' 'go to your {platform_name} dashboard.' '\n\nTo explore other credit-eligible courses, visit the {platform_name} website. ' 'We add new courses frequently!' '\n\nTo view your payment information, visit the following website.' '\n{receipt_url}' '\n\nThank you. We hope you enjoyed your course!' '\nThe {platform_name} team' '\n\nYou received this message because you purchased credit hours for {course_title}, ' 'an {platform_name} course.\n'.format( course_title=order.lines.first().product.title, full_name=user.get_full_name(), credit_hours=2, credit_provider='Hogwarts', platform_name=settings.PLATFORM_NAME, receipt_url=get_lms_url('/commerce/checkout/receipt/?basket_id={}'.format(order.basket.id)) ) )
def test_post_checkout_callback(self): """ When the post_checkout signal is emitted, the receiver should attempt to fulfill the newly-placed order and send receipt email. """ httpretty.register_uri(httpretty.GET, get_lms_url('api/credit/v1/providers/ASU'), body='{"display_name": "Hogwarts"}', content_type="application/json") toggle_switch('ENABLE_NOTIFICATIONS', True) user = UserFactory() course = Course.objects.create(id='edX/DemoX/Demo_Course', name='Demo Course') seat = course.create_or_update_seat('credit', False, 50, self.partner, 'ASU', None, 2) basket = BasketFactory() basket.add_product(seat, 1) order = factories.create_order(number=1, basket=basket, user=user) send_course_purchase_email(None, order=order) self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].subject, 'Order Receipt') self.assertEqual( mail.outbox[0].body, '\nPayment confirmation for: {course_title}' '\n\nDear {full_name},' '\n\nThank you for purchasing {credit_hours} credit hours from {credit_provider} for {course_title}. ' 'A charge will appear on your credit or debit card statement with a company name of "{platform_name}".' '\n\nTo receive your course credit, you must also request credit at the {credit_provider} website. ' 'For a link to request credit from {credit_provider}, or to see the status of your credit request, ' 'go to your {platform_name} dashboard.' '\n\nTo explore other credit-eligible courses, visit the {platform_name} website. ' 'We add new courses frequently!' '\n\nTo view your payment information, visit the following website.' '\n{receipt_url}' '\n\nThank you. We hope you enjoyed your course!' '\nThe {platform_name} team' '\n\nYou received this message because you purchased credit hours for {course_title}, ' 'an {platform_name} course.\n'.format( course_title=order.lines.first().product.title, full_name=user.get_full_name(), credit_hours=2, credit_provider='Hogwarts', platform_name=settings.PLATFORM_NAME, receipt_url=get_lms_url( '/commerce/checkout/receipt/?orderNum={}'.format( order.number))))
def mock_footer_api_response(self): """ Helper function to register an API endpoint for the footer information. """ footer_url = get_lms_url('api/branding/v1/footer') footer_content = { 'footer': 'edX Footer' } content_json = json.dumps(footer_content) httpretty.register_uri(httpretty.GET, footer_url, body=content_json, content_type='application/json')
def mock_credit_api_timeout(self): """ Mock a timeout when calling the Credit API providers endpoint. """ def callback(request, uri, headers): # pylint: disable=unused-argument raise Timeout url = get_lms_url("/api/credit/v1/providers/") httpretty.register_uri(httpretty.GET, url, body=callback, content_type="application/json")
def get(self, request): """ Looks up the passed code and adds the matching product to a basket, then applies the voucher and if the basket total is FREE places the order and enrolls the user in the course. """ template_name = 'coupons/offer.html' code = request.GET.get('code', None) if not code: return render(request, template_name, {'error': _('Code not provided')}) voucher, product = get_voucher(code=code) valid_voucher, msg = voucher_is_valid(voucher, product, request) if not valid_voucher: return render(request, template_name, {'error': msg}) basket = self._prepare_basket(request.site, request.user, product, voucher) if basket.total_excl_tax == AC.FREE: basket.freeze() order_metadata = data_api.get_order_metadata(basket) logger.info( u"Preparing to place order [%s] for the contents of basket [%d]", order_metadata[AC.KEYS.ORDER_NUMBER], basket.id, ) # Place an order. If order placement succeeds, the order is committed # to the database so that it can be fulfilled asynchronously. order = self.handle_order_placement( order_number=order_metadata[AC.KEYS.ORDER_NUMBER], user=basket.owner, basket=basket, shipping_address=None, shipping_method=order_metadata[AC.KEYS.SHIPPING_METHOD], shipping_charge=order_metadata[AC.KEYS.SHIPPING_CHARGE], billing_address=None, order_total=order_metadata[AC.KEYS.ORDER_TOTAL], ) else: return render( request, template_name, { 'error': _('Basket total not $0, current value = ${basket_price}'. format(basket_price=basket.total_excl_tax)) }) if order.status is ORDER.COMPLETE: return HttpResponseRedirect(get_lms_url('')) else: logger.error('Order was not completed [%s]', order.id) return render(request, template_name, {'error': _('Error when trying to redeem code')})
def test_get_provider_data_unavailable_request(self): """ Check if None return on the bad request """ httpretty.register_uri( httpretty.GET, get_lms_url('api/credit/v1/providers/ABC'), status=400 ) provider_data = get_provider_data('ABC') self.assertEqual(provider_data, None)
def test_post_checkout_callback(self): """ When the post_checkout signal is emitted, the receiver should attempt to fulfill the newly-placed order and send receipt email. """ httpretty.register_uri( httpretty.GET, get_lms_url('api/credit/v1/providers/ASU'), body='{"display_name": "Hogwarts"}', content_type="application/json" ) toggle_switch('ENABLE_NOTIFICATIONS', True) user = UserFactory() course = Course.objects.create(id='edX/DemoX/Demo_Course', name='Demo Course') partner = self.create_partner('edx') seat = course.create_or_update_seat('credit', False, 50, partner, 'ASU', None, 2) basket = BasketFactory() basket.add_product(seat, 1) order = factories.create_order(number=1, basket=basket, user=user) send_course_purchase_email(None, order=order) self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].subject, 'Order Receipt') self.assertEqual( mail.outbox[0].body, '\nReceipt Confirmation for: {course_name}' '\n\nHi {full_name},\n\n' 'Thank you for purchasing {credit_hour} credit hours from {provider_name} for {course_name}.' ' The charge below will appear on your next credit or debit card statement with a ' 'company name of {platform_name}.\n\nYou can see the status the status of your credit request or ' 'complete the credit request process on your {platform_name} dashboard\nTo browse other ' 'credit-eligible courses visit the edX website. More courses are added all the time.\n\n' 'Thank you and congratulation on your achievement. We hope you enjoy the course!\n\n' 'To view receipt please visit the link below' '\n\n{receipt_url}\n\n' '{platform_name} team\n\nThe edX team\n'.format( course_name=order.lines.first().product.title, full_name=user.get_full_name(), credit_hour=2, provider_name='Hogwarts', platform_name=settings.PLATFORM_NAME, receipt_url=get_lms_url('/commerce/checkout/receipt/?basket_id={}'.format(order.basket.id)) ) )
def _publish_creditcourse(self, course_id, access_token): """Creates or updates a CreditCourse object on the LMS.""" api = EdxRestApiClient(get_lms_url('api/credit/v1/'), oauth_access_token=access_token, timeout=self.timeout) data = {'course_key': course_id, 'enabled': True} api.courses(course_id).put(data)
def mock_credit_api_timeout(self): """ Mock a timeout when calling the Credit API providers endpoint. """ def callback(request, uri, headers): # pylint: disable=unused-argument raise Timeout url = get_lms_url('/api/credit/v1/providers/') httpretty.register_uri(httpretty.GET, url, body=callback, content_type='application/json')
def mock_creditcourse_endpoint(self, course_id, status, body=None): self.assertTrue(httpretty.is_enabled(), 'httpretty must be enabled to mock Credit API calls.') url = get_lms_url('/api/credit/v1/courses/{}/'.format(course_id)) httpretty.register_uri( httpretty.PUT, url, status=status, body=json.dumps(body), content_type=JSON )
def get(self, request): """ Looks up the passed code and adds the matching product to a basket, then applies the voucher and if the basket total is FREE places the order and enrolls the user in the course. """ template_name = 'coupons/offer.html' code = request.GET.get('code', None) if not code: return render(request, template_name, {'error': _('Code not provided')}) voucher, product = get_voucher(code=code) valid_voucher, msg = voucher_is_valid(voucher, product, request) if not valid_voucher: return render(request, template_name, {'error': msg}) basket = self._prepare_basket(request.site, request.user, product, voucher) if basket.total_excl_tax == AC.FREE: basket.freeze() order_metadata = data_api.get_order_metadata(basket) logger.info( u"Preparing to place order [%s] for the contents of basket [%d]", order_metadata[AC.KEYS.ORDER_NUMBER], basket.id, ) # Place an order. If order placement succeeds, the order is committed # to the database so that it can be fulfilled asynchronously. order = self.handle_order_placement( order_number=order_metadata[AC.KEYS.ORDER_NUMBER], user=basket.owner, basket=basket, shipping_address=None, shipping_method=order_metadata[AC.KEYS.SHIPPING_METHOD], shipping_charge=order_metadata[AC.KEYS.SHIPPING_CHARGE], billing_address=None, order_total=order_metadata[AC.KEYS.ORDER_TOTAL], ) else: return render( request, template_name, {'error': _('Basket total not $0, current value = ${basket_price}'.format( basket_price=basket.total_excl_tax ))} ) if order.status is ORDER.COMPLETE: return HttpResponseRedirect(get_lms_url('')) else: logger.error('Order was not completed [%s]', order.id) return render(request, template_name, {'error': _('Error when trying to redeem code')})
def test_get_provider_data(self): """ Check if correct data returns on the full filled request. """ httpretty.register_uri( httpretty.GET, get_lms_url('api/credit/v1/providers/ASU'), body='{"display_name": "Arizona State University"}', content_type="application/json" ) provider_data = get_provider_data('ASU') self.assertDictEqual(provider_data, {"display_name": "Arizona State University"})
def setUp(self): super(CheckoutPageTest, self).setUp() self.switch = toggle_switch('ENABLE_CREDIT_APP', True) user = self.create_user(is_superuser=False) self.create_access_token(user) self.client.login(username=user.username, password=self.password) self.course_name = 'credit course' self.provider = 'ASU' self.price = 100 self.thumbnail_url = 'http://www.edx.org/course.jpg' self.credit_hours = 2 self.eligibility_url = get_lms_url('/api/credit/v1/eligibility/') self.provider_url = get_lms_url('/api/credit/v1/providers/') # Create the course self.course = Course.objects.create( id='edx/Demo_Course/DemoX', name=self.course_name, thumbnail_url=self.thumbnail_url ) self.provider_data = [ { 'enable_integration': False, 'description': 'Arizona State University', 'url': 'https://credit.example.com/', 'status_url': 'https://credit.example.com/status', 'thumbnail_url': 'http://edX/DemoX/asset/images_course_image.jpg', 'fulfillment_instructions': 'Sample fulfilment requirement.', 'display_name': 'Arizona State University', 'id': 'ASU' } ] self.eligibilities = [ { 'deadline': '2016-10-28T09:56:44Z', 'course_key': 'edx/cs01/2015' } ]
class DataFunctionsTests(TestCase): """ Tests for api data functions. """ footer_url = get_lms_url('api/branding/v1/footer') def setUp(self): super(DataFunctionsTests, self).setUp() @httpretty.activate def test_get_lms_footer_success(self): """ Verify footer information is retrieved. """ content = {'footer': 'edX Footer'} content_json = json.dumps(content) httpretty.register_uri(httpretty.GET, self.footer_url, body=content_json, content_type='application/json') response = json.loads(get_lms_footer()) self.assertEqual(response['footer'], 'edX Footer') @httpretty.activate def test_get_lms_footer_failure(self): """ Verify None is returned on a non-200 status code. """ httpretty.register_uri(httpretty.GET, self.footer_url, status=404, content_type='application/json') response = get_lms_footer() self.assertIsNone(response) def test_get_lms_footer_conn_error(self): """ Verify proper logger message is displayed in case of a connection error. """ with mock.patch('requests.get', side_effect=requests.exceptions.ConnectionError()): with LogCapture(LOGGER_NAME) as l: response = get_lms_footer() l.check(( LOGGER_NAME, 'ERROR', u'Connection error occurred during getting data for {lms_url} provider' .format(lms_url=settings.LMS_URL_ROOT))) self.assertIsNone(response) def test_get_lms_footer_timeout(self): """ Verify proper logger message is displayed in case of a time out. """ with mock.patch('requests.get', side_effect=requests.Timeout()): with LogCapture(LOGGER_NAME) as l: response = get_lms_footer() l.check(( LOGGER_NAME, 'ERROR', u'Failed to retrieve data for {lms_url} provider, connection timeout' .format(lms_url=settings.LMS_URL_ROOT))) self.assertIsNone(response)
def get_context_data(self, **kwargs): context = super(BasketSummaryView, self).get_context_data(**kwargs) lines = context.get('line_list', []) api = EdxRestApiClient(get_lms_url('api/courses/v1/')) for line in lines: course_id = line.product.course_id # Get each course type so we can display to the user at checkout. try: line.certificate_type = get_certificate_type_display_value(line.product.attr.certificate_type) except ValueError: line.certificate_type = None cache_key = 'courses_api_detail_{}'.format(course_id) cache_hash = hashlib.md5(cache_key).hexdigest() try: course = cache.get(cache_hash) if not course: course = api.courses(course_id).get() course['image_url'] = get_lms_url(course['media']['course_image']['uri']) cache.set(cache_hash, course, settings.COURSES_API_CACHE_TIMEOUT) line.course = course except (ConnectionError, SlumberBaseException, Timeout): logger.exception('Failed to retrieve data from Course API for course [%s].', course_id) if line.has_discount: line.discount_percentage = line.discount_value / line.unit_price_incl_tax * Decimal(100) else: line.discount_percentage = 0 context.update({ 'payment_processors': self.get_payment_processors(), 'homepage_url': get_lms_url(''), 'footer': get_lms_footer(), 'lines': lines, 'faq_url': get_lms_url('') + '/verified-certificate', }) return context
def mock_course_api_response(self, course=None): """ Helper function to register an API endpoint for the course information. """ course_info = { 'media': { 'course_image': { 'uri': '/asset-v1:test+test+test+type@asset+block@images_course_image.jpg' } }, 'name': course.name if course else 'Test course' } course_info_json = json.dumps(course_info) course_id = course.id if course else 'course-v1:test+test+test' 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')
def get_context_data(self, **kwargs): context = super(CouponOfferView, self).get_context_data(**kwargs) code = self.request.GET.get('code', None) if code is not None: voucher, product = get_voucher(code=code) valid_voucher, msg = voucher_is_valid(voucher, product, self.request) if valid_voucher: api = EdxRestApiClient(get_lms_url('api/courses/v1/'), ) try: course = api.courses(product.course_id).get() except SlumberHttpBaseException as e: logger.exception('Could not get course information. [%s]', e) return { 'error': _('Could not get course information. [{error}]'.format( error=e)) } course['image_url'] = get_lms_url( course['media']['course_image']['uri']) stock_records = voucher.offers.first( ).benefit.range.catalog.stock_records.first() context.update({ 'course': course, 'code': code, 'price': stock_records.price_excl_tax, 'verified': (product.attr.certificate_type is 'verified') }) return context return {'error': msg} return {'error': _('This coupon code is invalid.')}
def _publish_creditcourse(self, course_id, access_token): """Creates or updates a CreditCourse object on the LMS.""" api = EdxRestApiClient( get_lms_url('api/credit/v1/'), oauth_access_token=access_token, timeout=self.timeout ) data = { 'course_key': course_id, 'enabled': True } api.courses(course_id).put(data)
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://127.0.0.1:8000/api/courses/v1/courses/{}/]' ).format(course.id) self.assertEqual(response.context['error'], _(response_text))
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') response = self.client.get(self.path_with_code) 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 mock_credit_api_providers(self): """ Mock GET requests to the Credit API's provider endpoint. /api/credit/v1/providers """ self.assertTrue(httpretty.is_enabled()) providers = [ {"id": "shk", "display_name": "School of Hard Knocks"}, {"id": "acme", "display_name": "Acme University"}, ] providers.sort(key=lambda provider: provider["display_name"]) provider_json = json.dumps(providers) url = get_lms_url("/api/credit/v1/providers/") httpretty.register_uri(httpretty.GET, url, body=provider_json, content_type="application/json") return providers, provider_json
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_
def _mock_credit_api(self, creation_status, update_status, body=None): self.assertTrue(httpretty.is_enabled, 'httpretty must be enabled to mock Credit API calls.') url = get_lms_url('api/credit/v1/courses/') httpretty.register_uri( httpretty.POST, url, status=creation_status, body=json.dumps(body), content_type=JSON ) if update_status is not None: url += self.course.id.strip('/') + '/' httpretty.register_uri( httpretty.PUT, url, status=update_status, body=json.dumps(body), content_type=JSON )
def get_credit_providers(self): """ Retrieve all credit providers from LMS. Results will be sorted alphabetically by display name. """ key = 'credit_providers' credit_providers = cache.get(key, []) if not credit_providers: try: credit_api = EdxRestApiClient( get_lms_url('/api/credit/v1/'), oauth_access_token=self.request.user.access_token ) credit_providers = credit_api.providers.get() credit_providers.sort(key=lambda provider: provider['display_name']) # Update the cache cache.set(key, credit_providers, settings.CREDIT_PROVIDER_CACHE_TIMEOUT) except (SlumberBaseException, Timeout): logger.exception('Failed to retrieve credit providers!') return credit_providers
def mock_credit_api_providers(self): """ Mock GET requests to the Credit API's provider endpoint. /api/credit/v1/providers """ self.assertTrue(httpretty.is_enabled()) providers = [{ 'id': 'shk', 'display_name': 'School of Hard Knocks' }, { 'id': 'acme', 'display_name': 'Acme University' }] providers.sort(key=lambda provider: provider['display_name']) provider_json = json.dumps(providers) url = get_lms_url('/api/credit/v1/providers/') httpretty.register_uri(httpretty.GET, url, body=provider_json, content_type='application/json') return providers, provider_json
def get_provider_data(provider_id): """Get the provider information for provider id provider. Args: provider_id(str): Identifier for the provider Returns: dict """ provider_info_url = get_lms_url( 'api/credit/v1/providers/{}'.format(provider_id)) timeout = settings.PROVIDER_DATA_PROCESSING_TIMEOUT headers = { 'Content-Type': 'application/json', 'X-Edx-Api-Key': settings.EDX_API_KEY } try: response = requests.get(provider_info_url, headers=headers, timeout=timeout) if response.status_code == 200: return response.json() else: logger.error( 'Failed retrieve provider information for %s provider. Provider API returned status code %d. Error: %s', provider_id, response.status_code, response.text) return None except requests.exceptions.ConnectionError: logger.exception( 'Connection error occurred during getting data for %s provider', provider_id) return None except requests.Timeout: logger.exception( 'Failed to retrieve data for %s provider, connection timeout', provider_id) return None
'USER': '', 'PASSWORD': '', 'HOST': '', 'PORT': '', 'ATOMIC_REQUESTS': True, }, } # END IN-MEMORY TEST DATABASE # URL CONFIGURATION ECOMMERCE_URL_ROOT = 'http://localhost:8002' LMS_URL_ROOT = 'http://127.0.0.1:8000' # The location of the LMS heartbeat page LMS_HEARTBEAT_URL = get_lms_url('/heartbeat') # The location of the LMS student dashboard LMS_DASHBOARD_URL = get_lms_url('/dashboard') COMMERCE_API_URL = get_lms_url('/api/commerce/v1/') # END URL CONFIGURATION # AUTHENTICATION ENABLE_AUTO_AUTH = True JWT_AUTH.update({ 'JWT_SECRET_KEY': 'insecure-secret-key', 'JWT_ISSUERS': ('test-issuer', ), })
MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware', ) DEBUG_TOOLBAR_PATCH_SETTINGS = False # http://django-debug-toolbar.readthedocs.org/en/latest/installation.html INTERNAL_IPS = ('127.0.0.1', ) # END TOOLBAR CONFIGURATION # URL CONFIGURATION ECOMMERCE_URL_ROOT = 'http://localhost:8002' LMS_URL_ROOT = 'http://127.0.0.1:8000' # The location of the LMS heartbeat page LMS_HEARTBEAT_URL = get_lms_url('/heartbeat') # The location of the LMS student dashboard LMS_DASHBOARD_URL = get_lms_url('/dashboard') OAUTH2_PROVIDER_URL = get_lms_url('/oauth2') COMMERCE_API_URL = get_lms_url('/api/commerce/v1/') # END URL CONFIGURATION # AUTHENTICATION # Set these to the correct values for your OAuth2/OpenID Connect provider (e.g., devstack) SOCIAL_AUTH_EDX_OIDC_KEY = 'replace-me' SOCIAL_AUTH_EDX_OIDC_SECRET = 'replace-me' SOCIAL_AUTH_EDX_OIDC_URL_ROOT = OAUTH2_PROVIDER_URL SOCIAL_AUTH_EDX_OIDC_ID_TOKEN_DECRYPTION_KEY = SOCIAL_AUTH_EDX_OIDC_SECRET
def mock_course_api_error(self, error): def callback(request, uri, headers): # pylint: disable=unused-argument raise error course_url = get_lms_url('api/courses/v1/courses/{}/'.format(self.course)) httpretty.register_uri(httpretty.GET, course_url, body=callback, content_type='application/json')
MIDDLEWARE_CLASSES += ("debug_toolbar.middleware.DebugToolbarMiddleware",) DEBUG_TOOLBAR_PATCH_SETTINGS = False # http://django-debug-toolbar.readthedocs.org/en/latest/installation.html INTERNAL_IPS = ("127.0.0.1",) # END TOOLBAR CONFIGURATION # URL CONFIGURATION ECOMMERCE_URL_ROOT = "http://localhost:8002" LMS_URL_ROOT = "http://127.0.0.1:8000" # The location of the LMS heartbeat page LMS_HEARTBEAT_URL = get_lms_url("/heartbeat") # The location of the LMS student dashboard LMS_DASHBOARD_URL = get_lms_url("/dashboard") OAUTH2_PROVIDER_URL = get_lms_url("/oauth2") COMMERCE_API_URL = get_lms_url("/api/commerce/v1/") # END URL CONFIGURATION # AUTHENTICATION # Set these to the correct values for your OAuth2/OpenID Connect provider (e.g., devstack) SOCIAL_AUTH_EDX_OIDC_KEY = "replace-me" SOCIAL_AUTH_EDX_OIDC_SECRET = "replace-me" SOCIAL_AUTH_EDX_OIDC_URL_ROOT = OAUTH2_PROVIDER_URL
'PASSWORD': '', 'HOST': '', 'PORT': '', 'ATOMIC_REQUESTS': True, }, } # END IN-MEMORY TEST DATABASE # URL CONFIGURATION ECOMMERCE_URL_ROOT = 'http://localhost:8002' LMS_URL_ROOT = 'http://127.0.0.1:8000' # The location of the LMS heartbeat page LMS_HEARTBEAT_URL = get_lms_url('/heartbeat') # The location of the LMS student dashboard LMS_DASHBOARD_URL = get_lms_url('/dashboard') COMMERCE_API_URL = get_lms_url('/api/commerce/v1/') # END URL CONFIGURATION # AUTHENTICATION ENABLE_AUTO_AUTH = True JWT_AUTH.update({ 'JWT_SECRET_KEY': 'insecure-secret-key', 'JWT_ISSUERS': ('test-issuer',), })
) DEBUG_TOOLBAR_PATCH_SETTINGS = False # http://django-debug-toolbar.readthedocs.org/en/latest/installation.html INTERNAL_IPS = ('127.0.0.1',) # END TOOLBAR CONFIGURATION # URL CONFIGURATION ECOMMERCE_URL_ROOT = 'http://localhost:8002' LMS_URL_ROOT = 'http://127.0.0.1:8000' # The location of the LMS heartbeat page LMS_HEARTBEAT_URL = get_lms_url('/heartbeat') # The location of the LMS student dashboard LMS_DASHBOARD_URL = get_lms_url('/dashboard') OAUTH2_PROVIDER_URL = get_lms_url('/oauth2') COMMERCE_API_URL = get_lms_url('/api/commerce/v1/') # END URL CONFIGURATION # AUTHENTICATION # Set these to the correct values for your OAuth2/OpenID Connect provider (e.g., devstack) SOCIAL_AUTH_EDX_OIDC_KEY = 'replace-me' SOCIAL_AUTH_EDX_OIDC_SECRET = 'replace-me' SOCIAL_AUTH_EDX_OIDC_URL_ROOT = OAUTH2_PROVIDER_URL
DEBUG = True ENABLE_AUTO_AUTH = True # PAYMENT PROCESSING PAYMENT_PROCESSOR_CONFIG = { 'cybersource': { 'soap_api_url': 'https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor/CyberSourceTransaction_1.115.wsdl', 'merchant_id': 'fake-merchant-id', 'transaction_key': 'fake-transaction-key', 'profile_id': 'fake-profile-id', 'access_key': 'fake-access-key', 'secret_key': 'fake-secret-key', 'payment_page_url': 'https://testsecureacceptance.cybersource.com/pay', 'receipt_page_url': get_lms_url('/commerce/checkout/receipt/'), 'cancel_page_url': get_lms_url('/commerce/checkout/cancel/'), }, 'paypal': { 'mode': 'sandbox', 'client_id': 'fake-client-id', 'client_secret': 'fake-client-secret', 'receipt_url': get_lms_url('/commerce/checkout/receipt/'), 'cancel_url': get_lms_url('/commerce/checkout/cancel/'), 'error_url': get_lms_url('/commerce/checkout/error/'), }, } # END PAYMENT PROCESSING # Load private settings if os.path.isfile(join(dirname(abspath(__file__)), 'private.py')):