Files
edx-platform/lms/djangoapps/learner_dashboard/tests/test_programs.py

355 lines
14 KiB
Python

"""
Unit tests covering the program listing and detail pages.
"""
import datetime
import json
import unittest
from urlparse import urljoin
from bs4 import BeautifulSoup
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test import override_settings, TestCase
from edx_oauth2_provider.tests.factories import ClientFactory
import httpretty
from opaque_keys.edx import locator
from provider.constants import CONFIDENTIAL
from openedx.core.djangoapps.credentials.models import CredentialsApiConfig
from openedx.core.djangoapps.credentials.tests import factories as credentials_factories
from openedx.core.djangoapps.credentials.tests.mixins import CredentialsDataMixin, CredentialsApiConfigMixin
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.djangoapps.programs.tests import factories
from openedx.core.djangoapps.programs.tests.mixins import (
ProgramsApiConfigMixin,
ProgramsDataMixin)
from student.models import CourseEnrollment
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
@override_settings(MKTG_URLS={'ROOT': 'http://edx.org'})
class TestProgramListing(
ModuleStoreTestCase,
ProgramsApiConfigMixin,
ProgramsDataMixin,
CredentialsDataMixin,
CredentialsApiConfigMixin):
"""
Unit tests for getting the list of programs enrolled by a logged in user
"""
PASSWORD = 'test'
url = reverse('program_listing_view')
def setUp(self):
"""
Add a student
"""
super(TestProgramListing, self).setUp()
ClientFactory(name=CredentialsApiConfig.OAUTH2_CLIENT_NAME, client_type=CONFIDENTIAL)
ClientFactory(name=ProgramsApiConfig.OAUTH2_CLIENT_NAME, client_type=CONFIDENTIAL)
self.student = UserFactory()
def _create_course_and_enroll(self, student, org, course, run):
"""
Creates a course and associated enrollment.
"""
course_location = locator.CourseLocator(org, course, run)
course = CourseFactory.create(
org=course_location.org,
number=course_location.course,
run=course_location.run
)
enrollment = CourseEnrollment.enroll(student, course.id)
enrollment.created = datetime.datetime(2000, 12, 31, 0, 0, 0, 0)
enrollment.save()
def _get_program_url(self, marketing_slug):
"""
Helper function to get the program card url
"""
return urljoin(
settings.MKTG_URLS.get('ROOT'),
'xseries' + '/{}'
).format(marketing_slug)
def _setup_and_get_program(self):
"""
The core function to setup the mock program api,
then call the django test client to get the actual program listing page
make sure the request suceeds and make sure x_series_url is on the page
"""
self.mock_programs_api()
self.client.login(username=self.student.username, password=self.PASSWORD)
response = self.client.get(self.url)
x_series_url = urljoin(settings.MKTG_URLS.get('ROOT'), 'xseries')
self.assertContains(response, x_series_url)
return response
def _get_program_checklist(self, program_id):
"""
The convenience function to get all the program related page element we would like to check against
"""
return [
self.PROGRAM_NAMES[program_id],
self._get_program_url(self.PROGRAMS_API_RESPONSE['results'][program_id]['marketing_slug']),
self.PROGRAMS_API_RESPONSE['results'][program_id]['organizations'][0]['display_name'],
]
def _assert_progress_data_present(self, response):
"""Verify that progress data is present."""
self.assertContains(response, 'userProgress')
@httpretty.activate
def test_get_program_with_no_enrollment(self):
self.create_programs_config()
response = self._setup_and_get_program()
for program_element in self._get_program_checklist(0):
self.assertNotContains(response, program_element)
for program_element in self._get_program_checklist(1):
self.assertNotContains(response, program_element)
@httpretty.activate
def test_get_one_program(self):
self.create_programs_config()
self._create_course_and_enroll(self.student, *self.COURSE_KEYS[0].split('/'))
response = self._setup_and_get_program()
for program_element in self._get_program_checklist(0):
self.assertContains(response, program_element)
for program_element in self._get_program_checklist(1):
self.assertNotContains(response, program_element)
self._assert_progress_data_present(response)
@httpretty.activate
def test_get_both_program(self):
self.create_programs_config()
self._create_course_and_enroll(self.student, *self.COURSE_KEYS[0].split('/'))
self._create_course_and_enroll(self.student, *self.COURSE_KEYS[5].split('/'))
response = self._setup_and_get_program()
for program_element in self._get_program_checklist(0):
self.assertContains(response, program_element)
for program_element in self._get_program_checklist(1):
self.assertContains(response, program_element)
self._assert_progress_data_present(response)
def test_get_programs_dashboard_not_enabled(self):
self.create_programs_config(program_listing_enabled=False)
self.client.login(username=self.student.username, password=self.PASSWORD)
response = self.client.get(self.url)
self.assertEqual(response.status_code, 404)
def test_xseries_advertise_disabled(self):
self.create_programs_config(xseries_ad_enabled=False)
self.client.login(username=self.student.username, password=self.PASSWORD)
response = self.client.get(self.url)
x_series_url = urljoin(settings.MKTG_URLS.get('ROOT'), 'xseries')
self.assertNotContains(response, x_series_url)
def test_get_programs_not_logged_in(self):
self.create_programs_config()
response = self.client.get(self.url)
self.assertRedirects(
response,
'{}?next={}'.format(reverse('signin_user'), self.url)
)
def _expected_progam_credentials_data(self):
"""
Dry method for getting expected program credentials response data.
"""
return [
credentials_factories.UserCredential(
id=1,
username='test',
credential=credentials_factories.ProgramCredential()
),
credentials_factories.UserCredential(
id=2,
username='test',
credential=credentials_factories.ProgramCredential()
)
]
def _expected_credentials_data(self):
""" Dry method for getting expected credentials."""
program_credentials_data = self._expected_progam_credentials_data()
return [
{
'display_name': self.PROGRAMS_API_RESPONSE['results'][0]['name'],
'subtitle': self.PROGRAMS_API_RESPONSE['results'][0]['subtitle'],
'credential_url':program_credentials_data[0]['certificate_url']
},
{
'display_name': self.PROGRAMS_API_RESPONSE['results'][1]['name'],
'subtitle':self.PROGRAMS_API_RESPONSE['results'][1]['subtitle'],
'credential_url':program_credentials_data[1]['certificate_url']
}
]
@httpretty.activate
def test_get_xseries_certificates_with_data(self):
self.create_programs_config()
self.create_credentials_config(is_learner_issuance_enabled=True)
self.client.login(username=self.student.username, password=self.PASSWORD)
# mock programs and credentials apis
self.mock_programs_api()
self.mock_credentials_api(self.student, data=self.CREDENTIALS_API_RESPONSE, reset_url=False)
response = self.client.get(reverse("program_listing_view"))
for certificate in self._expected_credentials_data():
self.assertContains(response, certificate['display_name'])
self.assertContains(response, certificate['credential_url'])
self.assertContains(response, 'images/xseries-certificate-visual.png')
@httpretty.activate
def test_get_xseries_certificates_without_data(self):
self.create_programs_config()
self.create_credentials_config(is_learner_issuance_enabled=True)
self.client.login(username=self.student.username, password=self.PASSWORD)
# mock programs and credentials apis
self.mock_programs_api()
self.mock_credentials_api(self.student, data={"results": []}, reset_url=False)
response = self.client.get(reverse("program_listing_view"))
for certificate in self._expected_credentials_data():
self.assertNotContains(response, certificate['display_name'])
self.assertNotContains(response, certificate['credential_url'])
@httpretty.activate
@override_settings(MKTG_URLS={'ROOT': 'http://edx.org'})
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class TestProgramDetails(ProgramsApiConfigMixin, SharedModuleStoreTestCase):
"""
Unit tests for the program details page
"""
program_id = 123
password = 'test'
@classmethod
def setUpClass(cls):
super(TestProgramDetails, cls).setUpClass()
cls.course = CourseFactory()
def setUp(self):
super(TestProgramDetails, self).setUp()
self.details_page = reverse('program_details_view', args=[self.program_id])
self.user = UserFactory()
self.client.login(username=self.user.username, password=self.password)
ClientFactory(name=ProgramsApiConfig.OAUTH2_CLIENT_NAME, client_type=CONFIDENTIAL)
self.organization = factories.Organization()
self.run_mode = factories.RunMode(course_key=unicode(self.course.id)) # pylint: disable=no-member
self.course_code = factories.CourseCode(run_modes=[self.run_mode])
self.data = factories.Program(
organizations=[self.organization],
course_codes=[self.course_code]
)
def _mock_programs_api(self, data, status=200):
"""Helper for mocking out Programs API URLs."""
self.assertTrue(httpretty.is_enabled(), msg='httpretty must be enabled to mock Programs API calls.')
url = '{api_root}/programs/{id}/'.format(
api_root=ProgramsApiConfig.current().internal_api_url.strip('/'),
id=self.program_id
)
body = json.dumps(data)
httpretty.register_uri(
httpretty.GET,
url,
body=body,
status=status,
content_type='application/json',
)
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['name'])
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):
"""
Verify that login is required to access the page.
"""
self.create_programs_config()
self._mock_programs_api(self.data)
self.client.logout()
response = self.client.get(self.details_page)
self.assertRedirects(
response,
'{}?next={}'.format(reverse('signin_user'), self.details_page)
)
self.client.login(username=self.user.username, password=self.password)
response = self.client.get(self.details_page)
self._assert_program_data_present(response)
def test_404_if_disabled(self):
"""
Verify that the page 404s if disabled.
"""
self.create_programs_config(program_details_enabled=False)
response = self.client.get(self.details_page)
self.assertEquals(response.status_code, 404)
def test_page_routing(self):
"""Verify that the page can be hit with or without a program name in the URL."""
self.create_programs_config()
self._mock_programs_api(self.data)
response = self.client.get(self.details_page)
self._assert_program_data_present(response)
response = self.client.get(self.details_page + 'program_name/')
self._assert_program_data_present(response)
response = self.client.get(self.details_page + 'program_name/invalid/')
self.assertEquals(response.status_code, 404)
def test_404_if_no_data(self):
"""Verify that the page 404s if no program data is found."""
self.create_programs_config()
self._mock_programs_api(self.data, status=404)
response = self.client.get(self.details_page)
self.assertEquals(response.status_code, 404)
httpretty.reset()
self._mock_programs_api({})
response = self.client.get(self.details_page)
self.assertEquals(response.status_code, 404)