From cc1c7d50125248a3cede5aa912ebc0fb40c3f551 Mon Sep 17 00:00:00 2001 From: jsa Date: Tue, 5 May 2015 19:29:32 -0400 Subject: [PATCH] fix ecommerce api calls in verify_student and update tests. XCOM-287 --- lms/djangoapps/commerce/tests/__init__.py | 97 +++---------------- lms/djangoapps/commerce/tests/mocks.py | 95 ++++++++++++++++++ lms/djangoapps/commerce/tests/test_views.py | 48 ++++----- .../verify_student/tests/test_views.py | 93 +++++++++++++++--- lms/djangoapps/verify_student/views.py | 10 +- 5 files changed, 217 insertions(+), 126 deletions(-) create mode 100644 lms/djangoapps/commerce/tests/mocks.py diff --git a/lms/djangoapps/commerce/tests/__init__.py b/lms/djangoapps/commerce/tests/__init__.py index ca85990fd7..c730ff09cc 100644 --- a/lms/djangoapps/commerce/tests/__init__.py +++ b/lms/djangoapps/commerce/tests/__init__.py @@ -3,7 +3,6 @@ import json from django.test import TestCase from django.test.utils import override_settings -from ecommerce_api_client.client import EcommerceApiClient import httpretty import jwt import mock @@ -12,15 +11,24 @@ from commerce import ecommerce_api_client from student.tests.factories import UserFactory +TEST_API_URL = 'http://example.com/api' +TEST_API_SIGNING_KEY = 'edx' +TEST_BASKET_ID = 7 +TEST_ORDER_NUMBER = '100004' +TEST_PAYMENT_DATA = { + 'payment_processor_name': 'test-processor', + 'payment_form_data': {}, + 'payment_page_url': 'http://example.com/pay', +} + + class EcommerceApiClientTest(TestCase): """ Tests to ensure the client is initialized properly. """ - TEST_SIGNING_KEY = 'edx' - TEST_API_URL = 'http://example.com/api' TEST_USER_EMAIL = 'test@example.com' TEST_CLIENT_ID = 'test-client-id' - @override_settings(ECOMMERCE_API_SIGNING_KEY=TEST_SIGNING_KEY, ECOMMERCE_API_URL=TEST_API_URL) + @override_settings(ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY, ECOMMERCE_API_URL=TEST_API_URL) @httpretty.activate def test_tracking_context(self): """ Ensure the tracking context is set up in the api client correctly @@ -32,7 +40,7 @@ class EcommerceApiClientTest(TestCase): # fake an ecommerce api request. httpretty.register_uri( httpretty.POST, - '{}/baskets/1/'.format(self.TEST_API_URL), + '{}/baskets/1/'.format(TEST_API_URL), status=200, body='{}', adding_headers={'Content-Type': 'application/json'} ) @@ -51,82 +59,5 @@ class EcommerceApiClientTest(TestCase): 'lms_client_id': self.TEST_CLIENT_ID, }, } - expected_header = 'JWT {}'.format(jwt.encode(expected_payload, self.TEST_SIGNING_KEY)) + expected_header = 'JWT {}'.format(jwt.encode(expected_payload, TEST_API_SIGNING_KEY)) self.assertEqual(actual_header, expected_header) - - -class EcommerceApiTestMixin(object): - """ Mixin for tests utilizing the E-Commerce API. """ - - ECOMMERCE_API_URL = 'http://example.com/api' - ECOMMERCE_API_SIGNING_KEY = 'edx' - BASKET_ID = 7 - ORDER_NUMBER = '100004' - PROCESSOR = 'test-processor' - PAYMENT_DATA = { - 'payment_processor_name': PROCESSOR, - 'payment_form_data': {}, - 'payment_page_url': 'http://example.com/pay', - } - ORDER_DATA = {'number': ORDER_NUMBER} - ECOMMERCE_API_SUCCESSFUL_BODY = { - 'id': BASKET_ID, - 'order': {'number': ORDER_NUMBER}, # never both None. - 'payment_data': PAYMENT_DATA, - } - ECOMMERCE_API_SUCCESSFUL_BODY_JSON = json.dumps(ECOMMERCE_API_SUCCESSFUL_BODY) # pylint: disable=invalid-name - - def _mock_ecommerce_api(self, status=200, body=None, is_payment_required=False): - """ - Mock calls to the E-Commerce API. - - The calling test should be decorated with @httpretty.activate. - """ - self.assertTrue(httpretty.is_enabled(), 'Test is missing @httpretty.activate decorator.') - - url = self.ECOMMERCE_API_URL + '/baskets/' - if body is None: - response_data = {'id': self.BASKET_ID, 'payment_data': None, 'order': None} - if is_payment_required: - response_data['payment_data'] = self.PAYMENT_DATA - else: - response_data['order'] = {'number': self.ORDER_NUMBER} - body = json.dumps(response_data) - httpretty.register_uri(httpretty.POST, url, status=status, body=body, - adding_headers={'Content-Type': 'application/json'}) - - class mock_create_basket(object): # pylint: disable=invalid-name - """ Mocks calls to E-Commerce API client basket creation method. """ - - patch = None - - def __init__(self, **kwargs): - default_kwargs = {'return_value': EcommerceApiTestMixin.ECOMMERCE_API_SUCCESSFUL_BODY} - default_kwargs.update(kwargs) - _mock = mock.Mock() - _mock.post = mock.Mock(**default_kwargs) - EcommerceApiClient.baskets = _mock - self.patch = _mock - - def __enter__(self): - return self.patch - - def __exit__(self, exc_type, exc_val, exc_tb): # pylint: disable=unused-argument - pass - - class mock_basket_order(object): # pylint: disable=invalid-name - """ Mocks calls to E-Commerce API client basket order method. """ - - patch = None - - def __init__(self, **kwargs): - _mock = mock.Mock() - _mock.order.get = mock.Mock(**kwargs) - EcommerceApiClient.baskets = lambda client, basket_id: _mock - self.patch = _mock - - def __enter__(self): - return self.patch - - def __exit__(self, exc_type, exc_val, exc_tb): # pylint: disable=unused-argument - pass diff --git a/lms/djangoapps/commerce/tests/mocks.py b/lms/djangoapps/commerce/tests/mocks.py new file mode 100644 index 0000000000..9f12805ce0 --- /dev/null +++ b/lms/djangoapps/commerce/tests/mocks.py @@ -0,0 +1,95 @@ +""" Commerce app tests package. """ +import json + +import httpretty + +from commerce.tests import TEST_API_URL + + +class mock_ecommerce_api_endpoint(object): # pylint: disable=invalid-name + """ + Base class for contextmanagers used to mock calls to api endpoints. + + The contextmanager internally activates and deactivates httpretty as + required, therefore it is not advised to use this mock endpoint in + test cases where httpretty is being used directly. + """ + + # override this in subclasses. + default_response = None + + # override this in subclasses, using one of httpretty's method constants + method = None + + def __init__(self, response=None, status=200, expect_called=True, exception=None): + """ + Keyword Arguments: + response: a JSON-serializable Python type representing the desired response body. + status: desired HTTP status for the response. + expect_called: a boolean indicating whether an API request was expected; set + to False if we should ensure that no request arrived. + exception: raise this exception instead of returning an HTTP response when called. + """ + self.response = response or self.default_response + self.status = status + self.expect_called = expect_called + self.exception = exception + + def get_uri(self): + """ + Return the uri to register with httpretty for this contextmanager. + + Subclasses must override this method. + """ + raise NotImplementedError + + def _exception_body(self, request, uri, headers): # pylint: disable=unused-argument + """Helper used to create callbacks in order to have httpretty raise Exceptions.""" + raise self.exception # pylint: disable=raising-bad-type + + def __enter__(self): + httpretty.reset() + httpretty.enable() + httpretty.register_uri( + self.method, + self.get_uri(), + status=self.status, + body=self._exception_body if self.exception is not None else json.dumps(self.response), + adding_headers={'Content-Type': 'application/json'}, + ) + + def __exit__(self, exc_type, exc_val, exc_tb): # pylint: disable=unused-argument + assert self.expect_called == (httpretty.last_request().headers != {}) + httpretty.disable() + + +class mock_create_basket(mock_ecommerce_api_endpoint): # pylint: disable=invalid-name + """ Mocks calls to E-Commerce API client basket creation method. """ + + default_response = { + 'id': 7, + 'order': {'number': '100004'}, # never both None. + 'payment_data': { + 'payment_processor_name': 'test-processor', + 'payment_form_data': {}, + 'payment_page_url': 'http://example.com/pay', + }, + } + method = httpretty.POST + + def get_uri(self): + return TEST_API_URL + '/baskets/' + + +class mock_basket_order(mock_ecommerce_api_endpoint): # pylint: disable=invalid-name + """ Mocks calls to E-Commerce API client basket order method. """ + + default_response = {'number': 1} + method = httpretty.GET + + def __init__(self, basket_id, **kwargs): + super(mock_basket_order, self).__init__(**kwargs) + self.basket_id = basket_id + + def get_uri(self): + return TEST_API_URL + '/baskets/{}/order/'.format(self.basket_id) diff --git a/lms/djangoapps/commerce/tests/test_views.py b/lms/djangoapps/commerce/tests/test_views.py index b9a43ebae8..1232f78c94 100644 --- a/lms/djangoapps/commerce/tests/test_views.py +++ b/lms/djangoapps/commerce/tests/test_views.py @@ -12,7 +12,8 @@ from xmodule.modulestore.tests.factories import CourseFactory from ecommerce_api_client import exceptions from commerce.constants import Messages -from commerce.tests import EcommerceApiTestMixin +from commerce.tests import TEST_BASKET_ID, TEST_ORDER_NUMBER, TEST_PAYMENT_DATA, TEST_API_URL, TEST_API_SIGNING_KEY +from commerce.tests.mocks import mock_basket_order, mock_create_basket from course_modes.models import CourseMode from enrollment.api import get_enrollment from student.models import CourseEnrollment @@ -33,9 +34,8 @@ class UserMixin(object): @ddt -@override_settings(ECOMMERCE_API_URL=EcommerceApiTestMixin.ECOMMERCE_API_URL, - ECOMMERCE_API_SIGNING_KEY=EcommerceApiTestMixin.ECOMMERCE_API_SIGNING_KEY) -class BasketsViewTests(EnrollmentEventTestMixin, EcommerceApiTestMixin, UserMixin, ModuleStoreTestCase): +@override_settings(ECOMMERCE_API_URL=TEST_API_URL, ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY) +class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase): """ Tests for the commerce orders view. """ @@ -60,7 +60,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, EcommerceApiTestMixin, UserMixi def assertResponsePaymentData(self, response): """ Asserts correctness of a JSON body containing payment information. """ actual_response = json.loads(response.content) - self.assertEqual(actual_response, self.PAYMENT_DATA) + self.assertEqual(actual_response, TEST_PAYMENT_DATA) def assertValidEcommerceInternalRequestErrorResponse(self, response): """ Asserts the response is a valid response sent when the E-Commerce API is unavailable. """ @@ -126,7 +126,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, EcommerceApiTestMixin, UserMixi """ If the call to the E-Commerce API times out, the view should log an error and return an HTTP 503 status. """ - with self.mock_create_basket(side_effect=exceptions.Timeout): + with mock_create_basket(exception=exceptions.Timeout): response = self._post_to_view() self.assertValidEcommerceInternalRequestErrorResponse(response) @@ -136,7 +136,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, EcommerceApiTestMixin, UserMixi """ If the E-Commerce API raises an error, the view should return an HTTP 503 status. """ - with self.mock_create_basket(side_effect=exceptions.SlumberBaseException): + with mock_create_basket(exception=exceptions.SlumberBaseException): response = self._post_to_view() self.assertValidEcommerceInternalRequestErrorResponse(response) @@ -150,7 +150,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, EcommerceApiTestMixin, UserMixi # Validate the response content if is_completed: - msg = Messages.ORDER_COMPLETED.format(order_number=self.ORDER_NUMBER) + msg = Messages.ORDER_COMPLETED.format(order_number=TEST_ORDER_NUMBER) self.assertResponseMessage(response, msg) else: self.assertResponsePaymentData(response) @@ -166,8 +166,8 @@ class BasketsViewTests(EnrollmentEventTestMixin, EcommerceApiTestMixin, UserMixi self.user.is_active = user_is_active self.user.save() # pylint: disable=no-member - return_value = {'id': self.BASKET_ID, 'payment_data': None, 'order': {'number': self.ORDER_NUMBER}} - with self.mock_create_basket(return_value=return_value): + return_value = {'id': TEST_BASKET_ID, 'payment_data': None, 'order': {'number': TEST_ORDER_NUMBER}} + with mock_create_basket(response=return_value): self._test_successful_ecommerce_api_call() @data(True, False) @@ -180,8 +180,8 @@ class BasketsViewTests(EnrollmentEventTestMixin, EcommerceApiTestMixin, UserMixi self.user.is_active = user_is_active self.user.save() # pylint: disable=no-member - return_value = {'id': self.BASKET_ID, 'payment_data': self.PAYMENT_DATA, 'order': None} - with self.mock_create_basket(return_value=return_value): + return_value = {'id': TEST_BASKET_ID, 'payment_data': TEST_PAYMENT_DATA, 'order': None} + with mock_create_basket(response=return_value): self._test_successful_ecommerce_api_call(False) def _test_course_without_sku(self): @@ -189,7 +189,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, EcommerceApiTestMixin, UserMixi Validates the view bypasses the E-Commerce API when the course has no CourseModes with SKUs. """ # Place an order - with self.mock_create_basket() as api_mock: + with mock_create_basket(expect_called=False): response = self._post_to_view() # Validate the response content @@ -198,9 +198,6 @@ class BasketsViewTests(EnrollmentEventTestMixin, EcommerceApiTestMixin, UserMixi username=self.user.username) self.assertResponseMessage(response, msg) - # No calls made to the E-Commerce API - self.assertFalse(api_mock.called) - def test_course_without_sku(self): """ If the course does NOT have a SKU, the user should be enrolled in the course (under the honor mode) and @@ -218,7 +215,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, EcommerceApiTestMixin, UserMixi """ If the E-Commerce Service is not configured, the view should enroll the user. """ - with self.mock_create_basket() as api_mock: + with mock_create_basket(expect_called=False): response = self._post_to_view() # Validate the response @@ -228,7 +225,6 @@ class BasketsViewTests(EnrollmentEventTestMixin, EcommerceApiTestMixin, UserMixi # Ensure that the user is not enrolled and that no calls were made to the E-Commerce API self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course.id)) - self.assertFalse(api_mock.called) def assertProfessionalModeBypassed(self): """ Verifies that the view returns HTTP 406 when a course with no honor mode is encountered. """ @@ -238,7 +234,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, EcommerceApiTestMixin, UserMixi CourseModeFactory.create(course_id=self.course.id, mode_slug=mode, mode_display_name=mode, sku=uuid4().hex.decode('ascii')) - with self.mock_create_basket() as api_mock: + with mock_create_basket(expect_called=False): response = self._post_to_view() # The view should return an error status code @@ -246,9 +242,6 @@ class BasketsViewTests(EnrollmentEventTestMixin, EcommerceApiTestMixin, UserMixi msg = Messages.NO_HONOR_MODE.format(course_id=self.course.id) self.assertResponseMessage(response, msg) - # No calls should be made to the E-Commerce API. - self.assertFalse(api_mock.called) - def test_course_with_professional_mode_only(self): """ Verifies that the view behaves appropriately when the course only has a professional mode. """ self.assertProfessionalModeBypassed() @@ -293,7 +286,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, EcommerceApiTestMixin, UserMixi self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course.id)) self.assertIsNotNone(get_enrollment(self.user.username, unicode(self.course.id))) - with self.mock_create_basket(): + with mock_create_basket(): self._test_successful_ecommerce_api_call(False) @@ -310,9 +303,8 @@ class OrdersViewTests(BasketsViewTests): self.url = reverse('commerce:orders') -@override_settings(ECOMMERCE_API_URL=EcommerceApiTestMixin.ECOMMERCE_API_URL, - ECOMMERCE_API_SIGNING_KEY=EcommerceApiTestMixin.ECOMMERCE_API_SIGNING_KEY) -class BasketOrderViewTests(UserMixin, EcommerceApiTestMixin, TestCase): +@override_settings(ECOMMERCE_API_URL=TEST_API_URL, ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY) +class BasketOrderViewTests(UserMixin, TestCase): """ Tests for the basket order view. """ view_name = 'commerce:basket_order' MOCK_ORDER = {'number': 1} @@ -325,7 +317,7 @@ class BasketOrderViewTests(UserMixin, EcommerceApiTestMixin, TestCase): def test_order_found(self): """ If the order is located, the view should pass the data from the API. """ - with self.mock_basket_order(return_value=self.MOCK_ORDER): + with mock_basket_order(basket_id=1, response=self.MOCK_ORDER): response = self.client.get(self.path) self.assertEqual(response.status_code, 200) @@ -334,7 +326,7 @@ class BasketOrderViewTests(UserMixin, EcommerceApiTestMixin, TestCase): def test_order_not_found(self): """ If the order is not found, the view should return a 404. """ - with self.mock_basket_order(side_effect=exceptions.HttpNotFoundError): + with mock_basket_order(basket_id=1, exception=exceptions.HttpNotFoundError): response = self.client.get(self.path) self.assertEqual(response.status_code, 404) diff --git a/lms/djangoapps/verify_student/tests/test_views.py b/lms/djangoapps/verify_student/tests/test_views.py index 477198d8f8..0042154ea0 100644 --- a/lms/djangoapps/verify_student/tests/test_views.py +++ b/lms/djangoapps/verify_student/tests/test_views.py @@ -18,6 +18,7 @@ from django.conf import settings from django.core.urlresolvers import reverse from django.core.exceptions import ObjectDoesNotExist from django.core import mail +import httpretty from bs4 import BeautifulSoup from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @@ -27,7 +28,7 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.locator import CourseLocator from openedx.core.djangoapps.user_api.accounts.api import get_account_settings -from commerce.tests import EcommerceApiTestMixin +from commerce.tests import TEST_PAYMENT_DATA, TEST_API_URL, TEST_API_SIGNING_KEY from student.tests.factories import UserFactory, CourseEnrollmentFactory from student.models import CourseEnrollment from course_modes.tests.factories import CourseModeFactory @@ -36,8 +37,11 @@ from shoppingcart.models import Order, CertificateItem from embargo.test_utils import restrict_course from util.testing import UrlResetMixin from verify_student.views import ( - render_to_response, PayAndVerifyView, EVENT_NAME_USER_ENTERED_INCOURSE_REVERIFY_VIEW, - EVENT_NAME_USER_SUBMITTED_INCOURSE_REVERIFY + checkout_with_ecommerce_service, + EVENT_NAME_USER_ENTERED_INCOURSE_REVERIFY_VIEW, + EVENT_NAME_USER_SUBMITTED_INCOURSE_REVERIFY, + PayAndVerifyView, + render_to_response, ) from verify_student.models import ( SoftwareSecurePhotoVerification, VerificationCheckpoint, @@ -650,13 +654,18 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase): course.start = kwargs.get('course_start') modulestore().update_item(course, ModuleStoreEnum.UserID.test) + mode_kwargs = {} + if kwargs.get('sku'): + mode_kwargs['sku'] = kwargs['sku'] + for course_mode in course_modes: min_price = (0 if course_mode in ["honor", "audit"] else self.MIN_PRICE) CourseModeFactory( course_id=course.id, mode_slug=course_mode, mode_display_name=course_mode, - min_price=min_price + min_price=min_price, + **mode_kwargs ) return course @@ -819,6 +828,35 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase): self.assertEqual(response_dict['course_name'], mode_display_name) + @httpretty.activate + @override_settings(ECOMMERCE_API_URL=TEST_API_URL, ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY) + def test_processors_api(self): + """ + Check that when working with a product being processed by the + ecommerce api, we correctly call to that api for the list of + available payment processors. + """ + # setting a nonempty sku on the course will a trigger calls to + # the ecommerce api to get payment processors. + course = self._create_course("verified", sku='nonempty-sku') + self._enroll(course.id, "honor") + + # mock out the payment processors endpoint + httpretty.register_uri( + httpretty.GET, + "{}/payment/processors/".format(TEST_API_URL), + body=json.dumps(['foo', 'bar']), + content_type="application/json", + ) + # make the server request + response = self._get_page('verify_student_start_flow', course.id) + self.assertEqual(response.status_code, 200) + + # ensure the mock api call was made. NOTE: the following line + # approximates the check - if the headers were empty it means + # there was no last request. + self.assertNotEqual(httpretty.last_request().headers, {}) + class CheckoutTestMixin(object): """ @@ -927,7 +965,7 @@ class CheckoutTestMixin(object): # ensure the response to a request from a stale js client is modified so as # not to break behavior in the browser. # (XCOM-214) remove after release. - expected_payment_data = EcommerceApiTestMixin.PAYMENT_DATA.copy() + expected_payment_data = TEST_PAYMENT_DATA.copy() expected_payment_data['payment_form_data'].update({'foo': 'bar'}) patched_create_order.return_value = expected_payment_data # there is no 'processor' parameter in the post payload, so the response should only contain payment form data. @@ -945,7 +983,7 @@ class CheckoutTestMixin(object): self.assertEqual(data, {'foo': 'bar'}) -@patch('verify_student.views.checkout_with_shoppingcart', return_value=EcommerceApiTestMixin.PAYMENT_DATA) +@patch('verify_student.views.checkout_with_shoppingcart', return_value=TEST_PAYMENT_DATA) class TestCreateOrderShoppingCart(CheckoutTestMixin, ModuleStoreTestCase): """ Test view behavior when the shoppingcart is used. """ @@ -958,12 +996,9 @@ class TestCreateOrderShoppingCart(CheckoutTestMixin, ModuleStoreTestCase): return dict(zip(('request', 'user', 'course_key', 'course_mode', 'amount'), patched_create_order.call_args[0])) -@override_settings( - ECOMMERCE_API_URL=EcommerceApiTestMixin.ECOMMERCE_API_URL, - ECOMMERCE_API_SIGNING_KEY=EcommerceApiTestMixin.ECOMMERCE_API_SIGNING_KEY -) -@patch('verify_student.views.checkout_with_ecommerce_service', return_value=EcommerceApiTestMixin.PAYMENT_DATA) -class TestCreateOrderEcommerceService(CheckoutTestMixin, EcommerceApiTestMixin, ModuleStoreTestCase): +@override_settings(ECOMMERCE_API_URL=TEST_API_URL, ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY) +@patch('verify_student.views.checkout_with_ecommerce_service', return_value=TEST_PAYMENT_DATA) +class TestCreateOrderEcommerceService(CheckoutTestMixin, ModuleStoreTestCase): """ Test view behavior when the ecommerce service is used. """ def make_sku(self): @@ -975,6 +1010,40 @@ class TestCreateOrderEcommerceService(CheckoutTestMixin, EcommerceApiTestMixin, return dict(zip(('user', 'course_key', 'course_mode', 'processor'), patched_create_order.call_args[0])) +class TestCheckoutWithEcommerceService(ModuleStoreTestCase): + """ + Ensures correct behavior in the function `checkout_with_ecommerce_service`. + """ + + @httpretty.activate + @override_settings(ECOMMERCE_API_URL=TEST_API_URL, ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY) + def test_create_basket(self): + """ + Check that when working with a product being processed by the + ecommerce api, we correctly call to that api to create a basket. + """ + user = UserFactory.create(username="test-username") + course_mode = CourseModeFactory(sku="test-sku") + expected_payment_data = {'foo': 'bar'} + # mock out the payment processors endpoint + httpretty.register_uri( + httpretty.POST, + "{}/baskets/".format(TEST_API_URL), + body=json.dumps({'payment_data': expected_payment_data}), + content_type="application/json", + ) + # call the function + actual_payment_data = checkout_with_ecommerce_service(user, 'dummy-course-key', course_mode, 'test-processor') + # check the api call + self.assertEqual(json.loads(httpretty.last_request().body), { + 'products': [{'sku': 'test-sku'}], + 'checkout': True, + 'payment_processor_name': 'test-processor', + }) + # check the response + self.assertEqual(actual_payment_data, expected_payment_data) + + class TestCreateOrderView(ModuleStoreTestCase): """ Tests for the create_order view of verified course enrollment process. diff --git a/lms/djangoapps/verify_student/views.py b/lms/djangoapps/verify_student/views.py index 8f5dec9f15..4683837634 100644 --- a/lms/djangoapps/verify_student/views.py +++ b/lms/djangoapps/verify_student/views.py @@ -384,7 +384,7 @@ class PayAndVerifyView(View): # get available payment processors if unexpired_paid_course_mode.sku: # transaction will be conducted via ecommerce service - processors = ecommerce_api_client(request.user).get_processors() + processors = ecommerce_api_client(request.user).payment.processors.get() else: # transaction will be conducted using legacy shopping cart processors = [settings.CC_PROCESSOR_NAME] @@ -657,9 +657,13 @@ def checkout_with_ecommerce_service(user, course_key, course_mode, processor): try: api = ecommerce_api_client(user) # Make an API call to create the order and retrieve the results - response_data = api.create_basket(course_mode.sku, processor) + result = api.baskets.post({ + 'products': [{'sku': course_mode.sku}], + 'checkout': True, + 'payment_processor_name': processor + }) # Pass the payment parameters directly from the API response. - return response_data.get('payment_data') + return result.get('payment_data') except SlumberBaseException: params = {'username': user.username, 'mode': course_mode.slug, 'course_id': unicode(course_key)} log.exception('Failed to create order for %(username)s %(mode)s mode of %(course_id)s', params)