""" test views """ import datetime import json import re import pytz import ddt import urlparse from dateutil.tz import tzutc from mock import patch, MagicMock from nose.plugins.attrib import attr from capa.tests.response_xml_factory import StringResponseXMLFactory from courseware.courses import get_course_by_id from courseware.tests.factories import StudentModuleFactory from courseware.tests.helpers import LoginEnrollmentTestCase from courseware.tabs import get_course_tab_list from courseware.testutils import FieldOverrideTestMixin from django_comment_client.utils import has_forum_access from django_comment_common.models import FORUM_ROLE_ADMINISTRATOR from django_comment_common.utils import are_permissions_roles_seeded from lms.djangoapps.instructor.access import ( allow_access, list_with_level, ) from django.conf import settings from django.core.urlresolvers import reverse, resolve from django.utils.translation import ugettext as _ from django.utils.timezone import UTC from django.test.utils import override_settings from django.test import RequestFactory from edxmako.shortcuts import render_to_response from request_cache.middleware import RequestCache from opaque_keys.edx.keys import CourseKey from student.roles import ( CourseCcxCoachRole, CourseInstructorRole, CourseStaffRole, ) from student.models import ( CourseEnrollment, CourseEnrollmentAllowed, ) from student.tests.factories import ( AdminFactory, CourseEnrollmentFactory, UserFactory, ) from xmodule.x_module import XModuleMixin from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import ( ModuleStoreTestCase, SharedModuleStoreTestCase, TEST_DATA_SPLIT_MODULESTORE) from xmodule.modulestore.tests.factories import ( CourseFactory, ItemFactory, SampleCourseFactory, ) from ccx_keys.locator import CCXLocator from lms.djangoapps.ccx.models import CustomCourseForEdX from lms.djangoapps.ccx.overrides import get_override_for_ccx, override_field_for_ccx from lms.djangoapps.ccx.tests.factories import CcxFactory from lms.djangoapps.ccx.tests.utils import ( CcxTestCase, flatten, ) from lms.djangoapps.ccx.utils import ( ccx_course, is_email, ) from lms.djangoapps.ccx.views import get_date from xmodule.modulestore.django import modulestore def intercept_renderer(path, context): """ Intercept calls to `render_to_response` and attach the context dict to the response for examination in unit tests. """ # I think Django already does this for you in their TestClient, except # we're bypassing that by using edxmako. Probably edxmako should be # integrated better with Django's rendering and event system. response = render_to_response(path, context) response.mako_context = context response.mako_template = path return response def ccx_dummy_request(): """ Returns dummy request object for CCX coach tab test """ factory = RequestFactory() request = factory.get('ccx_coach_dashboard') request.user = MagicMock() return request def setup_students_and_grades(context): """ Create students and set their grades. :param context: class reference """ if context.course: context.student = student = UserFactory.create() CourseEnrollmentFactory.create(user=student, course_id=context.course.id) context.student2 = student2 = UserFactory.create() CourseEnrollmentFactory.create(user=student2, course_id=context.course.id) # create grades for self.student as if they'd submitted the ccx for chapter in context.course.get_children(): for i, section in enumerate(chapter.get_children()): for j, problem in enumerate(section.get_children()): # if not problem.visible_to_staff_only: StudentModuleFactory.create( grade=1 if i < j else 0, max_grade=1, student=context.student, course_id=context.course.id, module_state_key=problem.location ) StudentModuleFactory.create( grade=1 if i > j else 0, max_grade=1, student=context.student2, course_id=context.course.id, module_state_key=problem.location ) def unhide(unit): """ Recursively unhide a unit and all of its children in the CCX schedule. """ unit['hidden'] = False for child in unit.get('children', ()): unhide(child) class TestAdminAccessCoachDashboard(CcxTestCase, LoginEnrollmentTestCase): """ Tests for Custom Courses views. """ MODULESTORE = TEST_DATA_SPLIT_MODULESTORE def setUp(self): super(TestAdminAccessCoachDashboard, self).setUp() self.make_coach() ccx = self.make_ccx() ccx_key = CCXLocator.from_course_locator(self.course.id, ccx.id) self.url = reverse('ccx_coach_dashboard', kwargs={'course_id': ccx_key}) def test_staff_access_coach_dashboard(self): """ User is staff, should access coach dashboard. """ staff = self.make_staff() self.client.login(username=staff.username, password="test") response = self.client.get(self.url) self.assertEqual(response.status_code, 200) def test_instructor_access_coach_dashboard(self): """ User is instructor, should access coach dashboard. """ instructor = self.make_instructor() self.client.login(username=instructor.username, password="test") # Now access URL response = self.client.get(self.url) self.assertEqual(response.status_code, 200) def test_forbidden_user_access_coach_dashboard(self): """ Assert user with no access must not see dashboard. """ user = UserFactory.create(password="test") self.client.login(username=user.username, password="test") response = self.client.get(self.url) self.assertEqual(response.status_code, 403) @attr(shard=1) @override_settings( XBLOCK_FIELD_DATA_WRAPPERS=['lms.djangoapps.courseware.field_overrides:OverrideModulestoreFieldData.wrap'], MODULESTORE_FIELD_OVERRIDE_PROVIDERS=['ccx.overrides.CustomCoursesForEdxOverrideProvider'], ) class TestCCXProgressChanges(CcxTestCase, LoginEnrollmentTestCase): """ Tests ccx schedule changes in progress page """ @classmethod def setUpClass(cls): """ Set up tests """ super(TestCCXProgressChanges, cls).setUpClass() start = datetime.datetime(2016, 7, 1, 0, 0, tzinfo=tzutc()) due = datetime.datetime(2016, 7, 8, 0, 0, tzinfo=tzutc()) cls.course = course = CourseFactory.create(enable_ccx=True, start=start) chapter = ItemFactory.create(start=start, parent=course, category=u'chapter') sequential = ItemFactory.create( parent=chapter, start=start, due=due, category=u'sequential', metadata={'graded': True, 'format': 'Homework'} ) vertical = ItemFactory.create( parent=sequential, start=start, due=due, category=u'vertical', metadata={'graded': True, 'format': 'Homework'} ) # Trying to wrap the whole thing in a bulk operation fails because it # doesn't find the parents. But we can at least wrap this part... with cls.store.bulk_operations(course.id, emit_signals=False): flatten([ItemFactory.create( parent=vertical, start=start, due=due, category="problem", data=StringResponseXMLFactory().build_xml(answer='foo'), metadata={'rerandomize': 'always'} )] for _ in xrange(2)) def assert_progress_summary(self, ccx_course_key, due): """ assert signal and schedule update. """ student = UserFactory.create(is_staff=False, password="test") CourseEnrollment.enroll(student, ccx_course_key) self.assertTrue( CourseEnrollment.objects.filter(course_id=ccx_course_key, user=student).exists() ) # login as student self.client.login(username=student.username, password="test") progress_page_response = self.client.get( reverse('progress', kwargs={'course_id': ccx_course_key}) ) grade_summary = progress_page_response.mako_context['courseware_summary'] # pylint: disable=no-member chapter = grade_summary[0] section = chapter['sections'][0] progress_page_due_date = section.due.strftime("%Y-%m-%d %H:%M") self.assertEqual(progress_page_due_date, due) @patch('ccx.views.render_to_response', intercept_renderer) @patch('courseware.views.views.render_to_response', intercept_renderer) @patch.dict('django.conf.settings.FEATURES', {'CUSTOM_COURSES_EDX': True}) def test_edit_schedule(self): """ Get CCX schedule, modify it, save it. """ self.make_coach() ccx = self.make_ccx() ccx_course_key = CCXLocator.from_course_locator(self.course.id, unicode(ccx.id)) self.client.login(username=self.coach.username, password="test") url = reverse('ccx_coach_dashboard', kwargs={'course_id': ccx_course_key}) response = self.client.get(url) schedule = json.loads(response.mako_context['schedule']) # pylint: disable=no-member self.assertEqual(len(schedule), 1) unhide(schedule[0]) # edit schedule date = datetime.datetime.now() - datetime.timedelta(days=5) start = date.strftime("%Y-%m-%d %H:%M") due = (date + datetime.timedelta(days=3)).strftime("%Y-%m-%d %H:%M") schedule[0]['start'] = start schedule[0]['children'][0]['start'] = start schedule[0]['children'][0]['due'] = due schedule[0]['children'][0]['children'][0]['start'] = start schedule[0]['children'][0]['children'][0]['due'] = due url = reverse('save_ccx', kwargs={'course_id': ccx_course_key}) response = self.client.post(url, json.dumps(schedule), content_type='application/json') self.assertEqual(response.status_code, 200) schedule = json.loads(response.content)['schedule'] self.assertEqual(schedule[0]['hidden'], False) self.assertEqual(schedule[0]['start'], start) self.assertEqual(schedule[0]['children'][0]['start'], start) self.assertEqual(schedule[0]['children'][0]['due'], due) self.assertEqual(schedule[0]['children'][0]['children'][0]['due'], due) self.assertEqual(schedule[0]['children'][0]['children'][0]['start'], start) self.assert_progress_summary(ccx_course_key, due) @attr(shard=1) @ddt.ddt class TestCoachDashboard(CcxTestCase, LoginEnrollmentTestCase): """ Tests for Custom Courses views. """ @classmethod def setUpClass(cls): super(TestCoachDashboard, cls).setUpClass() cls.course_disable_ccx = CourseFactory.create(enable_ccx=False) cls.course_with_ccx_connect_set = CourseFactory.create(enable_ccx=True, ccx_connector="http://ccx.com") def setUp(self): """ Set up tests """ super(TestCoachDashboard, self).setUp() # Login with the instructor account self.client.login(username=self.coach.username, password="test") # adding staff to master course. staff = UserFactory() allow_access(self.course, staff, 'staff') self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = UserFactory() allow_access(self.course, instructor, 'instructor') self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) def test_not_a_coach(self): """ User is not a coach, should get Forbidden response. """ self.make_coach() ccx = self.make_ccx() # create session of non-coach user user = UserFactory.create(password="test") self.client.login(username=user.username, password="test") url = reverse( 'ccx_coach_dashboard', kwargs={'course_id': CCXLocator.from_course_locator(self.course.id, ccx.id)}) response = self.client.get(url) self.assertEqual(response.status_code, 403) def test_no_ccx_created(self): """ No CCX is created, coach should see form to add a CCX. """ self.make_coach() url = reverse( 'ccx_coach_dashboard', kwargs={'course_id': unicode(self.course.id)}) response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertTrue(re.search( '