Files
edx-platform/lms/djangoapps/courseware/tests/test_tabs.py

937 lines
37 KiB
Python

"""
Test cases for tabs.
"""
from unittest.mock import MagicMock, Mock, patch
import pytest
from crum import set_current_request
from django.contrib.auth.models import AnonymousUser
from django.http import Http404
from django.urls import reverse
from milestones.tests.utils import MilestonesTestCaseMixin
from edx_toggles.toggles.testutils import override_waffle_flag
from lms.djangoapps.courseware.courses import get_course_by_id
from lms.djangoapps.courseware.tabs import (
CourseInfoTab,
CoursewareTab,
DatesTab,
ExternalDiscussionCourseTab,
ExternalLinkCourseTab,
ProgressTab,
get_course_tab_list
)
from lms.djangoapps.courseware.tests.factories import InstructorFactory, StaffFactory
from lms.djangoapps.courseware.tests.helpers import LoginEnrollmentTestCase
from lms.djangoapps.courseware.views.views import StaticCourseTabView, get_static_tab_fragment
from openedx.core.djangolib.testing.utils import get_mock_request
from openedx.features.course_experience import DISABLE_UNIFIED_COURSE_TAB_FLAG
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.tests.factories import UserFactory
from common.djangoapps.util.milestones_helpers import (
add_course_content_milestone,
add_course_milestone,
add_milestone,
get_milestone_relationship_types
)
from xmodule import tabs as xmodule_tabs
from xmodule.modulestore.tests.django_utils import (
TEST_DATA_MIXED_MODULESTORE,
ModuleStoreTestCase,
SharedModuleStoreTestCase
)
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.tests.utils import TEST_DATA_DIR
from xmodule.modulestore.xml_importer import import_course_from_xml
class TabTestCase(SharedModuleStoreTestCase):
"""Base class for Tab-related test cases."""
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.course = CourseFactory.create(org='edX', course='toy', run='2012_Fall')
cls.fake_dict_tab = {'fake_key': 'fake_value'}
cls.books = None
def setUp(self):
super().setUp()
self.reverse = lambda name, args: "name/{}/args/{}".format(name, ",".join(str(a) for a in args))
def create_mock_user(self, is_staff=True, is_enrolled=True):
"""
Creates a mock user with the specified properties.
"""
user = UserFactory(is_staff=is_staff)
user.is_enrolled = is_enrolled
return user
def is_tab_enabled(self, tab, course, user):
"""
Returns true if the specified tab is enabled.
"""
return tab.is_enabled(course, user=user)
def set_up_books(self, num_books):
"""Initializes the textbooks in the course and adds the given number of books to each textbook"""
self.books = [MagicMock() for _ in range(num_books)]
for book_index, book in enumerate(self.books):
book.title = f'Book{book_index}'
self.course.textbooks = self.books
self.course.pdf_textbooks = self.books
self.course.html_textbooks = self.books
def check_tab(
self,
tab_class,
dict_tab,
expected_link,
expected_tab_id,
expected_name='same',
invalid_dict_tab=None,
):
"""
Helper method to verify a tab class.
'tab_class' is the class of the tab that is being tested
'dict_tab' is the raw dictionary value of the tab
'expected_link' is the expected value for the hyperlink of the tab
'expected_tab_id' is the expected value for the unique id of the tab
'expected_name' is the expected value for the name of the tab
'invalid_dict_tab' is an invalid dictionary value for the tab.
Can be 'None' if the given tab class does not have any keys to validate.
"""
# create tab
tab = tab_class(tab_dict=dict_tab)
# name is as expected
assert tab.name == expected_name
# link is as expected
assert tab.link_func(self.course, self.reverse) == expected_link
# verify active page name
assert tab.tab_id == expected_tab_id
# validate tab
assert tab.validate(dict_tab)
if invalid_dict_tab:
with pytest.raises(xmodule_tabs.InvalidTabsException):
tab.validate(invalid_dict_tab)
# check get and set methods
self.check_get_and_set_methods(tab)
# check to_json and from_json methods
self.check_tab_json_methods(tab)
# check equality methods
self.check_tab_equality(tab, dict_tab)
# return tab for any additional tests
return tab
def check_tab_equality(self, tab, dict_tab):
"""Tests the equality methods on the given tab"""
assert tab == dict_tab
# test __eq__
ne_dict_tab = dict_tab
ne_dict_tab['type'] = 'fake_type'
assert tab != ne_dict_tab
# test __ne__: incorrect type
assert tab != {'fake_key': 'fake_value'}
# test __ne__: missing type
def check_tab_json_methods(self, tab):
"""Tests the json from and to methods on the given tab"""
serialized_tab = tab.to_json()
deserialized_tab = tab.from_json(serialized_tab)
assert serialized_tab == deserialized_tab
def check_can_display_results(
self,
tab,
expected_value=True,
for_authenticated_users_only=False,
for_staff_only=False,
for_enrolled_users_only=False
):
"""Checks can display results for various users"""
if for_staff_only:
user = self.create_mock_user(is_staff=True, is_enrolled=True)
assert expected_value == self.is_tab_enabled(tab, self.course, user)
if for_authenticated_users_only:
user = self.create_mock_user(is_staff=False, is_enrolled=False)
assert expected_value == self.is_tab_enabled(tab, self.course, user)
if not for_staff_only and not for_authenticated_users_only and not for_enrolled_users_only:
user = AnonymousUser()
assert expected_value == self.is_tab_enabled(tab, self.course, user)
if for_enrolled_users_only:
user = self.create_mock_user(is_staff=False, is_enrolled=True)
assert expected_value == self.is_tab_enabled(tab, self.course, user)
def check_get_and_set_methods(self, tab):
"""Test __getitem__ and __setitem__ calls"""
assert tab['type'] == tab.type
assert tab['tab_id'] == tab.tab_id
with pytest.raises(KeyError):
_ = tab['invalid_key']
self.check_get_and_set_method_for_key(tab, 'name')
self.check_get_and_set_method_for_key(tab, 'tab_id')
with pytest.raises(KeyError):
tab['invalid_key'] = 'New Value'
def check_get_and_set_method_for_key(self, tab, key):
"""Test __getitem__ and __setitem__ for the given key"""
old_value = tab[key]
new_value = 'New Value'
tab[key] = new_value
assert tab[key] == new_value
tab[key] = old_value
assert tab[key] == old_value
class TextbooksTestCase(TabTestCase):
"""Test cases for Textbook Tab."""
def setUp(self):
super().setUp()
self.set_up_books(2)
self.dict_tab = MagicMock()
self.course.tabs = [
xmodule_tabs.CourseTab.load('textbooks'),
xmodule_tabs.CourseTab.load('pdf_textbooks'),
xmodule_tabs.CourseTab.load('html_textbooks'),
]
self.num_textbook_tabs = sum(1 for tab in self.course.tabs if tab.type in [
'textbooks', 'pdf_textbooks', 'html_textbooks'
])
self.num_textbooks = self.num_textbook_tabs * len(self.books)
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_TEXTBOOK": True})
def test_textbooks_enabled(self):
type_to_reverse_name = {'textbook': 'book', 'pdftextbook': 'pdf_book', 'htmltextbook': 'html_book'}
num_textbooks_found = 0
user = self.create_mock_user(is_staff=False, is_enrolled=True)
for tab in xmodule_tabs.CourseTabList.iterate_displayable(self.course, user=user):
# verify all textbook type tabs
if tab.type == 'single_textbook':
book_type, book_index = tab.tab_id.split("/", 1)
expected_link = self.reverse(
type_to_reverse_name[book_type],
args=[str(self.course.id), book_index]
)
assert tab.link_func(self.course, self.reverse) == expected_link
assert tab.name.startswith(f'Book{book_index}')
num_textbooks_found = num_textbooks_found + 1
assert num_textbooks_found == self.num_textbooks
class StaticTabDateTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
"""Test cases for Static Tab Dates."""
MODULESTORE = TEST_DATA_MIXED_MODULESTORE
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.course = CourseFactory.create()
cls.page = ItemFactory.create(
category="static_tab", parent_location=cls.course.location,
data="OOGIE BLOOGIE", display_name="new_tab"
)
cls.course.tabs.append(xmodule_tabs.CourseTab.load('static_tab', name='New Tab', url_slug='new_tab'))
cls.course.save()
def test_logged_in(self):
self.setup_user()
url = reverse('static_tab', args=[str(self.course.id), 'new_tab'])
resp = self.client.get(url)
self.assertContains(resp, "OOGIE BLOOGIE")
def test_anonymous_user(self):
url = reverse('static_tab', args=[str(self.course.id), 'new_tab'])
resp = self.client.get(url)
self.assertContains(resp, "OOGIE BLOOGIE")
def test_invalid_course_key(self):
self.setup_user()
self.addCleanup(set_current_request, None)
request = get_mock_request(self.user)
with pytest.raises(Http404):
StaticCourseTabView().get(request, course_id='edX/toy', tab_slug='new_tab')
def test_get_static_tab_fragment(self):
self.setup_user()
course = get_course_by_id(self.course.id)
self.addCleanup(set_current_request, None)
request = get_mock_request(self.user)
tab = xmodule_tabs.CourseTabList.get_tab_by_slug(course.tabs, 'new_tab')
# Test render works okay
tab_content = get_static_tab_fragment(request, course, tab).content
assert str(self.course.id) in tab_content
assert 'static_tab' in tab_content
# Test when render raises an exception
with patch('lms.djangoapps.courseware.views.views.get_module') as mock_module_render:
mock_module_render.return_value = MagicMock(
render=Mock(side_effect=Exception('Render failed!'))
)
static_tab_content = get_static_tab_fragment(request, course, tab).content
assert 'this module is temporarily unavailable' in static_tab_content
class StaticTabDateTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase):
"""
Tests for the static tab dates of an XML course
"""
MODULESTORE = TEST_DATA_MIXED_MODULESTORE
def setUp(self):
"""
Set up the tests
"""
super().setUp()
# The following XML test course (which lives at common/test/data/2014)
# is closed; we're testing that tabs still appear when
# the course is already closed
self.xml_course_key = self.store.make_course_key('edX', 'detached_pages', '2014')
import_course_from_xml(
self.store,
'test_user',
TEST_DATA_DIR,
source_dirs=['2014'],
static_content_store=None,
target_id=self.xml_course_key,
raise_on_failure=True,
create_if_not_present=True,
)
# this text appears in the test course's tab
# common/test/data/2014/tabs/8e4cce2b4aaf4ba28b1220804619e41f.html
self.xml_data = "static 463139"
self.xml_url = "8e4cce2b4aaf4ba28b1220804619e41f"
@patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
def test_logged_in_xml(self):
self.setup_user()
url = reverse('static_tab', args=[str(self.xml_course_key), self.xml_url])
resp = self.client.get(url)
self.assertContains(resp, self.xml_data)
@patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
def test_anonymous_user_xml(self):
url = reverse('static_tab', args=[str(self.xml_course_key), self.xml_url])
resp = self.client.get(url)
self.assertContains(resp, self.xml_data)
@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True})
class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTestCaseMixin):
"""
Validate tab behavior when dealing with Entrance Exams
"""
MODULESTORE = TEST_DATA_MIXED_MODULESTORE
@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True})
def setUp(self):
"""
Test case scaffolding
"""
super().setUp()
self.course = CourseFactory.create()
self.instructor_tab = ItemFactory.create(
category="instructor", parent_location=self.course.location,
data="Instructor Tab", display_name="Instructor"
)
self.extra_tab_2 = ItemFactory.create(
category="static_tab", parent_location=self.course.location,
data="Extra Tab", display_name="Extra Tab 2"
)
self.extra_tab_3 = ItemFactory.create(
category="static_tab", parent_location=self.course.location,
data="Extra Tab", display_name="Extra Tab 3"
)
self.setup_user()
self.enroll(self.course)
self.user.is_staff = True
self.relationship_types = get_milestone_relationship_types()
self.addCleanup(set_current_request, None)
def test_get_course_tabs_list_entrance_exam_enabled(self):
"""
Unit Test: test_get_course_tabs_list_entrance_exam_enabled
"""
entrance_exam = ItemFactory.create(
category="chapter",
parent_location=self.course.location,
data="Exam Data",
display_name="Entrance Exam",
is_entrance_exam=True
)
milestone = {
'name': 'Test Milestone',
'namespace': '{}.entrance_exams'.format(str(self.course.id)),
'description': 'Testing Courseware Tabs'
}
self.user.is_staff = False
self.course.entrance_exam_enabled = True
self.course.entrance_exam_id = str(entrance_exam.location)
milestone = add_milestone(milestone)
add_course_milestone(
str(self.course.id),
self.relationship_types['REQUIRES'],
milestone
)
add_course_content_milestone(
str(self.course.id),
str(entrance_exam.location),
self.relationship_types['FULFILLS'],
milestone
)
course_tab_list = get_course_tab_list(self.user, self.course)
assert len(course_tab_list) == 1
assert course_tab_list[0]['tab_id'] == 'courseware'
assert course_tab_list[0]['name'] == 'Entrance Exam'
def test_get_course_tabs_list_skipped_entrance_exam(self):
"""
Tests tab list is not limited if user is allowed to skip entrance exam.
"""
#create a user
student = UserFactory()
# login as instructor hit skip entrance exam api in instructor app
instructor = InstructorFactory(course_key=self.course.id)
self.client.logout()
self.client.login(username=instructor.username, password='test')
url = reverse('mark_student_can_skip_entrance_exam', kwargs={'course_id': str(self.course.id)})
response = self.client.post(url, {
'unique_student_identifier': student.email,
})
assert response.status_code == 200
# log in again as student
self.client.logout()
self.login(self.email, self.password)
course_tab_list = get_course_tab_list(self.user, self.course)
assert len(course_tab_list) == 4
def test_course_tabs_list_for_staff_members(self):
"""
Tests tab list is not limited if user is member of staff
and has not passed entrance exam.
"""
# Login as member of staff
self.client.logout()
staff_user = StaffFactory(course_key=self.course.id)
self.client.login(username=staff_user.username, password='test')
course_tab_list = get_course_tab_list(staff_user, self.course)
assert len(course_tab_list) == 4
class TextBookCourseViewsTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
"""
Validate tab behavior when dealing with textbooks.
"""
MODULESTORE = TEST_DATA_MIXED_MODULESTORE
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.course = CourseFactory.create()
def setUp(self):
super().setUp()
self.set_up_books(2)
self.setup_user()
self.enroll(self.course)
self.num_textbook_tabs = sum(1 for tab in self.course.tabs if tab.type in [
'textbooks', 'pdf_textbooks', 'html_textbooks'
])
self.num_textbooks = self.num_textbook_tabs * len(self.books)
def set_up_books(self, num_books):
"""Initializes the textbooks in the course and adds the given number of books to each textbook"""
self.books = [MagicMock() for _ in range(num_books)]
for book_index, book in enumerate(self.books):
book.title = f'Book{book_index}'
self.course.textbooks = self.books
self.course.pdf_textbooks = self.books
self.course.html_textbooks = self.books
def test_pdf_textbook_tabs(self):
"""
Test that all textbooks tab links generating correctly.
"""
type_to_reverse_name = {'textbook': 'book', 'pdftextbook': 'pdf_book', 'htmltextbook': 'html_book'}
self.addCleanup(set_current_request, None)
course_tab_list = get_course_tab_list(self.user, self.course)
num_of_textbooks_found = 0
for tab in course_tab_list:
# Verify links of all textbook type tabs.
if tab.type == 'single_textbook':
book_type, book_index = tab.tab_id.split("/", 1)
expected_link = reverse(
type_to_reverse_name[book_type],
args=[str(self.course.id), book_index]
)
tab_link = tab.link_func(self.course, reverse)
assert tab_link == expected_link
num_of_textbooks_found += 1
assert num_of_textbooks_found == self.num_textbooks
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_TEXTBOOK": False})
def test_textbooks_disabled(self):
tab = xmodule_tabs.CourseTab.load('textbooks')
assert not tab.is_enabled(self.course, self.user)
class TabListTestCase(TabTestCase):
"""Base class for Test cases involving tab lists."""
def setUp(self):
super().setUp()
# invalid tabs
self.invalid_tabs = [
# less than 2 tabs
[{'type': CoursewareTab.type}],
# missing course_info
[{'type': CoursewareTab.type}, {'type': 'discussion', 'name': 'fake_name'}],
[{'type': 'unknown_type'}],
# incorrect order
[{'type': 'discussion', 'name': 'fake_name'},
{'type': CourseInfoTab.type, 'name': 'fake_name'}, {'type': CoursewareTab.type}],
]
# tab types that should appear only once
unique_tab_types = [
CoursewareTab.type,
CourseInfoTab.type,
'textbooks',
'pdf_textbooks',
'html_textbooks',
]
for unique_tab_type in unique_tab_types:
self.invalid_tabs.append([
{'type': CoursewareTab.type},
{'type': CourseInfoTab.type, 'name': 'fake_name'},
# add the unique tab multiple times
{'type': unique_tab_type},
{'type': unique_tab_type},
])
# valid tabs
self.valid_tabs = [
# any empty list is valid because a default list of tabs will be
# generated to replace the empty list.
[],
# all valid tabs
[
{'type': CoursewareTab.type},
{'type': CourseInfoTab.type, 'name': 'fake_name'},
{'type': DatesTab.type}, # Add this even though we filter it out, for testing purposes
{'type': 'discussion', 'name': 'fake_name'},
{'type': ExternalLinkCourseTab.type, 'name': 'fake_name', 'link': 'fake_link'},
{'type': ExternalLinkCourseTab.type, 'name': 'fake_name', 'link': 'fake_link'},
{'type': 'textbooks'},
{'type': 'pdf_textbooks'},
{'type': 'html_textbooks'},
{'type': ProgressTab.type, 'name': 'fake_name'},
{'type': xmodule_tabs.StaticTab.type, 'name': 'fake_name', 'url_slug': 'schlug'},
{'type': 'syllabus'},
],
# with external discussion
[
{'type': CoursewareTab.type},
{'type': CourseInfoTab.type, 'name': 'fake_name'},
{'type': ExternalDiscussionCourseTab.type, 'name': 'fake_name', 'link': 'fake_link'}
],
]
self.all_valid_tab_list = xmodule_tabs.CourseTabList().from_json(self.valid_tabs[1])
class ValidateTabsTestCase(TabListTestCase):
"""Test cases for validating tabs."""
def test_validate_tabs(self):
tab_list = xmodule_tabs.CourseTabList()
for invalid_tab_list in self.invalid_tabs:
with pytest.raises(xmodule_tabs.InvalidTabsException):
tab_list.from_json(invalid_tab_list)
for valid_tab_list in self.valid_tabs:
from_json_result = tab_list.from_json(valid_tab_list)
assert len(from_json_result) == len(valid_tab_list)
def test_invalid_tab_type(self):
"""
Verifies that having an unrecognized tab type does not cause
the tabs to be undisplayable.
"""
tab_list = xmodule_tabs.CourseTabList()
assert len(tab_list.from_json([{'type': CoursewareTab.type},
{'type': CourseInfoTab.type, 'name': 'fake_name'},
{'type': 'no_such_type'}])) == 2
class CourseTabListTestCase(TabListTestCase):
"""Testing the generator method for iterating through displayable tabs"""
def setUp(self):
super().setUp()
self.addCleanup(set_current_request, None)
def has_tab(self, tab_list, tab_type):
""" Searches the given lab_list for a given tab_type. """
for tab in tab_list:
if tab.type == tab_type:
return True
return False
def test_initialize_default_without_syllabus(self):
self.course.tabs = []
self.course.syllabus_present = False
xmodule_tabs.CourseTabList.initialize_default(self.course)
assert not self.has_tab(self.course.tabs, 'syllabus')
def test_initialize_default_with_syllabus(self):
self.course.tabs = []
self.course.syllabus_present = True
xmodule_tabs.CourseTabList.initialize_default(self.course)
assert self.has_tab(self.course.tabs, 'syllabus')
def test_initialize_default_with_external_link(self):
self.course.tabs = []
self.course.discussion_link = "other_discussion_link"
xmodule_tabs.CourseTabList.initialize_default(self.course)
assert self.has_tab(self.course.tabs, 'external_discussion')
assert not self.has_tab(self.course.tabs, 'discussion')
def test_initialize_default_without_external_link(self):
self.course.tabs = []
self.course.discussion_link = ""
xmodule_tabs.CourseTabList.initialize_default(self.course)
assert not self.has_tab(self.course.tabs, 'external_discussion')
assert self.has_tab(self.course.tabs, 'discussion')
@patch.dict("django.conf.settings.FEATURES", {
"ENABLE_TEXTBOOK": True,
"ENABLE_DISCUSSION_SERVICE": True,
"ENABLE_EDXNOTES": True,
})
def test_iterate_displayable(self):
self.course.hide_progress_tab = False
# create 1 book per textbook type
self.set_up_books(1)
# initialize the course tabs to a list of all valid tabs
self.course.tabs = self.all_valid_tab_list
# enumerate the tabs with no user
expected = [tab.type for tab in
xmodule_tabs.CourseTabList.iterate_displayable(self.course, inline_collections=False)]
actual = [tab.type for tab in self.course.tabs if tab.is_enabled(self.course, user=None)]
assert actual == expected
# enumerate the tabs with a staff user
user = UserFactory(is_staff=True)
CourseEnrollment.enroll(user, self.course.id)
# Need to mock this flag as we care that orders match, and a tab not enabled will result in a failure
with patch('lms.djangoapps.courseware.tabs.RELATIVE_DATES_FLAG') as mock_flag:
mock_flag.is_enabled().return_value = True
for i, tab in enumerate(xmodule_tabs.CourseTabList.iterate_displayable(self.course, user=user)):
if getattr(tab, 'is_collection_item', False):
# a collection item was found as a result of a collection tab
assert getattr(self.course.tabs[i], 'is_collection', False)
else:
# all other tabs must match the expected type
assert tab.type == self.course.tabs[i].type
# test including non-empty collections
assert {'type': 'html_textbooks'} in\
list(xmodule_tabs.CourseTabList.iterate_displayable(self.course, inline_collections=False))
# test not including empty collections
self.course.html_textbooks = []
assert {'type': 'html_textbooks'} not in\
list(xmodule_tabs.CourseTabList.iterate_displayable(self.course, inline_collections=False))
def test_get_tab_by_methods(self):
"""Tests the get_tab methods in CourseTabList"""
self.course.tabs = self.all_valid_tab_list
for tab in self.course.tabs:
# get tab by type
assert xmodule_tabs.CourseTabList.get_tab_by_type(self.course.tabs, tab.type) == tab
# get tab by id
assert xmodule_tabs.CourseTabList.get_tab_by_id(self.course.tabs, tab.tab_id) == tab
def test_course_tabs_staff_only(self):
"""
Tests the static tabs that available only for instructor
"""
self.course.tabs.append(xmodule_tabs.CourseTab.load('static_tab', name='Static Tab Free',
url_slug='extra_tab_1',
course_staff_only=False))
self.course.tabs.append(xmodule_tabs.CourseTab.load('static_tab', name='Static Tab Instructors Only',
url_slug='extra_tab_2',
course_staff_only=True))
self.course.save()
user = self.create_mock_user(is_staff=False, is_enrolled=True)
self.addCleanup(set_current_request, None)
course_tab_list = get_course_tab_list(user, self.course)
name_list = [x.name for x in course_tab_list]
assert 'Static Tab Free' in name_list
assert 'Static Tab Instructors Only' not in name_list
# Login as member of staff
self.client.logout()
staff_user = StaffFactory(course_key=self.course.id)
self.client.login(username=staff_user.username, password='test')
course_tab_list_staff = get_course_tab_list(staff_user, self.course)
name_list_staff = [x.name for x in course_tab_list_staff]
assert 'Static Tab Free' in name_list_staff
assert 'Static Tab Instructors Only' in name_list_staff
class ProgressTestCase(TabTestCase):
"""Test cases for Progress Tab."""
def check_progress_tab(self):
"""Helper function for verifying the progress tab."""
return self.check_tab(
tab_class=ProgressTab,
dict_tab={'type': ProgressTab.type, 'name': 'same'},
expected_link=self.reverse('progress', args=[str(self.course.id)]),
expected_tab_id=ProgressTab.type,
invalid_dict_tab=None,
)
@patch('common.djangoapps.student.models.CourseEnrollment.is_enrolled')
def test_progress(self, is_enrolled):
is_enrolled.return_value = True
self.course.hide_progress_tab = False
tab = self.check_progress_tab()
self.check_can_display_results(
tab, for_staff_only=True, for_enrolled_users_only=True
)
self.course.hide_progress_tab = True
self.check_progress_tab()
self.check_can_display_results(
tab, for_staff_only=True, for_enrolled_users_only=True, expected_value=False
)
class StaticTabTestCase(TabTestCase):
"""Test cases for Static Tab."""
def test_static_tab(self):
url_slug = 'schmug'
tab = self.check_tab(
tab_class=xmodule_tabs.StaticTab,
dict_tab={'type': xmodule_tabs.StaticTab.type, 'name': 'same', 'url_slug': url_slug},
expected_link=self.reverse('static_tab', args=[str(self.course.id), url_slug]),
expected_tab_id='static_tab_schmug',
invalid_dict_tab=self.fake_dict_tab,
)
self.check_can_display_results(tab)
self.check_get_and_set_method_for_key(tab, 'url_slug')
class CourseInfoTabTestCase(TabTestCase):
"""Test cases for the course info tab."""
def setUp(self): # lint-amnesty, pylint: disable=super-method-not-called
self.user = self.create_mock_user()
self.addCleanup(set_current_request, None)
@override_waffle_flag(DISABLE_UNIFIED_COURSE_TAB_FLAG, active=True)
def test_default_tab(self):
# Verify that the course info tab is the first tab
tabs = get_course_tab_list(self.user, self.course)
# So I know this means course_info is not the first tab, but it is going to be
# retired soon (https://openedx.atlassian.net/browse/TNL-7061) and also it has
# a lower priority than courseware so seems odd that it would ever be first.
# As such, I feel comfortable updating this test so it passes until it is removed
# as part of the linked ticket
assert tabs[1].type == 'course_info'
@override_waffle_flag(DISABLE_UNIFIED_COURSE_TAB_FLAG, active=False)
def test_default_tab_for_new_course_experience(self):
# Verify that the unified course experience hides the course info tab
tabs = get_course_tab_list(self.user, self.course)
assert tabs[0].type == 'courseware'
# TODO: LEARNER-611 - remove once course_info is removed.
@override_waffle_flag(DISABLE_UNIFIED_COURSE_TAB_FLAG, active=False)
def test_default_tab_for_displayable(self):
tabs = xmodule_tabs.CourseTabList.iterate_displayable(self.course, self.user)
for i, tab in enumerate(tabs):
if i == 0:
assert tab.type == 'course_info'
class DiscussionLinkTestCase(TabTestCase):
"""Test cases for discussion link tab."""
def setUp(self):
super().setUp()
self.tabs_with_discussion = [
xmodule_tabs.CourseTab.load('discussion'),
]
self.tabs_without_discussion = [
]
@staticmethod
def _reverse(course):
"""Custom reverse function"""
def reverse_discussion_link(viewname, args):
"""reverse lookup for discussion link"""
if viewname == "forum_form_discussion" and args == [str(course.id)]:
return "default_discussion_link"
return reverse_discussion_link
def check_discussion(
self, tab_list,
expected_discussion_link,
expected_can_display_value,
discussion_link_in_course="",
is_staff=True,
is_enrolled=True,
):
"""Helper function to verify whether the discussion tab exists and can be displayed"""
self.course.tabs = tab_list
self.course.discussion_link = discussion_link_in_course
discussion_tab = xmodule_tabs.CourseTabList.get_discussion(self.course)
user = self.create_mock_user(is_staff=is_staff, is_enrolled=is_enrolled)
with patch('common.djangoapps.student.models.CourseEnrollment.is_enrolled') as check_is_enrolled:
check_is_enrolled.return_value = is_enrolled
assert ((discussion_tab is not None) and self.is_tab_enabled(discussion_tab, self.course, user) and
(discussion_tab.link_func(self.course, self._reverse(self.course))
== expected_discussion_link)) == expected_can_display_value
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": False})
def test_explicit_discussion_link(self):
"""Test that setting discussion_link overrides everything else"""
self.check_discussion(
tab_list=self.tabs_with_discussion,
discussion_link_in_course="other_discussion_link",
expected_discussion_link="other_discussion_link",
expected_can_display_value=True,
)
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": False})
def test_discussions_disabled(self):
"""Test that other cases return None with discussions disabled"""
for tab_list in [[], self.tabs_with_discussion, self.tabs_without_discussion]:
self.check_discussion(
tab_list=tab_list,
expected_discussion_link=not None,
expected_can_display_value=False,
)
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def test_tabs_with_discussion(self):
"""Test a course with a discussion tab configured"""
self.check_discussion(
tab_list=self.tabs_with_discussion,
expected_discussion_link="default_discussion_link",
expected_can_display_value=True,
)
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def test_tabs_without_discussion(self):
"""Test a course with tabs configured but without a discussion tab"""
self.check_discussion(
tab_list=self.tabs_without_discussion,
expected_discussion_link=not None,
expected_can_display_value=False,
)
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def test_tabs_enrolled_or_staff(self):
for is_enrolled, is_staff in [(True, False), (False, True)]:
self.check_discussion(
tab_list=self.tabs_with_discussion,
expected_discussion_link="default_discussion_link",
expected_can_display_value=True,
is_enrolled=is_enrolled,
is_staff=is_staff
)
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def test_tabs_not_enrolled_or_staff(self):
is_enrolled = is_staff = False
self.check_discussion(
tab_list=self.tabs_with_discussion,
expected_discussion_link="default_discussion_link",
expected_can_display_value=False,
is_enrolled=is_enrolled,
is_staff=is_staff
)
class DatesTabTestCase(TabListTestCase):
"""Test cases for dates tab"""
@patch('lms.djangoapps.courseware.tabs.RELATIVE_DATES_FLAG')
@patch('common.djangoapps.student.models.CourseEnrollment.is_enrolled')
def test_dates_tab_disabled_if_unenrolled(self, is_enrolled, mock_flag):
mock_flag.is_enabled().return_value = True
tab = DatesTab({'type': DatesTab.type, 'name': 'dates'})
is_enrolled.return_value = False
unenrolled_user = self.create_mock_user(is_staff=False, is_enrolled=False)
assert not self.is_tab_enabled(tab, self.course, unenrolled_user)
staff_user = self.create_mock_user(is_staff=True, is_enrolled=False)
assert self.is_tab_enabled(tab, self.course, staff_user)
is_enrolled.return_value = True
enrolled_user = self.create_mock_user(is_staff=False, is_enrolled=True)
assert self.is_tab_enabled(tab, self.course, enrolled_user)
@patch('lms.djangoapps.courseware.tabs.RELATIVE_DATES_FLAG')
def test_singular_dates_tab(self, mock_flag):
"""Test cases for making sure no persisted dates tab is surfaced"""
mock_flag.is_enabled().return_value = True
user = self.create_mock_user()
self.course.tabs = self.all_valid_tab_list
self.course.save()
# Verify that there is a dates tab in the modulestore
has_dates_tab = False
for tab in self.course.tabs:
if tab.type == 'dates':
has_dates_tab = True
assert has_dates_tab
# Verify that there is only 1 'dates' tab in the returned result from get_course_tab_list()
tabs = get_course_tab_list(user, self.course)
num_dates_tabs = 0
for tab in tabs:
if tab.type == 'dates':
num_dates_tabs += 1
assert num_dates_tabs == 1