Files
edx-platform/lms/djangoapps/learner_dashboard/tests/test_programs.py
Ahtisham Shahid 00b53287d5 Added anonymous user id and extra params in program lti (#29429)
* fix: added anonymous user id and extra params in program lti

* refactor: updated flag name

* fix: fixed linter issues
2021-12-02 17:05:35 +05:00

336 lines
13 KiB
Python

"""
Unit tests covering the program listing and detail pages.
"""
import json
import re
from unittest import mock
from urllib.parse import urljoin
from uuid import uuid4
from bs4 import BeautifulSoup
from django.conf import settings
from django.test import override_settings
from django.urls import reverse, reverse_lazy
from edx_toggles.toggles.testutils import override_waffle_flag
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory as ModuleStoreCourseFactory
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
from lms.djangoapps.learner_dashboard.config.waffle import ENABLE_PROGRAM_TAB_VIEW
from lms.djangoapps.program_enrollments.rest_api.v1.tests.test_views import ProgramCacheMixin
from lms.envs.test import CREDENTIALS_PUBLIC_SERVICE_URL
from openedx.core.djangoapps.catalog.constants import PathwayType
from openedx.core.djangoapps.catalog.tests.factories import (
CourseFactory,
CourseRunFactory,
PathwayFactory,
ProgramFactory
)
from openedx.core.djangoapps.catalog.tests.mixins import CatalogIntegrationMixin
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin
from openedx.core.djangolib.testing.utils import skip_unless_lms
PROGRAMS_UTILS_MODULE = 'openedx.core.djangoapps.programs.utils'
PROGRAMS_MODULE = 'lms.djangoapps.learner_dashboard.programs'
def load_serialized_data(response, key):
"""
Extract and deserialize serialized data from the response.
"""
pattern = re.compile(f'{key}: (?P<data>\\[.*\\])')
match = pattern.search(response.content.decode('utf-8'))
serialized = match.group('data')
return json.loads(serialized)
@skip_unless_lms
@override_settings(MKTG_URLS={'ROOT': 'https://www.example.com'})
@mock.patch(PROGRAMS_UTILS_MODULE + '.get_programs')
class TestProgramListing(ProgramsApiConfigMixin, SharedModuleStoreTestCase):
"""Unit tests for the program listing page."""
maxDiff = None
password = 'test'
url = reverse_lazy('program_listing_view')
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.course = ModuleStoreCourseFactory()
course_run = CourseRunFactory(key=str(cls.course.id)) # lint-amnesty, 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().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 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}
assert 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)
assert 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)
assert 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) # lint-amnesty, pylint: disable=no-member
response = self.client.get(self.url)
actual = 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_mobile_marketing_url(self, mock_get_programs):
"""
Verify that a link to a programs marketing for mobile appears in the response.
"""
self.create_programs_config(marketing_path='bar')
mock_get_programs.return_value = self.data
mobile_marketing_url = 'edxapp://course?programs'
response = self.client.get('/dashboard/programs_fragment/?mobile_only=true')
self.assertContains(response, mobile_marketing_url)
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) # lint-amnesty, pylint: disable=no-member
response = self.client.get(self.url)
actual = 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']})
assert actual_program['detail_url'] == expected_url
@skip_unless_lms
@mock.patch(PROGRAMS_MODULE + '.get_pathways')
@mock.patch(PROGRAMS_UTILS_MODULE + '.get_programs')
class TestProgramDetails(ProgramsApiConfigMixin, CatalogIntegrationMixin, SharedModuleStoreTestCase):
"""Unit tests for the program details page."""
program_uuid = str(uuid4())
password = 'test'
url = reverse_lazy('program_details_view', kwargs={'program_uuid': program_uuid})
@classmethod
def setUpClass(cls):
super().setUpClass()
modulestore_course = ModuleStoreCourseFactory()
course_run = CourseRunFactory(key=str(modulestore_course.id)) # lint-amnesty, pylint: disable=no-member
course = CourseFactory(course_runs=[course_run])
cls.program_data = ProgramFactory(uuid=cls.program_uuid, courses=[course])
cls.pathway_data = PathwayFactory()
cls.program_data['pathway_ids'] = [cls.pathway_data['id']]
cls.pathway_data['program_uuids'] = [cls.program_data['uuid']]
del cls.pathway_data['programs'] # lint-amnesty, pylint: disable=unsupported-delete-operation
def setUp(self):
super().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,
f'"program_record_url": "{CREDENTIALS_PUBLIC_SERVICE_URL}/records/programs/')
self.assertContains(response, 'program_listing_url')
self.assertContains(response, self.program_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')
assert any(soup.find_all('a', class_='tab-nav-link', href=reverse('program_listing_view')))
def assert_pathway_data_present(self, response):
""" Verify that the correct pathway data is present. """
self.assertContains(response, 'industryPathways')
self.assertContains(response, 'creditPathways')
industry_pathways = load_serialized_data(response, 'industryPathways')
credit_pathways = load_serialized_data(response, 'creditPathways')
if self.pathway_data['pathway_type'] == PathwayType.CREDIT.value:
credit_pathway, = credit_pathways # Verify that there is only one credit pathway
assert self.pathway_data == credit_pathway
assert [] == industry_pathways
elif self.pathway_data['pathway_type'] == PathwayType.INDUSTRY.value:
industry_pathway, = industry_pathways # Verify that there is only one industry pathway
assert self.pathway_data == industry_pathway
assert [] == credit_pathways
def test_login_required(self, mock_get_programs, mock_get_pathways):
"""
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_programs.return_value = self.program_data
mock_get_pathways.return_value = self.pathway_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)
with mock.patch('lms.djangoapps.learner_dashboard.programs.get_certificates') as certs:
certs.return_value = [{'type': 'program', 'url': '/'}]
response = self.client.get(self.url)
self.assert_program_data_present(response)
self.assert_pathway_data_present(response)
def test_404_if_disabled(self, _mock_get_programs, _mock_get_pathways):
"""
Verify that the page 404s if disabled.
"""
self.create_programs_config(enabled=False)
response = self.client.get(self.url)
assert response.status_code == 404
def test_404_if_no_data(self, mock_get_programs, _mock_get_pathways):
"""Verify that the page 404s if no program data is found."""
self.create_programs_config()
mock_get_programs.return_value = None
response = self.client.get(self.url)
assert response.status_code == 404
@override_waffle_flag(ENABLE_PROGRAM_TAB_VIEW, active=True)
class TestProgramDetailsFragmentView(SharedModuleStoreTestCase, ProgramCacheMixin, ProgramsApiConfigMixin):
"""Unit tests for the program details page."""
program_uuid = str(uuid4())
password = 'test'
url = reverse_lazy('program_details_fragment_view', kwargs={'program_uuid': program_uuid})
@classmethod
def setUpClass(cls):
super().setUpClass()
modulestore_course = ModuleStoreCourseFactory()
course_run = CourseRunFactory(key=str(modulestore_course.id))
course = CourseFactory(course_runs=[course_run])
cls.program = ProgramFactory(uuid=cls.program_uuid, courses=[course])
def setUp(self):
super().setUp()
self.user = UserFactory()
self.client.login(username=self.user.username, password=self.password)
self.set_program_in_catalog_cache(self.program_uuid, self.program)
self.create_programs_config()
def test_discussion_flags_exist(self):
"""
Test if programTabViewEnabled and discussionFragment exist in html.
"""
response = self.client.get(self.url)
self.assertContains(response, 'programTabViewEnabled: true',)
self.assertContains(response, 'discussionFragment: {"configured": false, "iframe": ""')