Files
edx-platform/lms/djangoapps/learner_dashboard/tests/test_programs.py
Mike Dikan ca64946b99 Remove deprecated references to ProgramsApiConfig model
ECOM-7195

The (now deprecated) programs service had several fields to set up configuration of the API.  We are removing the property/field references in codeas the first part of deprecating the model fields.  Also being removed are the model properties.
2017-03-02 18:07:23 -05:00

308 lines
11 KiB
Python

# -*- coding: utf-8 -*-
"""
Unit tests covering the program listing and detail pages.
"""
import json
import re
import unittest
from urlparse import urljoin
from uuid import uuid4
from bs4 import BeautifulSoup
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test import override_settings
import mock
from openedx.core.djangoapps.catalog.tests.factories import ProgramFactory, CourseFactory, CourseRunFactory
from openedx.core.djangoapps.catalog.tests.mixins import CatalogIntegrationMixin
from openedx.core.djangoapps.credentials.tests.factories import UserCredential, ProgramCredential
from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfigMixin
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin
from openedx.core.djangolib.testing.utils import skip_unless_lms
from student.tests.factories import UserFactory, CourseEnrollmentFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory as ModuleStoreCourseFactory
CATALOG_UTILS_MODULE = 'openedx.core.djangoapps.catalog.utils'
CREDENTIALS_UTILS_MODULE = 'openedx.core.djangoapps.credentials.utils'
PROGRAMS_UTILS_MODULE = 'openedx.core.djangoapps.programs.utils'
@skip_unless_lms
@override_settings(MKTG_URLS={'ROOT': 'https://www.example.com'})
@mock.patch(PROGRAMS_UTILS_MODULE + '.get_programs')
class TestProgramListing(ProgramsApiConfigMixin, CredentialsApiConfigMixin, SharedModuleStoreTestCase):
"""Unit tests for the program listing page."""
maxDiff = None
password = 'test'
url = reverse('program_listing_view')
@classmethod
def setUpClass(cls):
super(TestProgramListing, cls).setUpClass()
cls.course = ModuleStoreCourseFactory()
course_run = CourseRunFactory(key=unicode(cls.course.id)) # pylint: disable=no-member
course = CourseFactory(course_runs=[course_run])
cls.first_program = ProgramFactory(courses=[course])
cls.second_program = ProgramFactory(courses=[course])
cls.data = sorted([cls.first_program, cls.second_program], key=cls.program_sort_key)
def setUp(self):
super(TestProgramListing, self).setUp()
self.user = UserFactory()
self.client.login(username=self.user.username, password=self.password)
@classmethod
def program_sort_key(cls, program):
"""
Helper function used to sort dictionaries representing programs.
"""
return program['title']
def credential_sort_key(self, credential):
"""
Helper function used to sort dictionaries representing credentials.
"""
try:
return credential['certificate_url']
except KeyError:
return credential['credential_url']
def load_serialized_data(self, response, key):
"""
Extract and deserialize serialized data from the response.
"""
pattern = re.compile(r'{key}: (?P<data>\[.*\])'.format(key=key))
match = pattern.search(response.content)
serialized = match.group('data')
return json.loads(serialized)
def assert_dict_contains_subset(self, superset, subset):
"""
Verify that the dict superset contains the dict subset.
Works like assertDictContainsSubset, deprecated since Python 3.2.
See: https://docs.python.org/2.7/library/unittest.html#unittest.TestCase.assertDictContainsSubset.
"""
superset_keys = set(superset.keys())
subset_keys = set(subset.keys())
intersection = {key: superset[key] for key in superset_keys & subset_keys}
self.assertEqual(subset, intersection)
def test_login_required(self, mock_get_programs):
"""
Verify that login is required to access the page.
"""
self.create_programs_config()
mock_get_programs.return_value = self.data
self.client.logout()
response = self.client.get(self.url)
self.assertRedirects(
response,
'{}?next={}'.format(reverse('signin_user'), self.url)
)
self.client.login(username=self.user.username, password=self.password)
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
def test_404_if_disabled(self, _mock_get_programs):
"""
Verify that the page 404s if disabled.
"""
self.create_programs_config(enabled=False)
response = self.client.get(self.url)
self.assertEqual(response.status_code, 404)
def test_empty_state(self, mock_get_programs):
"""
Verify that the response contains no programs data when no programs are engaged.
"""
self.create_programs_config()
mock_get_programs.return_value = self.data
response = self.client.get(self.url)
self.assertContains(response, 'programsData: []')
def test_programs_listed(self, mock_get_programs):
"""
Verify that the response contains accurate programs data when programs are engaged.
"""
self.create_programs_config()
mock_get_programs.return_value = self.data
CourseEnrollmentFactory(user=self.user, course_id=self.course.id) # pylint: disable=no-member
response = self.client.get(self.url)
actual = self.load_serialized_data(response, 'programsData')
actual = sorted(actual, key=self.program_sort_key)
for index, actual_program in enumerate(actual):
expected_program = self.data[index]
self.assert_dict_contains_subset(actual_program, expected_program)
def test_program_discovery(self, mock_get_programs):
"""
Verify that a link to a programs marketing page appears in the response.
"""
self.create_programs_config(marketing_path='bar')
mock_get_programs.return_value = self.data
marketing_root = urljoin(settings.MKTG_URLS.get('ROOT'), 'bar').rstrip('/')
response = self.client.get(self.url)
self.assertContains(response, marketing_root)
def test_links_to_detail_pages(self, mock_get_programs):
"""
Verify that links to detail pages are present.
"""
self.create_programs_config()
mock_get_programs.return_value = self.data
CourseEnrollmentFactory(user=self.user, course_id=self.course.id) # pylint: disable=no-member
response = self.client.get(self.url)
actual = self.load_serialized_data(response, 'programsData')
actual = sorted(actual, key=self.program_sort_key)
for index, actual_program in enumerate(actual):
expected_program = self.data[index]
expected_url = reverse('program_details_view', kwargs={'program_uuid': expected_program['uuid']})
self.assertEqual(actual_program['detail_url'], expected_url)
@mock.patch(CREDENTIALS_UTILS_MODULE + '.get_user_credentials')
@mock.patch(CREDENTIALS_UTILS_MODULE + '.get_programs')
def test_certificates_listed(self, mock_get_programs, mock_get_user_credentials, __):
"""
Verify that the response contains accurate certificate data when certificates are available.
"""
self.create_programs_config()
self.create_credentials_config(is_learner_issuance_enabled=True)
mock_get_programs.return_value = self.data
first_credential = UserCredential(
username=self.user.username,
credential=ProgramCredential(
program_uuid=self.first_program['uuid']
)
)
second_credential = UserCredential(
username=self.user.username,
credential=ProgramCredential(
program_uuid=self.second_program['uuid']
)
)
credentials_data = sorted([first_credential, second_credential], key=self.credential_sort_key)
mock_get_user_credentials.return_value = credentials_data
response = self.client.get(self.url)
actual = self.load_serialized_data(response, 'certificatesData')
actual = sorted(actual, key=self.credential_sort_key)
self.assertEqual(len(actual), len(credentials_data))
for index, actual_credential in enumerate(actual):
expected_credential = credentials_data[index]
self.assertEqual(
# TODO: certificate_url is needlessly transformed to credential_url. (╯°□°)╯︵ ┻━┻
# Clean this up!
actual_credential['credential_url'],
expected_credential['certificate_url']
)
@skip_unless_lms
@mock.patch(CATALOG_UTILS_MODULE + '.get_edx_api_data')
class TestProgramDetails(ProgramsApiConfigMixin, CatalogIntegrationMixin, SharedModuleStoreTestCase):
"""Unit tests for the program details page."""
program_uuid = str(uuid4())
password = 'test'
url = reverse('program_details_view', kwargs={'program_uuid': program_uuid})
@classmethod
def setUpClass(cls):
super(TestProgramDetails, cls).setUpClass()
modulestore_course = ModuleStoreCourseFactory()
course_run = CourseRunFactory(key=unicode(modulestore_course.id)) # pylint: disable=no-member
course = CourseFactory(course_runs=[course_run])
cls.data = ProgramFactory(uuid=cls.program_uuid, courses=[course])
def setUp(self):
super(TestProgramDetails, self).setUp()
self.user = UserFactory()
self.client.login(username=self.user.username, password=self.password)
def assert_program_data_present(self, response):
"""Verify that program data is present."""
self.assertContains(response, 'programData')
self.assertContains(response, 'urls')
self.assertContains(response, 'program_listing_url')
self.assertContains(response, self.data['title'])
self.assert_programs_tab_present(response)
def assert_programs_tab_present(self, response):
"""Verify that the programs tab is present in the nav."""
soup = BeautifulSoup(response.content, 'html.parser')
self.assertTrue(
any(soup.find_all('a', class_='tab-nav-link', href=reverse('program_listing_view')))
)
def test_login_required(self, mock_get_edx_api_data):
"""
Verify that login is required to access the page.
"""
self.create_programs_config()
catalog_integration = self.create_catalog_integration()
UserFactory(username=catalog_integration.service_username)
mock_get_edx_api_data.return_value = self.data
self.client.logout()
response = self.client.get(self.url)
self.assertRedirects(
response,
'{}?next={}'.format(reverse('signin_user'), self.url)
)
self.client.login(username=self.user.username, password=self.password)
response = self.client.get(self.url)
self.assert_program_data_present(response)
def test_404_if_disabled(self, _mock_get_edx_api_data):
"""
Verify that the page 404s if disabled.
"""
self.create_programs_config(enabled=False)
response = self.client.get(self.url)
self.assertEqual(response.status_code, 404)
def test_404_if_no_data(self, _mock_get_edx_api_data):
"""Verify that the page 404s if no program data is found."""
self.create_programs_config()
response = self.client.get(self.url)
self.assertEqual(response.status_code, 404)