""" Test cases for tabs. """ from django.core.urlresolvers import reverse from django.http import Http404 from milestones.tests.utils import MilestonesTestCaseMixin from mock import MagicMock, Mock, patch from nose.plugins.attrib import attr from courseware.courses import get_course_by_id from courseware.tabs import ( CourseInfoTab, CoursewareTab, ExternalDiscussionCourseTab, ExternalLinkCourseTab, ProgressTab, get_course_tab_list ) from courseware.tests.factories import InstructorFactory, StaffFactory from courseware.tests.helpers import LoginEnrollmentTestCase from courseware.views.views import StaticCourseTabView, get_static_tab_fragment from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag from openedx.core.djangolib.testing.utils import get_mock_request from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG from student.models import CourseEnrollment from student.tests.factories import UserFactory from 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(TabTestCase, cls).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(TabTestCase, self).setUp() self.reverse = lambda name, args: "name/{0}/args/{1}".format(name, ",".join(str(a) for a in args)) def create_mock_user(self, is_authenticated=True, 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 user.is_authenticated = lambda: is_authenticated 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 = 'Book{0}'.format(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 self.assertEqual(tab.name, expected_name) # link is as expected self.assertEqual(tab.link_func(self.course, self.reverse), expected_link) # verify active page name self.assertEqual(tab.tab_id, expected_tab_id) # validate tab self.assertTrue(tab.validate(dict_tab)) if invalid_dict_tab: with self.assertRaises(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""" self.assertEquals(tab, dict_tab) # test __eq__ ne_dict_tab = dict_tab ne_dict_tab['type'] = 'fake_type' self.assertNotEquals(tab, ne_dict_tab) # test __ne__: incorrect type self.assertNotEquals(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) self.assertEquals(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_authenticated=True, is_staff=True, is_enrolled=True) self.assertEquals(expected_value, self.is_tab_enabled(tab, self.course, user)) if for_authenticated_users_only: user = self.create_mock_user(is_authenticated=True, is_staff=False, is_enrolled=False) self.assertEquals(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 = self.create_mock_user(is_authenticated=False, is_staff=False, is_enrolled=False) self.assertEquals(expected_value, self.is_tab_enabled(tab, self.course, user)) if for_enrolled_users_only: user = self.create_mock_user(is_authenticated=True, is_staff=False, is_enrolled=True) self.assertEquals(expected_value, self.is_tab_enabled(tab, self.course, user)) def check_get_and_set_methods(self, tab): """Test __getitem__ and __setitem__ calls""" self.assertEquals(tab['type'], tab.type) self.assertEquals(tab['tab_id'], tab.tab_id) with self.assertRaises(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 self.assertRaises(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 self.assertEquals(tab[key], new_value) tab[key] = old_value self.assertEquals(tab[key], old_value) class TextbooksTestCase(TabTestCase): """Test cases for Textbook Tab.""" def setUp(self): super(TextbooksTestCase, self).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_authenticated=True, 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=[self.course.id.to_deprecated_string(), book_index] ) self.assertEqual(tab.link_func(self.course, self.reverse), expected_link) self.assertTrue(tab.name.startswith('Book{0}'.format(book_index))) num_textbooks_found = num_textbooks_found + 1 self.assertEquals(num_textbooks_found, self.num_textbooks) @attr(shard=1) class StaticTabDateTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase): """Test cases for Static Tab Dates.""" MODULESTORE = TEST_DATA_MIXED_MODULESTORE @classmethod def setUpClass(cls): super(StaticTabDateTestCase, cls).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=[self.course.id.to_deprecated_string(), 'new_tab']) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) self.assertIn("OOGIE BLOOGIE", resp.content) def test_anonymous_user(self): url = reverse('static_tab', args=[self.course.id.to_deprecated_string(), 'new_tab']) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) self.assertIn("OOGIE BLOOGIE", resp.content) def test_invalid_course_key(self): self.setup_user() request = get_mock_request(self.user) with self.assertRaises(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) 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 self.assertIn(self.course.id.to_deprecated_string(), tab_content) self.assertIn('static_tab', tab_content) # Test when render raises an exception with patch('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 self.assertIn("this module is temporarily unavailable", static_tab_content) @attr(shard=1) 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(StaticTabDateTestCaseXML, self).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=[self.xml_course_key.to_deprecated_string(), self.xml_url]) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) self.assertIn(self.xml_data, resp.content) @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) def test_anonymous_user_xml(self): url = reverse('static_tab', args=[self.xml_course_key.to_deprecated_string(), self.xml_url]) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) self.assertIn(self.xml_data, resp.content) @attr(shard=1) @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(EntranceExamsTabsTestCase, self).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() 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(unicode(self.course.id)), 'description': 'Testing Courseware Tabs' } self.user.is_staff = False request = get_mock_request(self.user) self.course.entrance_exam_enabled = True self.course.entrance_exam_id = unicode(entrance_exam.location) milestone = add_milestone(milestone) add_course_milestone( unicode(self.course.id), self.relationship_types['REQUIRES'], milestone ) add_course_content_milestone( unicode(self.course.id), unicode(entrance_exam.location), self.relationship_types['FULFILLS'], milestone ) course_tab_list = get_course_tab_list(request, self.course) self.assertEqual(len(course_tab_list), 1) self.assertEqual(course_tab_list[0]['tab_id'], 'courseware') self.assertEqual(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': unicode(self.course.id)}) response = self.client.post(url, { 'unique_student_identifier': student.email, }) self.assertEqual(response.status_code, 200) # log in again as student self.client.logout() self.login(self.email, self.password) request = get_mock_request(self.user) course_tab_list = get_course_tab_list(request, self.course) self.assertEqual(len(course_tab_list), 5) 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') request = get_mock_request(staff_user) course_tab_list = get_course_tab_list(request, self.course) self.assertEqual(len(course_tab_list), 5) @attr(shard=1) class TextBookCourseViewsTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase): """ Validate tab behavior when dealing with textbooks. """ MODULESTORE = TEST_DATA_MIXED_MODULESTORE @classmethod def setUpClass(cls): super(TextBookCourseViewsTestCase, cls).setUpClass() cls.course = CourseFactory.create() def setUp(self): super(TextBookCourseViewsTestCase, self).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 = 'Book{0}'.format(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'} request = get_mock_request(self.user) course_tab_list = get_course_tab_list(request, 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=[self.course.id.to_deprecated_string(), book_index] ) tab_link = tab.link_func(self.course, reverse) self.assertEqual(tab_link, expected_link) num_of_textbooks_found += 1 self.assertEqual(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') self.assertFalse(tab.is_enabled(self.course, self.user)) class TabListTestCase(TabTestCase): """Base class for Test cases involving tab lists.""" def setUp(self): super(TabListTestCase, self).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': '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]) @attr(shard=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 self.assertRaises(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) self.assertEquals(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() self.assertEquals( len(tab_list.from_json([ {'type': CoursewareTab.type}, {'type': CourseInfoTab.type, 'name': 'fake_name'}, {'type': 'no_such_type'} ])), 2 ) @attr(shard=1) class CourseTabListTestCase(TabListTestCase): """Testing the generator method for iterating through displayable tabs""" 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) self.assertFalse(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) self.assertTrue(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) self.assertTrue(self.has_tab(self.course.tabs, 'external_discussion')) self.assertFalse(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) self.assertFalse(self.has_tab(self.course.tabs, 'external_discussion')) self.assertTrue(self.has_tab(self.course.tabs, 'discussion')) @patch.dict("django.conf.settings.FEATURES", { "ENABLE_TEXTBOOK": True, "ENABLE_DISCUSSION_SERVICE": True, "ENABLE_STUDENT_NOTES": 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) 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 self.assertTrue(getattr(self.course.tabs[i], 'is_collection', False)) else: # all other tabs must match the expected type self.assertEquals(tab.type, self.course.tabs[i].type) # test including non-empty collections self.assertIn( {'type': 'html_textbooks'}, list(xmodule_tabs.CourseTabList.iterate_displayable(self.course, inline_collections=False)), ) # test not including empty collections self.course.html_textbooks = [] self.assertNotIn( {'type': 'html_textbooks'}, 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 self.assertEquals(xmodule_tabs.CourseTabList.get_tab_by_type(self.course.tabs, tab.type), tab) # get tab by id self.assertEquals(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_authenticated=True, is_staff=False, is_enrolled=True) request = get_mock_request(user) course_tab_list = get_course_tab_list(request, self.course) name_list = [x.name for x in course_tab_list] self.assertIn('Static Tab Free', name_list) self.assertNotIn('Static Tab Instructors Only', 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') request = get_mock_request(staff_user) course_tab_list_staff = get_course_tab_list(request, self.course) name_list_staff = [x.name for x in course_tab_list_staff] self.assertIn('Static Tab Free', name_list_staff) self.assertIn('Static Tab Instructors Only', name_list_staff) @attr(shard=1) 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=[self.course.id.to_deprecated_string()]), expected_tab_id=ProgressTab.type, invalid_dict_tab=None, ) @patch('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 ) @attr(shard=1) 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=[self.course.id.to_deprecated_string(), 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') @attr(shard=1) class CourseInfoTabTestCase(TabTestCase): """Test cases for the course info tab.""" def setUp(self): self.user = self.create_mock_user() self.request = get_mock_request(self.user) @override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=False) def test_default_tab(self): # Verify that the course info tab is the first tab tabs = get_course_tab_list(self.request, self.course) self.assertEqual(tabs[0].type, 'course_info') @override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=True) 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.request, self.course) self.assertEqual(tabs[0].type, 'courseware') # TODO: LEARNER-611 - remove once course_info is removed. @override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=True) 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: self.assertEqual(tab.type, 'course_info') @attr(shard=1) class DiscussionLinkTestCase(TabTestCase): """Test cases for discussion link tab.""" def setUp(self): super(DiscussionLinkTestCase, self).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 == "discussion.views.forum_form_discussion" and args == [unicode(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_authenticated=True, is_staff=is_staff, is_enrolled=is_enrolled) with patch('student.models.CourseEnrollment.is_enrolled') as check_is_enrolled: check_is_enrolled.return_value = is_enrolled self.assertEquals( ( 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 )