fix ecommerce api calls in verify_student and update tests.
XCOM-287
This commit is contained in:
@@ -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
|
||||
|
||||
95
lms/djangoapps/commerce/tests/mocks.py
Normal file
95
lms/djangoapps/commerce/tests/mocks.py
Normal file
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user