diff --git a/lms/djangoapps/commerce/api/v0/tests/test_views.py b/lms/djangoapps/commerce/api/v0/tests/test_views.py index f3b8491540..ac3a66a458 100644 --- a/lms/djangoapps/commerce/api/v0/tests/test_views.py +++ b/lms/djangoapps/commerce/api/v0/tests/test_views.py @@ -31,6 +31,11 @@ from student.tests.tests import EnrollmentEventTestMixin from xmodule.modulestore.django import modulestore from commerce.api.v0.views import SAILTHRU_CAMPAIGN_COOKIE +UTM_COOKIE_NAME = 'edx.test.utm' +UTM_COOKIE_CONTENTS = { + 'utm_source': 'test-source' +} + @attr(shard=1) @ddt.ddt @@ -39,7 +44,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) """ Tests for the commerce orders view. """ - def _post_to_view(self, course_id=None, marketing_email_opt_in=False): + def _post_to_view(self, course_id=None, marketing_email_opt_in=False, include_utm_cookie=False): """ POST to the view being tested. @@ -55,6 +60,8 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) payload["email_opt_in"] = True self.client.cookies[SAILTHRU_CAMPAIGN_COOKIE] = 'sailthru id' + if include_utm_cookie: + self.client.cookies[UTM_COOKIE_NAME] = json.dumps(UTM_COOKIE_CONTENTS) return self.client.post(self.url, payload) def assertResponseMessage(self, response, expected_msg): @@ -160,12 +167,12 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) self.assertValidEcommerceInternalRequestErrorResponse(response) self.assertUserNotEnrolled() - def _test_successful_ecommerce_api_call(self, is_completed=True): + 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() + response = self._post_to_view(include_utm_cookie=utm_tracking_present) # Verify that an audit message was logged self.assertTrue(mock_audit_log.called) @@ -177,9 +184,15 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) else: self.assertResponsePaymentData(response) - # make sure ecommerce API call forwards Sailthru cookie + # 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']) + @ddt.data(True, False) def test_course_with_honor_seat_sku(self, user_is_active): """ @@ -193,7 +206,14 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) 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) @ddt.data(True, False) def test_course_with_paid_seat_sku(self, user_is_active): @@ -207,7 +227,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) 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) + self._test_successful_ecommerce_api_call(is_completed=False) def _test_course_without_sku(self, enrollment_mode=CourseMode.DEFAULT_MODE_SLUG): """ @@ -334,7 +354,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) self.assertIsNotNone(get_enrollment(self.user.username, unicode(self.course.id))) with mock_create_basket(): - self._test_successful_ecommerce_api_call(False) + self._test_successful_ecommerce_api_call(is_completed=False) @mock.patch('commerce.api.v0.views.update_email_opt_in') @ddt.data(*itertools.product((False, True), (False, True), (False, True))) diff --git a/lms/djangoapps/commerce/api/v0/views.py b/lms/djangoapps/commerce/api/v0/views.py index 3ea812412b..b427c89236 100644 --- a/lms/djangoapps/commerce/api/v0/views.py +++ b/lms/djangoapps/commerce/api/v0/views.py @@ -22,7 +22,7 @@ from openedx.core.djangoapps.commerce.utils import ecommerce_api_client from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in 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 @@ -150,13 +150,11 @@ class BasketsView(APIView): # Make the API call try: # Pass along Sailthru campaign id - campaign_cookie = request.COOKIES.get(SAILTHRU_CAMPAIGN_COOKIE) - if campaign_cookie: - cookie = {SAILTHRU_CAMPAIGN_COOKIE: campaign_cookie} - if api_session.cookies: - requests.utils.add_dict_to_cookiejar(api_session.cookies, cookie) - else: - api_session.cookies = requests.utils.cookiejar_from_dict(cookie) + 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}], @@ -194,6 +192,18 @@ class BasketsView(APIView): self._handle_marketing_opt_in(request, course_key, user) return response + 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) + class BasketOrderView(APIView): """ Retrieve the order associated with a basket. """