def test_enrollment_module_fulfill_bad_attributes(self): """Test that use of the Fulfillment Module fails when the product does not have attributes.""" ProductAttribute.objects.get(code='course_key').delete() EnrollmentFulfillmentModule().fulfill_product( self.order, list(self.order.lines.all())) self.assertEqual(LINE.FULFILLMENT_CONFIGURATION_ERROR, self.order.lines.all()[0].status)
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_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_headers(self): """ Test that the enrollment module 'EnrollmentFulfillmentModule' is sending enrollment request over to the LMS with proper headers. """ # Create a dummy data for the enrollment request. data = { 'user': '******', 'is_active': True, 'mode': 'honor', 'course_details': { 'course_id': self.course_id }, 'enrollment_attributes': [] } # Now call the enrollment api to send POST request to LMS and verify # that the header of the request being sent contains the analytics # header 'x-edx-ga-client-id'. # This will raise the exception 'ConnectionError' because the LMS is # not available for ecommerce tests. try: # pylint: disable=protected-access EnrollmentFulfillmentModule()._post_to_enrollment_api( data=data, user=self.user) except ConnectionError as exp: # Check that the enrollment request object has the analytics header # 'x-edx-ga-client-id' and 'x-forwarded-for'. self.assertEqual(exp.request.headers.get('x-edx-ga-client-id'), self.user.tracking_context['ga_client_id']) self.assertEqual(exp.request.headers.get('x-forwarded-for'), self.user.tracking_context['lms_ip'])
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 test_enrollment_module_fulfill(self, mock_post_request): """Happy path test to ensure we can properly fulfill enrollments.""" fake_enrollment_api_response = Response() fake_enrollment_api_response.status_code = status.HTTP_200_OK mock_post_request.return_value = fake_enrollment_api_response self._create_attributes() # Attempt to enroll. EnrollmentFulfillmentModule().fulfill_product(self.order, list(self.order.lines.all())) self.assertEqual(LINE.COMPLETE, self.order.lines.all()[0].status)
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_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_enrollment_module_server_error(self, response_content): """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. fake_error_response = Response() fake_error_response._content = response_content # pylint: disable=protected-access fake_error_response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR with mock.patch('requests.post', return_value=fake_error_response): self._create_attributes() # Attempt to enroll EnrollmentFulfillmentModule().fulfill_product(self.order, list(self.order.lines.all())) self.assertEqual(LINE.FULFILLMENT_SERVER_ERROR, self.order.lines.all()[0].status)
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_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 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_enrollment_module_request_timeout(self): """Test that lines receive a timeout error status if a fulfillment request times out.""" EnrollmentFulfillmentModule().fulfill_product( self.order, list(self.order.lines.all())) self.assertEqual(LINE.FULFILLMENT_TIMEOUT_ERROR, self.order.lines.all()[0].status)
def test_enrollment_module_network_error(self): """Test that lines receive a network error status if a fulfillment request experiences a network error.""" EnrollmentFulfillmentModule().fulfill_product( self.order, list(self.order.lines.all())) self.assertEqual(LINE.FULFILLMENT_NETWORK_ERROR, self.order.lines.all()[0].status)
def test_enrollment_module_not_configured(self): """Test that lines receive a configuration error status if fulfillment configuration is invalid.""" EnrollmentFulfillmentModule().fulfill_product( self.order, list(self.order.lines.all())) self.assertEqual(LINE.FULFILLMENT_CONFIGURATION_ERROR, self.order.lines.all()[0].status)
def test_enrollment_module_support(self): """Test that we get the correct values back for supported product lines.""" supported_lines = EnrollmentFulfillmentModule().get_supported_lines( list(self.order.lines.all())) self.assertEqual(1, len(supported_lines))
def test_enrollment_module_fulfill_bad_attributes(self): """Test that use of the Fulfillment Module fails when the product does not have attributes.""" # Attempt to enroll without creating the product attributes. EnrollmentFulfillmentModule().fulfill_product(self.order, list(self.order.lines.all())) self.assertEqual(LINE.FULFILLMENT_CONFIGURATION_ERROR, self.order.lines.all()[0].status)
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)
def test_enrollment_module_revoke(self): """Test that use of this method due to "not implemented" error.""" EnrollmentFulfillmentModule().revoke_product(self.order, list(self.order.lines.all()))