Commit 32679b04 by ayub-khan

Removed waffle flag on commerce api for default_enrollment_mode

Removed ecommerce calls for default_enrollment_mode order creation
Removed tests related to those
LEARNER-2294
parent e4deef9d
...@@ -5,7 +5,6 @@ from datetime import datetime, timedelta ...@@ -5,7 +5,6 @@ from datetime import datetime, timedelta
from uuid import uuid4 from uuid import uuid4
import ddt import ddt
import httpretty
import mock import mock
import pytz import pytz
from django.conf import settings from django.conf import settings
...@@ -15,15 +14,13 @@ from django.test.utils import override_settings ...@@ -15,15 +14,13 @@ from django.test.utils import override_settings
from edx_rest_api_client import exceptions from edx_rest_api_client import exceptions
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from commerce.api.v0.views import SAILTHRU_CAMPAIGN_COOKIE, STOP_BASKET_CREATION_FLAG from commerce.api.v0.views import SAILTHRU_CAMPAIGN_COOKIE
from commerce.constants import Messages from commerce.constants import Messages
from commerce.tests import TEST_BASKET_ID, TEST_ORDER_NUMBER, TEST_PAYMENT_DATA from commerce.tests.mocks import mock_basket_order
from commerce.tests.mocks import mock_basket_order, mock_create_basket
from commerce.tests.test_views import UserMixin from commerce.tests.test_views import UserMixin
from course_modes.models import CourseMode from course_modes.models import CourseMode
from enrollment.api import get_enrollment from enrollment.api import get_enrollment
from openedx.core.djangoapps.embargo.test_utils import restrict_course from openedx.core.djangoapps.embargo.test_utils import restrict_course
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
from openedx.core.lib.django_test_client_utils import get_absolute_url from openedx.core.lib.django_test_client_utils import get_absolute_url
from student.models import CourseEnrollment from student.models import CourseEnrollment
from course_modes.tests.factories import CourseModeFactory from course_modes.tests.factories import CourseModeFactory
...@@ -42,7 +39,7 @@ UTM_COOKIE_CONTENTS = { ...@@ -42,7 +39,7 @@ UTM_COOKIE_CONTENTS = {
@ddt.ddt @ddt.ddt
class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase): class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase):
""" """
Tests for the commerce orders view. Tests for the commerce Baskets view.
""" """
def _post_to_view(self, course_id=None, marketing_email_opt_in=False, include_utm_cookie=False): def _post_to_view(self, course_id=None, marketing_email_opt_in=False, include_utm_cookie=False):
""" """
...@@ -69,22 +66,6 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -69,22 +66,6 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
actual = json.loads(response.content)['detail'] actual = json.loads(response.content)['detail']
self.assertEqual(actual, expected_msg) self.assertEqual(actual, expected_msg)
def assertResponsePaymentData(self, response):
""" Asserts correctness of a JSON body containing payment information. """
actual_response = json.loads(response.content)
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. """
self.assertEqual(response.status_code, 500)
actual = json.loads(response.content)['detail']
self.assertIn('Call to E-Commerce API failed', actual)
def assertUserNotEnrolled(self):
""" Asserts that the user is NOT enrolled in the course, and that an enrollment event was NOT fired. """
self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course.id))
self.assert_no_events_were_emitted()
def setUp(self): def setUp(self):
super(BasketsViewTests, self).setUp() super(BasketsViewTests, self).setUp()
self.url = reverse('commerce_api:v0:baskets:create') self.url = reverse('commerce_api:v0:baskets:create')
...@@ -147,102 +128,15 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -147,102 +128,15 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
self.assertEqual(406, self.client.post(self.url, {}).status_code) self.assertEqual(406, self.client.post(self.url, {}).status_code)
self.assertEqual(406, self.client.post(self.url, {'not_course_id': ''}).status_code) self.assertEqual(406, self.client.post(self.url, {'not_course_id': ''}).status_code)
def test_ecommerce_api_timeout(self):
"""
If the call to the E-Commerce API times out, the view should log an error and return an HTTP 503 status.
"""
with mock_create_basket(exception=exceptions.Timeout):
response = self._post_to_view()
self.assertValidEcommerceInternalRequestErrorResponse(response)
self.assertUserNotEnrolled()
def test_ecommerce_api_error(self):
"""
If the E-Commerce API raises an error, the view should return an HTTP 503 status.
"""
with mock_create_basket(exception=exceptions.SlumberBaseException):
response = self._post_to_view()
self.assertValidEcommerceInternalRequestErrorResponse(response)
self.assertUserNotEnrolled()
def _test_successful_ecommerce_api_call(self, is_completed=True, utm_tracking_present=False):
"""
Verifies that the view contacts the E-Commerce API with the correct data and headers.
"""
with mock.patch('commerce.api.v0.views.audit_log') as mock_audit_log:
response = self._post_to_view(include_utm_cookie=utm_tracking_present)
# Verify that an audit message was logged
self.assertTrue(mock_audit_log.called)
# Validate the response content
if is_completed:
msg = Messages.ORDER_COMPLETED.format(order_number=TEST_ORDER_NUMBER)
self.assertResponseMessage(response, msg)
else:
self.assertResponsePaymentData(response)
# Make sure ecommerce API call forwards Sailthru cookie
self.assertIn('{}=sailthru id'.format(SAILTHRU_CAMPAIGN_COOKIE), httpretty.last_request().headers['cookie'])
# Check that UTM tracking cookie is passed along in request to ecommerce for attribution
if utm_tracking_present:
cookie_string = '{cookie_name}={cookie_contents}'.format(
cookie_name=UTM_COOKIE_NAME, cookie_contents=json.dumps(UTM_COOKIE_CONTENTS))
self.assertIn(cookie_string, httpretty.last_request().headers['cookie'])
@override_waffle_flag(STOP_BASKET_CREATION_FLAG, active=False)
@ddt.data(True, False)
def test_course_with_honor_seat_sku(self, user_is_active):
"""
If the course has a SKU, the view should get authorization from the E-Commerce API before enrolling
the user in the course. If authorization is approved, the user should be redirected to the user dashboard.
"""
# Set user's active flag
self.user.is_active = user_is_active
self.user.save() # pylint: disable=no-member
return_value = {'id': TEST_BASKET_ID, 'payment_data': None, 'order': {'number': TEST_ORDER_NUMBER}}
with mock_create_basket(response=return_value):
# Test that call without utm tracking works
self._test_successful_ecommerce_api_call()
with mock.patch('student.models.RegistrationCookieConfiguration.current') as config:
instance = config.return_value
instance.utm_cookie_name = UTM_COOKIE_NAME
# Test that call with cookie passes cookie along
self._test_successful_ecommerce_api_call(utm_tracking_present=True)
@override_waffle_flag(STOP_BASKET_CREATION_FLAG, active=False)
@ddt.data(True, False)
def test_course_with_paid_seat_sku(self, user_is_active):
"""
If the course has a SKU, the view should return data that the client
will use to redirect the user to an external payment processor.
"""
# Set user's active flag
self.user.is_active = user_is_active
self.user.save() # pylint: disable=no-member
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(is_completed=False)
@override_waffle_flag(STOP_BASKET_CREATION_FLAG, active=True)
@ddt.data(True, False) @ddt.data(True, False)
def test_course_without_creating_order(self, user_is_active): def test_course_for_active_and_inactive_user(self, user_is_active):
""" """
If the course has a SKU, and the STOP_BASKET_CREATION waffle flag is on, Test course enrollment for active and inactive user.
the enrollment should happen without contacting ecommerce api
""" """
# Set user's active flag # Set user's active flag
self.user.is_active = user_is_active self.user.is_active = user_is_active
self.user.save() # pylint: disable=no-member self.user.save() # pylint: disable=no-member
with mock_create_basket(expect_called=False): response = self._post_to_view()
response = self._post_to_view()
# Validate the response content # Validate the response content
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
...@@ -254,11 +148,9 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -254,11 +148,9 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
def _test_course_without_sku(self, enrollment_mode=CourseMode.DEFAULT_MODE_SLUG): def _test_course_without_sku(self, enrollment_mode=CourseMode.DEFAULT_MODE_SLUG):
""" """
Validates the view bypasses the E-Commerce API when the course has no CourseModes with SKUs. Validates the view when course has no CourseModes with SKUs.
""" """
# Place an order response = self._post_to_view()
with mock_create_basket(expect_called=False):
response = self._post_to_view()
# Validate the response content # Validate the response content
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
...@@ -299,22 +191,6 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -299,22 +191,6 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
# We should be enrolled in honor mode # We should be enrolled in honor mode
self._test_course_without_sku(enrollment_mode=CourseMode.HONOR) self._test_course_without_sku(enrollment_mode=CourseMode.HONOR)
@override_settings(ECOMMERCE_API_URL=None)
def test_ecommerce_service_not_configured(self):
"""
If the E-Commerce Service is not configured, the view should enroll the user.
"""
with mock_create_basket(expect_called=False):
response = self._post_to_view()
# Validate the response
self.assertEqual(response.status_code, 200)
msg = Messages.NO_ECOM_API.format(username=self.user.username, course_id=self.course.id)
self.assertResponseMessage(response, msg)
# 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))
def assertProfessionalModeBypassed(self): def assertProfessionalModeBypassed(self):
""" Verifies that the view returns HTTP 406 when a course with no honor or audit mode is encountered. """ """ Verifies that the view returns HTTP 406 when a course with no honor or audit mode is encountered. """
...@@ -323,9 +199,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -323,9 +199,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
sku_string = uuid4().hex.decode('ascii') sku_string = uuid4().hex.decode('ascii')
CourseModeFactory.create(course_id=self.course.id, mode_slug=mode, mode_display_name=mode, CourseModeFactory.create(course_id=self.course.id, mode_slug=mode, mode_display_name=mode,
sku=sku_string, bulk_sku='BULK-{}'.format(sku_string)) sku=sku_string, bulk_sku='BULK-{}'.format(sku_string))
response = self._post_to_view()
with mock_create_basket(expect_called=False):
response = self._post_to_view()
# The view should return an error status code # The view should return an error status code
self.assertEqual(response.status_code, 406) self.assertEqual(response.status_code, 406)
...@@ -376,9 +250,6 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -376,9 +250,6 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course.id)) self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course.id))
self.assertIsNotNone(get_enrollment(self.user.username, unicode(self.course.id))) self.assertIsNotNone(get_enrollment(self.user.username, unicode(self.course.id)))
with mock_create_basket():
self._test_successful_ecommerce_api_call(is_completed=False)
@mock.patch('commerce.api.v0.views.update_email_opt_in') @mock.patch('commerce.api.v0.views.update_email_opt_in')
@ddt.data(*itertools.product((False, True), (False, True), (False, True))) @ddt.data(*itertools.product((False, True), (False, True), (False, True)))
@ddt.unpack @ddt.unpack
...@@ -395,21 +266,17 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -395,21 +266,17 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
if is_exception: if is_exception:
mock_update.side_effect = Exception("boink") mock_update.side_effect = Exception("boink")
return_value = {'id': TEST_BASKET_ID, 'payment_data': None, 'order': {'number': TEST_ORDER_NUMBER}} response = self._post_to_view(marketing_email_opt_in=is_opt_in)
with mock_create_basket(response=return_value, expect_called=has_sku):
response = self._post_to_view(marketing_email_opt_in=is_opt_in)
self.assertEqual(mock_update.called, is_opt_in) self.assertEqual(mock_update.called, is_opt_in)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_closed_course(self): def test_closed_course(self):
""" """
Ensure that the view does not attempt to create a basket for closed Verifies that the view returns HTTP 406 when a course is closed.
courses.
""" """
self.course.enrollment_end = datetime.now(pytz.UTC) - timedelta(days=1) self.course.enrollment_end = datetime.now(pytz.UTC) - timedelta(days=1)
modulestore().update_item(self.course, self.user.id) # pylint:disable=no-member modulestore().update_item(self.course, self.user.id) # pylint:disable=no-member
with mock_create_basket(expect_called=False): self.assertEqual(self._post_to_view().status_code, 406)
self.assertEqual(self._post_to_view().status_code, 406)
@attr(shard=1) @attr(shard=1)
......
""" API v0 views. """ """ API v0 views. """
import logging import logging
import requests
from edx_rest_api_client import exceptions from edx_rest_api_client import exceptions
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
...@@ -11,9 +10,7 @@ from rest_framework.status import HTTP_406_NOT_ACCEPTABLE, HTTP_409_CONFLICT ...@@ -11,9 +10,7 @@ from rest_framework.status import HTTP_406_NOT_ACCEPTABLE, HTTP_409_CONFLICT
from rest_framework.views import APIView from rest_framework.views import APIView
from commerce.constants import Messages from commerce.constants import Messages
from commerce.exceptions import InvalidResponseError from commerce.http import DetailResponse
from commerce.http import DetailResponse, InternalRequestErrorResponse
from commerce.utils import COMMERCE_API_WAFFLE_FLAG_NAMESPACE
from course_modes.models import CourseMode from course_modes.models import CourseMode
from courseware import courses from courseware import courses
from enrollment.api import add_enrollment from enrollment.api import add_enrollment
...@@ -21,15 +18,12 @@ from enrollment.views import EnrollmentCrossDomainSessionAuth ...@@ -21,15 +18,12 @@ from enrollment.views import EnrollmentCrossDomainSessionAuth
from openedx.core.djangoapps.commerce.utils import ecommerce_api_client from openedx.core.djangoapps.commerce.utils import ecommerce_api_client
from openedx.core.djangoapps.embargo import api as embargo_api from openedx.core.djangoapps.embargo import api as embargo_api
from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in
from openedx.core.djangoapps.waffle_utils import WaffleFlag
from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser
from openedx.core.lib.log_utils import audit_log from student.models import CourseEnrollment
from student.models import CourseEnrollment, RegistrationCookieConfiguration
from util.json_request import JsonResponse from util.json_request import JsonResponse
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
SAILTHRU_CAMPAIGN_COOKIE = 'sailthru_bid' SAILTHRU_CAMPAIGN_COOKIE = 'sailthru_bid'
STOP_BASKET_CREATION_FLAG = WaffleFlag(COMMERCE_API_WAFFLE_FLAG_NAMESPACE, 'stop_basket_creation')
class BasketsView(APIView): class BasketsView(APIView):
...@@ -85,7 +79,7 @@ class BasketsView(APIView): ...@@ -85,7 +79,7 @@ class BasketsView(APIView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
""" """
Attempt to enroll the user, and if needed, create the basket. Attempt to enroll the user.
""" """
user = request.user user = request.user
valid, course_key, error = self._is_data_valid(request) valid, course_key, error = self._is_data_valid(request)
...@@ -120,11 +114,7 @@ class BasketsView(APIView): ...@@ -120,11 +114,7 @@ class BasketsView(APIView):
# Accept either honor or audit as an enrollment mode to # Accept either honor or audit as an enrollment mode to
# maintain backwards compatibility with existing courses # maintain backwards compatibility with existing courses
default_enrollment_mode = audit_mode or honor_mode default_enrollment_mode = audit_mode or honor_mode
if default_enrollment_mode:
if not default_enrollment_mode:
msg = Messages.NO_DEFAULT_ENROLLMENT_MODE.format(course_id=course_id)
return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE)
elif not default_enrollment_mode.sku or STOP_BASKET_CREATION_FLAG.is_enabled():
msg = Messages.ENROLL_DIRECTLY.format( msg = Messages.ENROLL_DIRECTLY.format(
username=user.username, username=user.username,
course_id=course_id course_id=course_id
...@@ -141,81 +131,8 @@ class BasketsView(APIView): ...@@ -141,81 +131,8 @@ class BasketsView(APIView):
self._handle_marketing_opt_in(request, course_key, user) self._handle_marketing_opt_in(request, course_key, user)
return DetailResponse(msg) return DetailResponse(msg)
else: else:
return self._create_basket_to_order(request, user, course_key, default_enrollment_mode) msg = Messages.NO_DEFAULT_ENROLLMENT_MODE.format(course_id=course_id)
return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE)
def _add_request_cookie_to_api_session(self, server_session, request, cookie_name):
""" Add cookie from user request into server session """
user_cookie = None
if cookie_name:
user_cookie = request.COOKIES.get(cookie_name)
if user_cookie:
server_cookie = {cookie_name: user_cookie}
if server_session.cookies:
requests.utils.add_dict_to_cookiejar(server_session.cookies, server_cookie)
else:
server_session.cookies = requests.utils.cookiejar_from_dict(server_cookie)
def _create_basket_to_order(self, request, user, course_key, default_enrollment_mode):
"""
Connect to the ecommerce service to create the basket and the order to do the enrollment
"""
# Setup the API
course_id = unicode(course_key)
try:
api_session = requests.Session()
api = ecommerce_api_client(user, session=api_session)
except ValueError:
self._enroll(course_key, user)
msg = Messages.NO_ECOM_API.format(username=user.username, course_id=course_id)
log.debug(msg)
return DetailResponse(msg)
response = None
# Make the API call
try:
# Pass along Sailthru campaign id
self._add_request_cookie_to_api_session(api_session, request, SAILTHRU_CAMPAIGN_COOKIE)
# Pass along UTM tracking info
utm_cookie_name = RegistrationCookieConfiguration.current().utm_cookie_name
self._add_request_cookie_to_api_session(api_session, request, utm_cookie_name)
response_data = api.baskets.post({
'products': [{'sku': default_enrollment_mode.sku}],
'checkout': True,
})
payment_data = response_data["payment_data"]
if payment_data:
# Pass data to the client to begin the payment flow.
response = JsonResponse(payment_data)
elif response_data['order']:
# The order was completed immediately because there is no charge.
msg = Messages.ORDER_COMPLETED.format(order_number=response_data['order']['number'])
log.debug(msg)
response = DetailResponse(msg)
else:
msg = u'Unexpected response from basket endpoint.'
log.error(
msg + u' Could not enroll user %(username)s in course %(course_id)s.',
{'username': user.id, 'course_id': course_id},
)
raise InvalidResponseError(msg)
except (exceptions.SlumberBaseException, exceptions.Timeout) as ex:
log.exception(ex.message)
return InternalRequestErrorResponse(ex.message)
finally:
audit_log(
'checkout_requested',
course_id=course_id,
mode=default_enrollment_mode.slug,
processor_name=None,
user_id=user.id
)
self._handle_marketing_opt_in(request, course_key, user)
return response
class BasketOrderView(APIView): class BasketOrderView(APIView):
......
...@@ -80,24 +80,6 @@ class mock_ecommerce_api_endpoint(object): ...@@ -80,24 +80,6 @@ class mock_ecommerce_api_endpoint(object):
httpretty.reset() httpretty.reset()
class mock_create_basket(mock_ecommerce_api_endpoint):
""" 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_path(self):
return '/baskets/'
class mock_basket_order(mock_ecommerce_api_endpoint): class mock_basket_order(mock_ecommerce_api_endpoint):
""" Mocks calls to E-Commerce API client basket order method. """ """ Mocks calls to E-Commerce API client basket order method. """
......
...@@ -7,9 +7,6 @@ from django.conf import settings ...@@ -7,9 +7,6 @@ from django.conf import settings
from commerce.models import CommerceConfiguration from commerce.models import CommerceConfiguration
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.waffle_utils import WaffleFlagNamespace
COMMERCE_API_WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='commerce_api')
def is_account_activation_requirement_disabled(): def is_account_activation_requirement_disabled():
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment