diff --git a/lms/djangoapps/commerce/tests/__init__.py b/lms/djangoapps/commerce/tests/__init__.py index fdf63630e8..74a82d36a8 100644 --- a/lms/djangoapps/commerce/tests/__init__.py +++ b/lms/djangoapps/commerce/tests/__init__.py @@ -4,6 +4,7 @@ import datetime import json from django.conf import settings +from django.contrib.auth.models import User from django.test import TestCase from django.test.utils import override_settings from freezegun import freeze_time @@ -99,3 +100,13 @@ class EdxRestApiClientTest(TestCase): ) actual_object = ecommerce_api_client(self.user).baskets(1).order.get() self.assertEqual(actual_object, {u"result": u"Préparatoire"}) + + def test_client_with_user_without_profile(self): + """ + Verify client initialize successfully for users having no profile. + """ + worker = User.objects.create_user(username='test_worker', email='test@example.com') + api_client = ecommerce_api_client(worker) + + self.assertEqual(api_client._store['session'].auth.__dict__['username'], worker.username) # pylint: disable=protected-access + self.assertIsNone(api_client._store['session'].auth.__dict__['full_name']) # pylint: disable=protected-access diff --git a/lms/templates/credit_notifications/credit_eligibility_email.html b/lms/templates/credit_notifications/credit_eligibility_email.html index b587b76499..752bed9f2a 100644 --- a/lms/templates/credit_notifications/credit_eligibility_email.html +++ b/lms/templates/credit_notifications/credit_eligibility_email.html @@ -34,13 +34,25 @@
- ${_(u"Congratulations! You are eligible to receive course credit for successfully completing your {platform_name} course! {link_start}Get your credit now.{link_end}").format( - link_start=u''.format( - dashboard_url=dashboard_link - ), - link_end=u'', - platform_name=settings.PLATFORM_NAME - )} + + % if providers: + ${_(u"Congratulations! You are eligible to receive course credit from {providers} for successfully completing your {platform_name} course! {link_start}Get your credit now.{link_end}").format( + link_start=u''.format( + dashboard_url=dashboard_link + ), + link_end=u'', + platform_name=settings.PLATFORM_NAME, + providers=providers + )} + % else: + ${_(u"Congratulations! You are eligible to receive course credit for successfully completing your {platform_name} course! {link_start}Get your credit now.{link_end}").format( + link_start=u''.format( + dashboard_url=dashboard_link + ), + link_end=u'', + platform_name=settings.PLATFORM_NAME + )} + % endif
diff --git a/lms/templates/credit_notifications/credit_eligibility_email.txt b/lms/templates/credit_notifications/credit_eligibility_email.txt index c0e6284497..76b794652d 100644 --- a/lms/templates/credit_notifications/credit_eligibility_email.txt +++ b/lms/templates/credit_notifications/credit_eligibility_email.txt @@ -5,7 +5,11 @@ ${_(u"Hi {name},").format(name=full_name)} ${_(u"Hi,")} % endif -${_(u"Congratulations! You are eligible to receive course credit for successfully completing your edX course!")} +% if providers: + ${_(u"Congratulations! You are eligible to receive course credit from {providers} for successfully completing your edX course!").format(providers=providers)} +% else: + ${_(u"Congratulations! You are eligible to receive course credit for successfully completing your edX course!")} +% endif ${_(u"Click on the link below to get your credit now:")} diff --git a/openedx/core/djangoapps/commerce/utils.py b/openedx/core/djangoapps/commerce/utils.py index 1c0fde66e2..676793a5e1 100644 --- a/openedx/core/djangoapps/commerce/utils.py +++ b/openedx/core/djangoapps/commerce/utils.py @@ -28,11 +28,13 @@ def is_commerce_service_configured(): def ecommerce_api_client(user): """ Returns an E-Commerce API client setup with authentication for the specified user. """ - return EdxRestApiClient(settings.ECOMMERCE_API_URL, - settings.ECOMMERCE_API_SIGNING_KEY, - user.username, - user.profile.name, - user.email, - tracking_context=create_tracking_context(user), - issuer=settings.JWT_ISSUER, - expires_in=settings.JWT_EXPIRATION) + return EdxRestApiClient( + settings.ECOMMERCE_API_URL, + settings.ECOMMERCE_API_SIGNING_KEY, + user.username, + user.profile.name if hasattr(user, 'profile') else None, + user.email, + tracking_context=create_tracking_context(user), + issuer=settings.JWT_ISSUER, + expires_in=settings.JWT_EXPIRATION + ) diff --git a/openedx/core/djangoapps/credit/admin.py b/openedx/core/djangoapps/credit/admin.py index 744c8f0688..3fd6d89943 100644 --- a/openedx/core/djangoapps/credit/admin.py +++ b/openedx/core/djangoapps/credit/admin.py @@ -3,7 +3,7 @@ Django admin page for credit eligibility """ from ratelimitbackend import admin from openedx.core.djangoapps.credit.models import ( - CreditCourse, CreditProvider, CreditEligibility, CreditRequest + CreditConfig, CreditCourse, CreditProvider, CreditEligibility, CreditRequest ) @@ -51,3 +51,4 @@ admin.site.register(CreditCourse, CreditCourseAdmin) admin.site.register(CreditProvider, CreditProviderAdmin) admin.site.register(CreditEligibility, CreditEligibilityAdmin) admin.site.register(CreditRequest, CreditRequestAdmin) +admin.site.register(CreditConfig) diff --git a/openedx/core/djangoapps/credit/email_utils.py b/openedx/core/djangoapps/credit/email_utils.py index 3375abb48f..fc555dc426 100644 --- a/openedx/core/djangoapps/credit/email_utils.py +++ b/openedx/core/djangoapps/credit/email_utils.py @@ -21,10 +21,11 @@ from django.utils.translation import ugettext as _ from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart from eventtracking import tracker - from edxmako.shortcuts import render_to_string from edxmako.template import Template from microsite_configuration import microsite +from openedx.core.djangoapps.commerce.utils import ecommerce_api_client +from openedx.core.djangoapps.credit.models import CreditConfig, CreditProvider from xmodule.modulestore.django import modulestore @@ -67,6 +68,26 @@ def send_credit_notifications(username, course_key): # strip enclosing angle brackets from 'logo_image' cache 'Content-ID' logo_image_id = logo_image.get('Content-ID', '')[1:-1] + providers = get_credit_provider_display_names(course_key) + providers_string = None + if providers: + if len(providers) > 1: + if len(providers) > 2: + # Translators: The join of three or more university names. The first of these formatting strings + # represents a comma-separated list of names (e.g., MIT, Harvard, Dartmouth). + providers_string = _("{first_providers}, and {last_provider}").format( + first_providers=u", ".join(providers[:-1]), + last_provider=providers[-1] + ) + else: + # Translators: The join of two university names (e.g., Harvard and MIT). + providers_string = _("{first_provider} and {second_provider}").format( + first_provider=providers[0], + second_provider=providers[1] + ) + else: + providers_string = providers[0] + context = { 'full_name': user.get_full_name(), 'platform_name': settings.PLATFORM_NAME, @@ -75,6 +96,7 @@ def send_credit_notifications(username, course_key): 'dashboard_link': dashboard_link, 'credit_course_link': credit_course_link, 'tracking_pixel': tracking_pixel, + 'providers': providers_string, } # create the root email message @@ -85,6 +107,10 @@ def send_credit_notifications(username, course_key): notification_msg.attach(msg_alternative) # render the credit notification templates subject = _(u'Course Credit Eligibility') + if providers: + subject = _(u'You are eligible for credit from {providers_string}').format( + providers_string=providers_string + ) # add alternative plain text message email_body_plain = render_to_string('credit_notifications/credit_eligibility_email.txt', context) @@ -180,3 +206,56 @@ def _email_url_parser(url_name, extra_param=None): dashboard_url_path = reverse(url_name) + extra_param if extra_param else reverse(url_name) dashboard_link_parts = ("https", site_name, dashboard_url_path, '', '', '') return urlparse.urlunparse(dashboard_link_parts) + + +def get_credit_provider_display_names(course_key): + """Get the course information from ecommerce and parse the data to get providers. + + Arguments: + course_key (CourseKey): The identifier for the course. + + Returns: + List of credit provider display names. + """ + course_id = unicode(course_key) + credit_config = CreditConfig.current() + + cache_key = None + provider_names = None + + if credit_config.is_cache_enabled: + cache_key = '{key_prefix}.{course_key}'.format( + key_prefix=credit_config.CACHE_KEY, course_key=course_id + ) + provider_names = cache.get(cache_key) + + if provider_names is not None: + return provider_names + + try: + user = User.objects.get(username=settings.ECOMMERCE_SERVICE_WORKER_USERNAME) + response = ecommerce_api_client(user).courses(course_id).get(include_products=1) + except Exception: # pylint: disable=broad-except + log.exception("Failed to receive data from the ecommerce course API for Course ID '%s'.", course_id) + return provider_names + + if not response: + log.info("No Course information found from ecommerce API for Course ID '%s'.", course_id) + return provider_names + + provider_ids = [] + for product in response.get('products'): + provider_ids += [ + attr.get('value') for attr in product.get('attribute_values') if attr.get('name') == 'credit_provider' + ] + + provider_names = [] + credit_providers = CreditProvider.get_credit_providers() + for provider in credit_providers: + if provider['id'] in provider_ids: + provider_names.append(provider['display_name']) + + if credit_config.is_cache_enabled: + cache.set(cache_key, provider_names, credit_config.cache_ttl) + + return provider_names diff --git a/openedx/core/djangoapps/credit/migrations/0002_creditconfig.py b/openedx/core/djangoapps/credit/migrations/0002_creditconfig.py new file mode 100644 index 0000000000..77b411edc9 --- /dev/null +++ b/openedx/core/djangoapps/credit/migrations/0002_creditconfig.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('credit', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='CreditConfig', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')), + ('enabled', models.BooleanField(default=False, verbose_name='Enabled')), + ('cache_ttl', models.PositiveIntegerField(default=0, help_text='Specified in seconds. Enable caching by setting this to a value greater than 0.', verbose_name='Cache Time To Live')), + ('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Changed by')), + ], + options={ + 'ordering': ('-change_date',), + 'abstract': False, + }, + ), + ] diff --git a/openedx/core/djangoapps/credit/models.py b/openedx/core/djangoapps/credit/models.py index 7cde41b58f..c657e7118b 100644 --- a/openedx/core/djangoapps/credit/models.py +++ b/openedx/core/djangoapps/credit/models.py @@ -6,23 +6,22 @@ Credit courses allow students to receive university credit for successful completion of a course on EdX """ -import datetime from collections import defaultdict +import datetime import logging -import pytz - +from config_models.models import ConfigurationModel from django.conf import settings from django.core.cache import cache -from django.dispatch import receiver -from django.db import models, transaction, IntegrityError from django.core.validators import RegexValidator -from simple_history.models import HistoricalRecords - +from django.db import models, transaction, IntegrityError +from django.dispatch import receiver +from django.utils.translation import ugettext_lazy, ugettext as _ from jsonfield.fields import JSONField from model_utils.models import TimeStampedModel +import pytz +from simple_history.models import HistoricalRecords from xmodule_django.models import CourseKeyField -from django.utils.translation import ugettext_lazy CREDIT_PROVIDER_ID_REGEX = r"[a-z,A-Z,0-9,\-]+" @@ -709,3 +708,25 @@ class CreditRequest(TimeStampedModel): provider=self.provider.provider_id, status=self.status, ) + + +class CreditConfig(ConfigurationModel): + """ Manage credit configuration """ + CACHE_KEY = 'credit.providers.api.data' + + cache_ttl = models.PositiveIntegerField( + verbose_name=_("Cache Time To Live"), + default=0, + help_text=_( + "Specified in seconds. Enable caching by setting this to a value greater than 0." + ) + ) + + @property + def is_cache_enabled(self): + """Whether responses from the commerce API will be cached.""" + return self.enabled and self.cache_ttl > 0 + + def __unicode__(self): + """Unicode representation of the config. """ + return 'Credit Configuration' diff --git a/openedx/core/djangoapps/credit/tests/test_api.py b/openedx/core/djangoapps/credit/tests/test_api.py index d58d151d5c..7616e7fbf6 100644 --- a/openedx/core/djangoapps/credit/tests/test_api.py +++ b/openedx/core/djangoapps/credit/tests/test_api.py @@ -2,21 +2,23 @@ Tests for the API functions in the credit app. """ import datetime +import json import unittest import ddt from django.conf import settings +from django.contrib.auth.models import User from django.core import mail from django.test.utils import override_settings -from django.db import connection, transaction +from django.db import connection from nose.plugins.attrib import attr -from opaque_keys.edx.keys import CourseKey +import httpretty +from lms.djangoapps.commerce.tests import TEST_API_SIGNING_KEY, TEST_API_URL +import mock import pytz -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase -from xmodule.modulestore.tests.factories import CourseFactory - -from util.date_utils import from_timestamp +from opaque_keys.edx.keys import CourseKey from openedx.core.djangoapps.credit import api +from openedx.core.djangoapps.credit.email_utils import get_credit_provider_display_names from openedx.core.djangoapps.credit.exceptions import ( InvalidCreditRequirements, InvalidCreditCourse, @@ -26,6 +28,7 @@ from openedx.core.djangoapps.credit.exceptions import ( CreditRequestNotFound, ) from openedx.core.djangoapps.credit.models import ( + CreditConfig, CreditCourse, CreditProvider, CreditRequirement, @@ -33,8 +36,13 @@ from openedx.core.djangoapps.credit.models import ( CreditEligibility ) from student.tests.factories import UserFactory +from util.date_utils import from_timestamp +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory + TEST_CREDIT_PROVIDER_SECRET_KEY = "931433d583c84ca7ba41784bad3232e6" +TEST_ECOMMERCE_WORKER = 'test_worker' @override_settings(CREDIT_PROVIDER_SECRET_KEYS={ @@ -64,6 +72,86 @@ class CreditApiTestBase(ModuleStoreTestCase): } THUMBNAIL_URL = "https://credit.example.com/logo.png" + PROVIDERS_LIST = [u'Hogwarts School of Witchcraft and Wizardry', u'Arizona State University'] + + COURSE_API_RESPONSE = { + "id": "course-v1:Demo+Demox+Course", + "url": "http://localhost/api/v2/courses/course-v1:Demo+Demox+Course/", + "name": "dummy edX Demonstration Course", + "verification_deadline": "2023-09-12T23:59:00Z", + "type": "credit", + "products_url": "http://localhost/api/v2/courses/course:Demo+Demox+Course/products/", + "last_edited": "2016-03-06T09:51:10Z", + "products": [ + { + "id": 1, + "url": "http://localhost/api/v2/products/11/", + "structure": "child", + "product_class": "Seat", + "title": "", + "price": 1, + "expires": '2016-03-06T09:51:10Z', + "attribute_values": [ + { + "name": "certificate_type", + "value": "credit" + }, + { + "name": "course_key", + "value": "edX/DemoX/Demo_Course", + }, + { + "name": "credit_hours", + "value": 1 + }, + { + "name": "credit_provider", + "value": "ASU" + }, + { + "name": "id_verification_required", + "value": False + } + ], + "is_available_to_buy": False, + "stockrecords": [] + }, + { + "id": 2, + "url": "http://localhost/api/v2/products/10/", + "structure": "child", + "product_class": "Seat", + "title": "", + "price": 1, + "expires": '2016-03-06T09:51:10Z', + "attribute_values": [ + { + "name": "certificate_type", + "value": "credit" + }, + { + "name": "course_key", + "value": "edX/DemoX/Demo_Course", + }, + { + "name": "credit_hours", + "value": 1 + }, + { + "name": "credit_provider", + "value": PROVIDER_ID + }, + { + "name": "id_verification_required", + "value": False + } + ], + "is_available_to_buy": False, + "stockrecords": [] + } + ] + } + def setUp(self, **kwargs): super(CreditApiTestBase, self).setUp() self.course_key = CourseKey.from_string("edX/DemoX/Demo_Course") @@ -86,6 +174,15 @@ class CreditApiTestBase(ModuleStoreTestCase): return credit_course + def _mock_ecommerce_courses_api(self, course_key, body, status=200): + """ Mock GET requests to the ecommerce course API endpoint. """ + httpretty.reset() + httpretty.register_uri( + httpretty.GET, '{}/courses/{}/?include_products=1'.format(TEST_API_URL, unicode(course_key)), + status=status, + body=json.dumps(body), content_type='application/json', + ) + @attr('shard_2') @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in LMS') @@ -394,10 +491,20 @@ class CreditRequirementApiTests(CreditApiTestBase): req_status = api.get_credit_requirement_status(self.course_key, "bob", namespace="grade", name="grade") self.assertEqual(len(req_status), 0) + @httpretty.activate + @override_settings( + ECOMMERCE_API_URL=TEST_API_URL, + ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY, + ECOMMERCE_SERVICE_WORKER_USERNAME=TEST_ECOMMERCE_WORKER + ) def test_satisfy_all_requirements(self): """ Test the credit requirements, eligibility notification, email content caching for a credit course. """ + self._mock_ecommerce_courses_api(self.course_key, self.COURSE_API_RESPONSE) + worker_user = User.objects.create_user(username=TEST_ECOMMERCE_WORKER) + self.assertFalse(hasattr(worker_user, 'profile')) + # Configure a course with two credit requirements self.add_credit_course() CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course') @@ -435,7 +542,7 @@ class CreditRequirementApiTests(CreditApiTestBase): self.assertFalse(api.is_user_eligible_for_credit("bob", self.course_key)) # Satisfy the other requirement - with self.assertNumQueries(15): + with self.assertNumQueries(19): api.set_credit_requirement_status( "bob", self.course_key, @@ -448,7 +555,10 @@ class CreditRequirementApiTests(CreditApiTestBase): # Credit eligibility email should be sent self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].subject, 'Course Credit Eligibility') + self.assertEqual( + mail.outbox[0].subject, + 'You are eligible for credit from Hogwarts School of Witchcraft and Wizardry' + ) # Now verify them email content email_payload_first = mail.outbox[0].attachments[0]._payload # pylint: disable=protected-access @@ -471,11 +581,22 @@ class CreditRequirementApiTests(CreditApiTestBase): image_id = email_image.get('Content-ID', '')[1:-1] self.assertIsNotNone(image_id) self.assertIn(image_id, html_content_first) + self.assertIn( + 'credit from Hogwarts School of Witchcraft and Wizardry for', + html_content_first + ) + + # test text email contents + text_content_first = email_payload_first[0]._payload[0]._payload + self.assertIn( + 'credit from Hogwarts School of Witchcraft and Wizardry for', + text_content_first + ) # Delete the eligibility entries and satisfy the user's eligibility # requirement again to trigger eligibility notification CreditEligibility.objects.all().delete() - with self.assertNumQueries(13): + with self.assertNumQueries(15): api.set_credit_requirement_status( "bob", self.course_key, @@ -551,6 +672,87 @@ class CreditRequirementApiTests(CreditApiTestBase): self.assertEqual(len(req_status), 1) self.assertEqual(req_status[0]["status"], None) + @ddt.data( + ( + [u'Arizona State University'], + 'credit from Arizona State University for', + 'You are eligible for credit from Arizona State University'), + ( + [u'Arizona State University', u'Hogwarts School of Witchcraft and Wizardry'], + 'credit from Arizona State University and Hogwarts School of Witchcraft and Wizardry for', + 'You are eligible for credit from Arizona State University and Hogwarts School of Witchcraft and Wizardry' + ), + ( + [u'Arizona State University', u'Hogwarts School of Witchcraft and Wizardry', u'Charter Oak'], + 'credit from Arizona State University, Hogwarts School of Witchcraft and Wizardry, and Charter Oak for', + 'You are eligible for credit from Arizona State University, Hogwarts School' + ' of Witchcraft and Wizardry, and Charter Oak' + ), + ([], 'credit for', 'Course Credit Eligibility'), + (None, 'credit for', 'Course Credit Eligibility') + ) + @ddt.unpack + def test_eligibility_email_with_providers(self, providers_list, providers_email_message, expected_subject): + """ Test the credit requirements, eligibility notification, email + for different providers combinations. + """ + # Configure a course with two credit requirements + self.add_credit_course() + CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course') + requirements = [ + { + "namespace": "grade", + "name": "grade", + "display_name": "Grade", + "criteria": { + "min_grade": 0.8 + }, + }, + { + "namespace": "reverification", + "name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid", + "display_name": "Assessment 1", + "criteria": {}, + } + ] + api.set_credit_requirements(self.course_key, requirements) + + user = UserFactory.create(username=self.USER_INFO['username'], password=self.USER_INFO['password']) + + # Satisfy one of the requirements, but not the other + api.set_credit_requirement_status( + user.username, + self.course_key, + requirements[0]["namespace"], + requirements[0]["name"] + ) + # Satisfy the other requirement. And mocked the api to return different kind of data. + with mock.patch('openedx.core.djangoapps.credit.email_utils.get_credit_provider_display_names') as mock_method: + mock_method.return_value = providers_list + api.set_credit_requirement_status( + "bob", + self.course_key, + requirements[1]["namespace"], + requirements[1]["name"] + ) + # Now the user should be eligible + self.assertTrue(api.is_user_eligible_for_credit("bob", self.course_key)) + + # Credit eligibility email should be sent + self.assertEqual(len(mail.outbox), 1) + + # Verify the email subject + self.assertEqual(mail.outbox[0].subject, expected_subject) + + # Now verify them email content + email_payload_first = mail.outbox[0].attachments[0]._payload # pylint: disable=protected-access + html_content_first = email_payload_first[0]._payload[1]._payload # pylint: disable=protected-access + self.assertIn(providers_email_message, html_content_first) + + # test text email + text_content_first = email_payload_first[0]._payload[0]._payload # pylint: disable=protected-access + self.assertIn(providers_email_message, text_content_first) + @attr('shard_2') @ddt.ddt @@ -879,3 +1081,126 @@ class CreditProviderIntegrationApiTests(CreditApiTestBase): """Check the user's credit status. """ statuses = api.get_credit_requests_for_user(self.USER_INFO["username"]) self.assertEqual(statuses[0]["status"], expected_status) + + +@attr('shard_2') +@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in LMS') +@override_settings( + ECOMMERCE_API_URL=TEST_API_URL, + ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY, + ECOMMERCE_SERVICE_WORKER_USERNAME=TEST_ECOMMERCE_WORKER +) +@ddt.ddt +class CourseApiTests(CreditApiTestBase): + """Test Python API for course product information.""" + def setUp(self): + super(CourseApiTests, self).setUp() + self.worker_user = User.objects.create_user(username=TEST_ECOMMERCE_WORKER) + self.add_credit_course(self.course_key) + self.credit_config = CreditConfig(cache_ttl=100, enabled=True) + self.credit_config.save() + + # Add another provider. + CreditProvider.objects.get_or_create( + provider_id='ASU', + display_name='Arizona State University', + provider_url=self.PROVIDER_URL, + provider_status_url=self.PROVIDER_STATUS_URL, + provider_description=self.PROVIDER_DESCRIPTION, + enable_integration=self.ENABLE_INTEGRATION, + fulfillment_instructions=self.FULFILLMENT_INSTRUCTIONS, + thumbnail_url=self.THUMBNAIL_URL + ) + self.assertFalse(hasattr(self.worker_user, 'profile')) + + @httpretty.activate + def test_get_credit_provider_display_names_method(self): + """Verify that parsed providers list is returns after getting course production information.""" + self._mock_ecommerce_courses_api(self.course_key, self.COURSE_API_RESPONSE) + response_providers = get_credit_provider_display_names(self.course_key) + self.assertListEqual(self.PROVIDERS_LIST, response_providers) + + @httpretty.activate + @mock.patch('edx_rest_api_client.client.EdxRestApiClient.__init__') + def test_get_credit_provider_display_names_method_with_exception(self, mock_init): + """Verify that in case of any exception it logs the error and return.""" + mock_init.side_effect = Exception + response = get_credit_provider_display_names(self.course_key) + self.assertTrue(mock_init.called) + self.assertEqual(response, None) + + @httpretty.activate + def test_get_credit_provider_display_names_caching(self): + """Verify that providers list is cached.""" + self.assertTrue(self.credit_config.is_cache_enabled) + self._mock_ecommerce_courses_api(self.course_key, self.COURSE_API_RESPONSE) + + # Warm up the cache. + response_providers = get_credit_provider_display_names(self.course_key) + self.assertListEqual(self.PROVIDERS_LIST, response_providers) + + # Hit the cache. + response_providers = get_credit_provider_display_names(self.course_key) + self.assertListEqual(self.PROVIDERS_LIST, response_providers) + + # Verify only one request was made. + self.assertEqual(len(httpretty.httpretty.latest_requests), 1) + + @httpretty.activate + def test_get_credit_provider_display_names_without_caching(self): + """Verify that providers list is not cached.""" + self.credit_config.cache_ttl = 0 + self.credit_config.save() + self.assertFalse(self.credit_config.is_cache_enabled) + + self._mock_ecommerce_courses_api(self.course_key, self.COURSE_API_RESPONSE) + + response_providers = get_credit_provider_display_names(self.course_key) + self.assertListEqual(self.PROVIDERS_LIST, response_providers) + + response_providers = get_credit_provider_display_names(self.course_key) + self.assertListEqual(self.PROVIDERS_LIST, response_providers) + + self.assertEqual(len(httpretty.httpretty.latest_requests), 2) + + @httpretty.activate + @ddt.data( + (None, None), + ({'products': []}, []), + ( + { + 'products': [{'expires': '', 'attribute_values': [{'name': 'credit_provider', 'value': 'ASU'}]}] + }, ['Arizona State University'] + ), + ( + { + 'products': [{'expires': '', 'attribute_values': [{'name': 'namespace', 'value': 'grade'}]}] + }, [] + ), + ( + { + 'products': [ + { + 'expires': '', 'attribute_values': + [ + {'name': 'credit_provider', 'value': 'ASU'}, + {'name': 'credit_provider', 'value': 'hogwarts'}, + {'name': 'course_type', 'value': 'credit'} + ] + } + ] + }, ['Hogwarts School of Witchcraft and Wizardry', 'Arizona State University'] + ) + ) + @ddt.unpack + def test_get_provider_api_with_multiple_data(self, data, expected_data): + self._mock_ecommerce_courses_api(self.course_key, data) + response_providers = get_credit_provider_display_names(self.course_key) + self.assertEqual(expected_data, response_providers) + + @httpretty.activate + def test_get_credit_provider_display_names_without_providers(self): + """Verify that if all providers are in-active than method return empty list.""" + self._mock_ecommerce_courses_api(self.course_key, self.COURSE_API_RESPONSE) + CreditProvider.objects.all().update(active=False) + self.assertEqual(get_credit_provider_display_names(self.course_key), [])