def mock_enrollment_api(self, status=200): self.assertTrue(httpretty.is_enabled()) httpretty.register_uri(httpretty.GET, get_lms_enrollment_api_url(), status=status, body=json.dumps(self.data), content_type='application/json')
def test_enrollment_module_server_error(self, body): """Test that lines receive a server-side error status if a server-side error occurs during fulfillment.""" # NOTE: We are testing for cases where the response does and does NOT have data. The module should be able # to handle both cases. httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=500, body=body, content_type=JSON) EnrollmentFulfillmentModule().fulfill_product(self.order, list(self.order.lines.all())) self.assertEqual(LINE.FULFILLMENT_SERVER_ERROR, self.order.lines.all()[0].status)
def test_zero_dollar_refund(self, mock_revoke_line): """ Given an order and order lines which total $0 and are not refunded, Refund.create_with_lines should create and approve a Refund with corresponding RefundLines. """ httpretty.register_uri( httpretty.POST, get_lms_enrollment_api_url(), status=200, body='{}', content_type='application/json' ) order = self.create_order(user=UserFactory(), free=True) # Verify that the order totals $0. self.assertEqual(order.total_excl_tax, 0) refund = Refund.create_with_lines(order, list(order.lines.all())) # Verify that refund lines are not revoked. self.assertFalse(mock_revoke_line.called) # Verify that the refund has been successfully approved. self.assertEqual(refund.status, REFUND.COMPLETE) self.assertEqual(set([line.status for line in refund.lines.all()]), {REFUND_LINE.COMPLETE})
def test_zero_dollar_refund(self, mock_revoke_line): """ Given an order and order lines which total $0 and are not refunded, Refund.create_with_lines should create and approve a Refund with corresponding RefundLines. """ httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=200, body='{}', content_type='application/json') order = self.create_order(user=UserFactory(), free=True) # Verify that the order totals $0. self.assertEqual(order.total_excl_tax, 0) refund = Refund.create_with_lines(order, list(order.lines.all())) # Verify that refund lines are not revoked. self.assertFalse(mock_revoke_line.called) # Verify that the refund has been successfully approved. self.assertEqual(refund.status, REFUND.COMPLETE) self.assertEqual({line.status for line in refund.lines.all()}, {REFUND_LINE.COMPLETE})
def _get_enrollments(self): """Retrieve the enrollments for the User being viewed.""" username = self.object.username try: url = '{}?user={}'.format(get_lms_enrollment_api_url(), username) timeout = settings.ENROLLMENT_FULFILLMENT_TIMEOUT headers = { 'Content-Type': 'application/json', 'X-Edx-Api-Key': settings.EDX_API_KEY } response = requests.get(url, headers=headers, timeout=timeout) status_code = response.status_code if status_code == 200: return response.json() else: logger.warning(u'Failed to retrieve enrollments for [%s]. Enrollment API returned status code [%d].', username, status_code) except Exception: # pylint: disable=broad-except logger.exception(u'An unexpected error occurred while retrieving enrollments for [%s].', username) messages.add_message(self.request, messages.ERROR, _(u'Failed to retrieve enrollment data.')) return []
def test_multiple_vouchers(self): """ Verify a redirect to LMS happens when a basket with already existing vouchers is used. """ self.create_and_test_coupon() basket = Basket.get_basket(self.user, self.site) basket.vouchers.add(Voucher.objects.get(code=COUPON_CODE)) httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=200) self.assert_redemption_page_redirects(get_lms_url())
def test_basket_redirect_enrollment_code(self): """ Verify the view redirects to LMS when an enrollment code is provided. """ self.create_and_test_coupon() httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=200) self.assert_redemption_page_redirects(get_lms_url())
def test_credit_enrollment_module_fulfill(self): """Happy path test to ensure we can properly fulfill enrollments.""" # Create the credit certificate type and order for the credit certificate type. self.create_seat_and_order(certificate_type='credit', provider='MIT') httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=200, body='{}', content_type=JSON) # Attempt to enroll. with LogCapture(LOGGER_NAME) as logger: EnrollmentFulfillmentModule().fulfill_product( self.order, list(self.order.lines.all())) line = self.order.lines.get() logger.check_present(( LOGGER_NAME, 'INFO', 'line_fulfilled: course_id="{}", credit_provider="{}", mode="{}", order_line_id="{}", ' 'order_number="{}", product_class="{}", user_id="{}"'.format( line.product.attr.course_key, line.product.attr.credit_provider, mode_for_product(line.product), line.id, line.order.number, line.product.get_product_class().name, line.order.user.id, ))) self.assertEqual(LINE.COMPLETE, line.status) actual = json.loads(httpretty.last_request().body.decode('utf-8')) expected = { 'user': self.order.user.username, 'is_active': True, 'mode': self.certificate_type, 'course_details': { 'course_id': self.course_id, }, 'enrollment_attributes': [{ 'namespace': 'order', 'name': 'order_number', 'value': self.order.number }, { 'namespace': 'order', 'name': 'date_placed', 'value': self.order.date_placed.strftime(ISO_8601_FORMAT) }, { 'namespace': 'credit', 'name': 'provider_id', 'value': self.provider }] } self.assertEqual(actual, expected)
def test_enrollment_module_fulfill(self, parse_tracking_context): """Happy path test to ensure we can properly fulfill enrollments.""" httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=200, body='{}', content_type=JSON) parse_tracking_context.return_value = ('user_123', 'GA-123456789', '11.22.33.44') # Attempt to enroll. with LogCapture(LOGGER_NAME) as l: EnrollmentFulfillmentModule().fulfill_product( self.order, list(self.order.lines.all())) line = self.order.lines.get() l.check(( LOGGER_NAME, 'INFO', 'line_fulfilled: course_id="{}", credit_provider="{}", mode="{}", order_line_id="{}", ' 'order_number="{}", product_class="{}", user_id="{}"'.format( line.product.attr.course_key, None, mode_for_seat(line.product), line.id, line.order.number, line.product.get_product_class().name, line.order.user.id, ))) self.assertEqual(LINE.COMPLETE, line.status) last_request = httpretty.last_request() actual_body = json.loads(last_request.body) actual_headers = last_request.headers expected_body = { 'user': self.order.user.username, 'is_active': True, 'mode': self.certificate_type, 'course_details': { 'course_id': self.course_id, }, 'enrollment_attributes': [{ 'namespace': 'order', 'name': 'order_number', 'value': self.order.number }] } expected_headers = { 'X-Edx-Ga-Client-Id': 'GA-123456789', 'X-Forwarded-For': '11.22.33.44', } self.assertDictContainsSubset(expected_headers, actual_headers) self.assertEqual(expected_body, actual_body)
def mock_enrollment_api_success(self, course_id, mode='audit'): """ Returns a successful response indicating self.user is enrolled in the specified course mode. """ self.assertTrue(httpretty.is_enabled()) url = '{host}/{username},{course_id}'.format( host=get_lms_enrollment_api_url(), username=self.user.username, course_id=course_id ) httpretty.register_uri(httpretty.GET, url, body=json.dumps({'mode': mode}), content_type='application/json')
def test_enrollment_module_fulfill(self, parse_tracking_context): """Happy path test to ensure we can properly fulfill enrollments.""" httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=200, body='{}', content_type=JSON) parse_tracking_context.return_value = ('user_123', 'GA-123456789', '11.22.33.44') # Attempt to enroll. with LogCapture(LOGGER_NAME) as l: EnrollmentFulfillmentModule().fulfill_product(self.order, list(self.order.lines.all())) line = self.order.lines.get() l.check( ( LOGGER_NAME, 'INFO', 'line_fulfilled: course_id="{}", credit_provider="{}", mode="{}", order_line_id="{}", ' 'order_number="{}", product_class="{}", user_id="{}"'.format( line.product.attr.course_key, None, mode_for_seat(line.product), line.id, line.order.number, line.product.get_product_class().name, line.order.user.id, ) ) ) self.assertEqual(LINE.COMPLETE, line.status) last_request = httpretty.last_request() actual_body = json.loads(last_request.body) actual_headers = last_request.headers expected_body = { 'user': self.order.user.username, 'is_active': True, 'mode': self.certificate_type, 'course_details': { 'course_id': self.course_id, }, 'enrollment_attributes': [ { 'namespace': 'order', 'name': 'order_number', 'value': self.order.number } ] } expected_headers = { 'X-Edx-Ga-Client-Id': 'GA-123456789', 'X-Forwarded-For': '11.22.33.44', } self.assertDictContainsSubset(expected_headers, actual_headers) self.assertEqual(expected_body, actual_body)
def mock_enrollment_api_error(self, error): """ Mock Enrollment api call which raises error when called """ self.assertTrue(httpretty.is_enabled()) def callback(request, uri, headers): # pylint: disable=unused-argument raise error url = '{host}/{username},{course_id}'.format( host=get_lms_enrollment_api_url(), username=self.user.username, course_id=self.course.id ) httpretty.register_uri(httpretty.GET, url, body=callback, content_type='application/json')
def test_enrollment_module_fulfill_order_with_discount_no_voucher(self): """ Test that components of the Fulfillment Module which trigger on the presence of a voucher do not cause failures in cases where a discount does not have a voucher included (such as with a Conditional Offer) """ httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=200, body='{}', content_type=JSON) self.create_seat_and_order(certificate_type='credit', provider='MIT') self.order.discounts.create() __, lines = EnrollmentFulfillmentModule().fulfill_product(self.order, list(self.order.lines.all())) # No exceptions should be raised and the order should be fulfilled self.assertEqual(lines[0].status, 'Complete')
def test_credit_enrollment_module_fulfill(self): """Happy path test to ensure we can properly fulfill enrollments.""" # Create the credit certificate type and order for the credit certificate type. self.create_seat_and_order(certificate_type='credit', provider='MIT') httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=200, body='{}', content_type=JSON) # Attempt to enroll. with LogCapture(LOGGER_NAME) as l: EnrollmentFulfillmentModule().fulfill_product(self.order, list(self.order.lines.all())) line = self.order.lines.get() l.check( ( LOGGER_NAME, 'INFO', 'line_fulfilled: course_id="{}", credit_provider="{}", mode="{}", order_line_id="{}", ' 'order_number="{}", product_class="{}", user_id="{}"'.format( line.product.attr.course_key, line.product.attr.credit_provider, mode_for_seat(line.product), line.id, line.order.number, line.product.get_product_class().name, line.order.user.id, ) ) ) self.assertEqual(LINE.COMPLETE, line.status) actual = json.loads(httpretty.last_request().body) expected = { 'user': self.order.user.username, 'is_active': True, 'mode': self.certificate_type, 'course_details': { 'course_id': self.course_id, }, 'enrollment_attributes': [ { 'namespace': 'order', 'name': 'order_number', 'value': self.order.number }, { 'namespace': 'credit', 'name': 'provider_id', 'value': self.provider } ] } self.assertEqual(actual, expected)
def test_voucher_usage_with_program(self): """ Test that using a voucher with a program basket results in a fulfilled order. """ httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=200, body='{}', content_type=JSON) self.create_seat_and_order(certificate_type='credit', provider='MIT') program_uuid = uuid.uuid4() self.mock_program_detail_endpoint(program_uuid, self.site_configuration.discovery_api_url) self.mock_user_data(self.user.username) self.prepare_basket_with_voucher(program_uuid=program_uuid) __, lines = EnrollmentFulfillmentModule().fulfill_product(self.order, list(self.order.lines.all())) # No exceptions should be raised and the order should be fulfilled self.assertEqual(lines[0].status, 'Complete')
def test_revoke_product_unexpected_error(self): """ If the Enrollment API responds with a non-200 status, the method should log an error and return False. """ message = 'Meh.' body = '{{"message": "{}"}}'.format(message) httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=500, body=body, content_type=JSON) line = self.order.lines.first() logger_name = 'ecommerce.extensions.fulfillment.modules' with LogCapture(logger_name) as logger: self.assertFalse(EnrollmentFulfillmentModule().revoke_line(line)) logger.check_present( (logger_name, 'INFO', 'Attempting to revoke fulfillment of Line [{}]...'.format(line.id)), (logger_name, 'ERROR', 'Failed to revoke fulfillment of Line [%d]: %s' % (line.id, message)) )
def test_revoke_product_unexpected_error(self): """ If the Enrollment API responds with a non-200 status, the method should log an error and return False. """ message = 'Meh.' body = '{{"message": "{}"}}'.format(message) httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=500, body=body, content_type=JSON) line = self.order.lines.first() logger_name = 'ecommerce.extensions.fulfillment.modules' with LogCapture(logger_name) as l: self.assertFalse(EnrollmentFulfillmentModule().revoke_line(line)) l.check( (logger_name, 'INFO', 'Attempting to revoke fulfillment of Line [{}]...'.format(line.id)), (logger_name, 'ERROR', 'Failed to revoke fulfillment of Line [%d]: %s' % (line.id, message)) )
def mock_enrollment_api(self, username, enrollments=None, response_code=200): """ Mocks enrollment retrieval from LMS Returns: list: Mocked enrollment data """ self.mock_access_token_response() httpretty.register_uri( method=httpretty.GET, uri='{}?user={}'.format(get_lms_enrollment_api_url(), username), body=json.dumps([] if enrollments is None else enrollments), status=response_code, content_type='application/json') return enrollments
def _post_to_enrollment_api(self, data, user): enrollment_api_url = get_lms_enrollment_api_url() timeout = settings.ENROLLMENT_FULFILLMENT_TIMEOUT headers = { 'Content-Type': 'application/json', 'X-Edx-Api-Key': settings.EDX_API_KEY } __, client_id, ip = parse_tracking_context(user) if client_id: headers['X-Edx-Ga-Client-Id'] = client_id if ip: headers['X-Forwarded-For'] = ip return requests.post(enrollment_api_url, data=json.dumps(data), headers=headers, timeout=timeout)
def test_revoke_product_expected_error(self): """ If the Enrollment API responds with an expected error, the method should log that revocation was bypassed, and return True. """ message = 'Enrollment mode mismatch: active mode=x, requested mode=y. Won\'t deactivate.' body = '{{"message": "{}"}}'.format(message) httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=400, body=body, content_type=JSON) line = self.order.lines.first() logger_name = 'ecommerce.extensions.fulfillment.modules' with LogCapture(logger_name) as l: self.assertTrue(EnrollmentFulfillmentModule().revoke_line(line)) l.check( (logger_name, 'INFO', 'Attempting to revoke fulfillment of Line [{}]...'.format(line.id)), (logger_name, 'INFO', 'Skipping revocation for line [%d]: %s' % (line.id, message)) )
def test_revoke_product_expected_error(self): """ If the Enrollment API responds with an expected error, the method should log that revocation was bypassed, and return True. """ message = 'Enrollment mode mismatch: active mode=x, requested mode=y. Won\'t deactivate.' body = '{{"message": "{}"}}'.format(message) httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=400, body=body, content_type=JSON) line = self.order.lines.first() logger_name = 'ecommerce.extensions.fulfillment.modules' with LogCapture(logger_name) as logger: self.assertTrue(EnrollmentFulfillmentModule().revoke_line(line)) logger.check_present( (logger_name, 'INFO', 'Attempting to revoke fulfillment of Line [{}]...'.format(line.id)), (logger_name, 'INFO', 'Skipping revocation for line [%d]: %s' % (line.id, message)) )
def test_revoke_product(self, parse_tracking_context): """ The method should call the Enrollment API to un-enroll the student, and return True. """ httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=200, body='{}', content_type=JSON) parse_tracking_context.return_value = ('user_123', 'GA-123456789', '11.22.33.44') line = self.order.lines.first() with LogCapture(LOGGER_NAME) as l: self.assertTrue(EnrollmentFulfillmentModule().revoke_line(line)) l.check( ( LOGGER_NAME, 'INFO', 'line_revoked: certificate_type="{}", course_id="{}", order_line_id="{}", order_number="{}", ' 'product_class="{}", user_id="{}"'.format( getattr(line.product.attr, 'certificate_type', ''), line.product.attr.course_key, line.id, line.order.number, line.product.get_product_class().name, line.order.user.id ) ) ) last_request = httpretty.last_request() actual_body = json.loads(last_request.body) actual_headers = last_request.headers expected_body = { 'user': self.order.user.username, 'is_active': False, 'mode': self.certificate_type, 'course_details': { 'course_id': self.course_id, }, } expected_headers = { 'X-Edx-Ga-Client-Id': 'GA-123456789', 'X-Forwarded-For': '11.22.33.44', } self.assertDictContainsSubset(expected_headers, actual_headers) self.assertEqual(expected_body, actual_body)
def mock_user_data(self, username, mocked_api='enrollments', owned_products=None, response_code=200): """ Mocks user ownership data retrieval from LMS Returns: list: Mocked entitlement or enrollment data """ self.mock_access_token_response() if mocked_api == 'enrollments': api_url = get_lms_enrollment_api_url() else: api_url = get_lms_entitlement_api_url() + 'entitlements/' httpretty.register_uri( method=httpretty.GET, uri='{}?user={}'.format(api_url, username), body=json.dumps([] if owned_products is None else owned_products), status=response_code, content_type='application/json' ) return owned_products
def test_revoke_product_unknown_exception(self): """ If an exception is raised while contacting the Enrollment API, the method should log an error and return False. """ def request_callback(_method, _uri, _headers): raise Timeout httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), body=request_callback) line = self.order.lines.first() logger_name = 'ecommerce.extensions.fulfillment.modules' with LogCapture(logger_name) as logger: self.assertFalse(EnrollmentFulfillmentModule().revoke_line(line)) logger.check_present( (logger_name, 'INFO', 'Attempting to revoke fulfillment of Line [{}]...'.format(line.id)), (logger_name, 'ERROR', 'Failed to revoke fulfillment of Line [{}].'.format(line.id)) )
def test_revoke_product_unknown_exception(self): """ If an exception is raised while contacting the Enrollment API, the method should log an error and return False. """ def request_callback(_method, _uri, _headers): raise Timeout httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), body=request_callback) line = self.order.lines.first() logger_name = 'ecommerce.extensions.fulfillment.modules' with LogCapture(logger_name) as l: self.assertFalse(EnrollmentFulfillmentModule().revoke_line(line)) l.check( (logger_name, 'INFO', 'Attempting to revoke fulfillment of Line [{}]...'.format(line.id)), (logger_name, 'ERROR', 'Failed to revoke fulfillment of Line [{}].'.format(line.id)) )
def mock_enrollment_api(self, status=200): responses.add(responses.GET, get_lms_enrollment_api_url(), status=status, json=self.data, content_type='application/json')
def test_enrollment_module_fulfill(self): """Happy path test to ensure we can properly fulfill enrollments.""" httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=200, body='{}', content_type=JSON) # Attempt to enroll. with LogCapture(LOGGER_NAME) as logger: EnrollmentFulfillmentModule().fulfill_product( self.order, list(self.order.lines.all())) line = self.order.lines.get() logger.check_present(( LOGGER_NAME, 'INFO', 'line_fulfilled: course_id="{}", credit_provider="{}", mode="{}", order_line_id="{}", ' 'order_number="{}", product_class="{}", user_id="{}"'.format( line.product.attr.course_key, None, mode_for_product(line.product), line.id, line.order.number, line.product.get_product_class().name, line.order.user.id, ))) self.assertEqual(LINE.COMPLETE, line.status) last_request = httpretty.last_request() actual_body = json.loads(last_request.body.decode('utf-8')) actual_headers = last_request.headers expected_body = { 'user': self.order.user.username, 'is_active': True, 'mode': self.certificate_type, 'course_details': { 'course_id': self.course_id, }, 'enrollment_attributes': [{ 'namespace': 'order', 'name': 'order_number', 'value': self.order.number }, { 'namespace': 'order', 'name': 'date_placed', 'value': self.order.date_placed.strftime(ISO_8601_FORMAT) }] } expected_headers = { 'X-Edx-Ga-Client-Id': self.user.tracking_context['ga_client_id'], 'X-Forwarded-For': self.user.tracking_context['lms_ip'], } self.assertDictContainsSubset(expected_headers, actual_headers) self.assertEqual(expected_body, actual_body)