Commit 562f35bc by mikedikan Committed by GitHub

Merge pull request #14181 from edx/mdikan/add-utm-tracking-to-free-purchases

Adding support for UTM Tracking to apply to free purhases
parents 7daf1df9 8f4cf682
...@@ -31,6 +31,11 @@ from student.tests.tests import EnrollmentEventTestMixin ...@@ -31,6 +31,11 @@ from student.tests.tests import EnrollmentEventTestMixin
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from commerce.api.v0.views import SAILTHRU_CAMPAIGN_COOKIE 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) @attr(shard=1)
@ddt.ddt @ddt.ddt
...@@ -39,7 +44,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -39,7 +44,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
""" """
Tests for the commerce orders view. 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. POST to the view being tested.
...@@ -55,6 +60,8 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -55,6 +60,8 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
payload["email_opt_in"] = True payload["email_opt_in"] = True
self.client.cookies[SAILTHRU_CAMPAIGN_COOKIE] = 'sailthru id' 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) return self.client.post(self.url, payload)
def assertResponseMessage(self, response, expected_msg): def assertResponseMessage(self, response, expected_msg):
...@@ -160,12 +167,12 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -160,12 +167,12 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
self.assertValidEcommerceInternalRequestErrorResponse(response) self.assertValidEcommerceInternalRequestErrorResponse(response)
self.assertUserNotEnrolled() 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. 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: 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 # Verify that an audit message was logged
self.assertTrue(mock_audit_log.called) self.assertTrue(mock_audit_log.called)
...@@ -177,9 +184,15 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -177,9 +184,15 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
else: else:
self.assertResponsePaymentData(response) 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']) 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) @ddt.data(True, False)
def test_course_with_honor_seat_sku(self, user_is_active): def test_course_with_honor_seat_sku(self, user_is_active):
""" """
...@@ -193,7 +206,14 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -193,7 +206,14 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
return_value = {'id': TEST_BASKET_ID, 'payment_data': None, 'order': {'number': TEST_ORDER_NUMBER}} return_value = {'id': TEST_BASKET_ID, 'payment_data': None, 'order': {'number': TEST_ORDER_NUMBER}}
with mock_create_basket(response=return_value): with mock_create_basket(response=return_value):
# Test that call without utm tracking works
self._test_successful_ecommerce_api_call() 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) @ddt.data(True, False)
def test_course_with_paid_seat_sku(self, user_is_active): def test_course_with_paid_seat_sku(self, user_is_active):
...@@ -207,7 +227,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -207,7 +227,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
return_value = {'id': TEST_BASKET_ID, 'payment_data': TEST_PAYMENT_DATA, 'order': None} return_value = {'id': TEST_BASKET_ID, 'payment_data': TEST_PAYMENT_DATA, 'order': None}
with mock_create_basket(response=return_value): 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): def _test_course_without_sku(self, enrollment_mode=CourseMode.DEFAULT_MODE_SLUG):
""" """
...@@ -334,7 +354,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -334,7 +354,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
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(): 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') @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)))
......
...@@ -22,7 +22,7 @@ from openedx.core.djangoapps.commerce.utils import ecommerce_api_client ...@@ -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.djangoapps.user_api.preferences.api import update_email_opt_in
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 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
...@@ -150,13 +150,11 @@ class BasketsView(APIView): ...@@ -150,13 +150,11 @@ class BasketsView(APIView):
# Make the API call # Make the API call
try: try:
# Pass along Sailthru campaign id # Pass along Sailthru campaign id
campaign_cookie = request.COOKIES.get(SAILTHRU_CAMPAIGN_COOKIE) self._add_request_cookie_to_api_session(api_session, request, SAILTHRU_CAMPAIGN_COOKIE)
if campaign_cookie:
cookie = {SAILTHRU_CAMPAIGN_COOKIE: campaign_cookie} # Pass along UTM tracking info
if api_session.cookies: utm_cookie_name = RegistrationCookieConfiguration.current().utm_cookie_name
requests.utils.add_dict_to_cookiejar(api_session.cookies, cookie) self._add_request_cookie_to_api_session(api_session, request, utm_cookie_name)
else:
api_session.cookies = requests.utils.cookiejar_from_dict(cookie)
response_data = api.baskets.post({ response_data = api.baskets.post({
'products': [{'sku': default_enrollment_mode.sku}], 'products': [{'sku': default_enrollment_mode.sku}],
...@@ -194,6 +192,18 @@ class BasketsView(APIView): ...@@ -194,6 +192,18 @@ class BasketsView(APIView):
self._handle_marketing_opt_in(request, course_key, user) self._handle_marketing_opt_in(request, course_key, user)
return response 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): class BasketOrderView(APIView):
""" Retrieve the order associated with a basket. """ """ Retrieve the order associated with a basket. """
......
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