Use course run marketing URLs from the catalog service on program detail page

Part of ECOM-4566.
This commit is contained in:
Renzo Lucioni
2016-07-05 14:10:59 -04:00
parent b1fcc51c7f
commit a43c507a00
17 changed files with 377 additions and 36 deletions

View File

@@ -0,0 +1,14 @@
"""Factories for generating fake catalog data."""
import factory
from factory.fuzzy import FuzzyText
class CourseRun(factory.Factory):
"""
Factory for stubbing CourseRun resources from the catalog API.
"""
class Meta(object):
model = dict
key = FuzzyText(prefix='org/', suffix='/run')
marketing_url = FuzzyText(prefix='https://www.example.com/marketing/')

View File

@@ -0,0 +1,120 @@
"""Tests covering utilities for integrating with the catalog service."""
import ddt
from django.test import TestCase
import mock
from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.catalog import utils
from openedx.core.djangoapps.catalog.tests import factories, mixins
from student.tests.factories import UserFactory
UTILS_MODULE = 'openedx.core.djangoapps.catalog.utils'
@mock.patch(UTILS_MODULE + '.get_edx_api_data')
class TestGetCourseRun(mixins.CatalogIntegrationMixin, TestCase):
"""Tests covering retrieval of course runs from the catalog service."""
def setUp(self):
super(TestGetCourseRun, self).setUp()
self.user = UserFactory()
self.course_key = CourseKey.from_string('foo/bar/baz')
self.catalog_integration = self.create_catalog_integration()
def assert_contract(self, call_args):
"""Verify that API data retrieval utility is used correctly."""
args, kwargs = call_args
for arg in (self.catalog_integration, self.user, 'course_runs'):
self.assertIn(arg, args)
self.assertEqual(kwargs['resource_id'], unicode(self.course_key))
self.assertEqual(kwargs['api']._store['base_url'], self.catalog_integration.internal_api_url) # pylint: disable=protected-access
return args, kwargs
def test_get_course_run(self, mock_get_catalog_data):
course_run = factories.CourseRun()
mock_get_catalog_data.return_value = course_run
data = utils.get_course_run(self.course_key, self.user)
self.assert_contract(mock_get_catalog_data.call_args)
self.assertEqual(data, course_run)
def test_course_run_unavailable(self, mock_get_catalog_data):
mock_get_catalog_data.return_value = []
data = utils.get_course_run(self.course_key, self.user)
self.assert_contract(mock_get_catalog_data.call_args)
self.assertEqual(data, {})
def test_cache_disabled(self, mock_get_catalog_data):
utils.get_course_run(self.course_key, self.user)
_, kwargs = self.assert_contract(mock_get_catalog_data.call_args)
self.assertIsNone(kwargs['cache_key'])
def test_cache_enabled(self, mock_get_catalog_data):
catalog_integration = self.create_catalog_integration(cache_ttl=1)
utils.get_course_run(self.course_key, self.user)
_, kwargs = mock_get_catalog_data.call_args
self.assertEqual(kwargs['cache_key'], catalog_integration.CACHE_KEY)
@mock.patch(UTILS_MODULE + '.get_course_run')
@mock.patch(UTILS_MODULE + '.strip_querystring')
class TestGetRunMarketingUrl(TestCase):
"""Tests covering retrieval of course run marketing URLs."""
def setUp(self):
super(TestGetRunMarketingUrl, self).setUp()
self.course_key = CourseKey.from_string('foo/bar/baz')
self.user = UserFactory()
def test_get_run_marketing_url(self, mock_strip, mock_get_course_run):
course_run = factories.CourseRun()
mock_get_course_run.return_value = course_run
mock_strip.return_value = course_run['marketing_url']
url = utils.get_run_marketing_url(self.course_key, self.user)
self.assertTrue(mock_strip.called)
self.assertEqual(url, course_run['marketing_url'])
def test_marketing_url_empty(self, mock_strip, mock_get_course_run):
course_run = factories.CourseRun()
course_run['marketing_url'] = ''
mock_get_course_run.return_value = course_run
url = utils.get_run_marketing_url(self.course_key, self.user)
self.assertFalse(mock_strip.called)
self.assertEqual(url, None)
def test_marketing_url_missing(self, mock_strip, mock_get_course_run):
mock_get_course_run.return_value = {}
url = utils.get_run_marketing_url(self.course_key, self.user)
self.assertFalse(mock_strip.called)
self.assertEqual(url, None)
@ddt.ddt
class TestStripQuerystring(TestCase):
"""Tests covering querystring stripping."""
bare_url = 'https://www.example.com/path'
@ddt.data(
bare_url,
bare_url + '?foo=bar&baz=qux',
)
def test_strip_querystring(self, url):
self.assertEqual(utils.strip_querystring(url), self.bare_url)

View File

@@ -0,0 +1,69 @@
"""Helper functions for working with the catalog service."""
from urlparse import urlparse
from django.conf import settings
from edx_rest_api_client.client import EdxRestApiClient
from openedx.core.djangoapps.catalog.models import CatalogIntegration
from openedx.core.lib.edx_api_utils import get_edx_api_data
from openedx.core.lib.token_utils import JwtBuilder
def get_course_run(course_key, user):
"""Get a course run's data from the course catalog service.
Arguments:
course_key (CourseKey): Course key object identifying the run whose data we want.
user (User): The user to authenticate as when making requests to the catalog service.
Returns:
dict, empty if no data could be retrieved.
"""
catalog_integration = CatalogIntegration.current()
scopes = ['email', 'profile']
expires_in = settings.OAUTH_ID_TOKEN_EXPIRATION
jwt = JwtBuilder(user).build_token(scopes, expires_in)
api = EdxRestApiClient(catalog_integration.internal_api_url, jwt=jwt)
data = get_edx_api_data(
catalog_integration,
user,
'course_runs',
resource_id=unicode(course_key),
cache_key=catalog_integration.CACHE_KEY if catalog_integration.is_cache_enabled else None,
api=api,
)
return data if data else {}
def get_run_marketing_url(course_key, user):
"""Get a course run's marketing URL from the course catalog service.
Arguments:
course_key (CourseKey): Course key object identifying the run whose marketing URL we want.
user (User): The user to authenticate as when making requests to the catalog service.
Returns:
string, the marketing URL, or None if no URL is available.
"""
course_run = get_course_run(course_key, user)
marketing_url = course_run.get('marketing_url')
if marketing_url:
# This URL may include unwanted UTM parameters in the querystring.
# For more, see https://en.wikipedia.org/wiki/UTM_parameters.
return strip_querystring(marketing_url)
else:
return None
def strip_querystring(url):
"""Strip the querystring from the provided URL.
urlparse's ParseResult is a subclass of namedtuple. _replace is part of namedtuple's
public API: https://docs.python.org/2/library/collections.html#collections.somenamedtuple._replace.
The name starts with an underscore to prevent conflicts with field names.
"""
return urlparse(url)._replace(query='').geturl() # pylint: disable=no-member

View File

@@ -36,7 +36,8 @@ from xmodule.modulestore.tests.factories import CourseFactory
UTILS_MODULE = 'openedx.core.djangoapps.programs.utils'
CERTIFICATES_API_MODULE = 'lms.djangoapps.certificates.api'
ECOMMERCE_URL_ROOT = 'http://example-ecommerce.com'
ECOMMERCE_URL_ROOT = 'https://example-ecommerce.com'
MARKETING_URL = 'https://www.example.com/marketing/path'
@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
@@ -677,6 +678,7 @@ class TestProgramProgressMeter(ProgramsApiConfigMixin, TestCase):
@ddt.ddt
@override_settings(ECOMMERCE_PUBLIC_URL_ROOT=ECOMMERCE_URL_ROOT)
@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
@mock.patch(UTILS_MODULE + '.get_run_marketing_url', mock.Mock(return_value=MARKETING_URL))
class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
"""Tests of the utility function used to supplement program data."""
maxDiff = None
@@ -719,7 +721,7 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
is_course_ended=self.course.end < timezone.now(),
is_enrolled=False,
is_enrollment_open=True,
marketing_url=None,
marketing_url=MARKETING_URL,
start_date=strftime_localized(self.course.start, 'SHORT_DATE'),
upgrade_url=None,
),

View File

@@ -13,6 +13,7 @@ import pytz
from course_modes.models import CourseMode
from lms.djangoapps.certificates import api as certificate_api
from lms.djangoapps.commerce.utils import EcommerceService
from openedx.core.djangoapps.catalog.utils import get_run_marketing_url
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.lib.edx_api_utils import get_edx_api_data
@@ -392,8 +393,7 @@ def supplement_program_data(program_data, user):
'is_course_ended': is_course_ended,
'is_enrolled': is_enrolled,
'is_enrollment_open': is_enrollment_open,
# TODO: Not currently available on LMS.
'marketing_url': None,
'marketing_url': get_run_marketing_url(course_key, user),
'start_date': start_date_string,
'upgrade_url': upgrade_url,
})