""" test views """ import datetime import json import re from unittest.mock import MagicMock, patch import ddt import six from ccx_keys.locator import CCXLocator from django.conf import settings from django.test import RequestFactory from django.test.utils import override_settings from django.urls import resolve, reverse from django.utils.translation import ugettext as _ from edx_django_utils.cache import RequestCache from opaque_keys.edx.keys import CourseKey from pytz import UTC from capa.tests.response_xml_factory import StringResponseXMLFactory from common.djangoapps.edxmako.shortcuts import render_to_response from common.djangoapps.student.models import CourseEnrollment, CourseEnrollmentAllowed from common.djangoapps.student.roles import CourseCcxCoachRole, CourseInstructorRole, CourseStaffRole from common.djangoapps.student.tests.factories import AdminFactory, CourseEnrollmentFactory, UserFactory 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 lms.djangoapps.courseware.courses import get_course_by_id from lms.djangoapps.courseware.tabs import get_course_tab_list from lms.djangoapps.courseware.tests.factories import StudentModuleFactory from lms.djangoapps.courseware.tests.helpers import LoginEnrollmentTestCase from lms.djangoapps.courseware.testutils import FieldOverrideTestMixin from lms.djangoapps.discussion.django_comment_client.utils import has_forum_access from lms.djangoapps.grades.api import task_compute_all_grades_for_course from lms.djangoapps.instructor.access import allow_access, list_with_level from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.django_comment_common.models import FORUM_ROLE_ADMINISTRATOR from openedx.core.djangoapps.django_comment_common.utils import are_permissions_roles_seeded from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import ( TEST_DATA_SPLIT_MODULESTORE, ModuleStoreTestCase, SharedModuleStoreTestCase ) from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, SampleCourseFactory from xmodule.x_module import XModuleMixin 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(username='u\u0131\u028c\u0279\u0250\u026f') 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 ) task_compute_all_grades_for_course.apply_async(kwargs={'course_key': str(context.course.id)}) 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().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) assert 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) assert 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) assert response.status_code == 403 @override_settings( XBLOCK_FIELD_DATA_WRAPPERS=['lms.djangoapps.courseware.field_overrides:OverrideModulestoreFieldData.wrap'], MODULESTORE_FIELD_OVERRIDE_PROVIDERS=['lms.djangoapps.ccx.overrides.CustomCoursesForEdxOverrideProvider'], ) class TestCCXProgressChanges(CcxTestCase, LoginEnrollmentTestCase): """ Tests ccx schedule changes in progress page """ @classmethod def setUpClass(cls): """ Set up tests """ super().setUpClass() start = datetime.datetime(2016, 7, 1, 0, 0, tzinfo=UTC) due = datetime.datetime(2016, 7, 8, 0, 0, tzinfo=UTC) cls.course = course = CourseFactory.create(enable_ccx=True, start=start) chapter = ItemFactory.create(start=start, parent=course, category='chapter') sequential = ItemFactory.create( parent=chapter, start=start, due=due, category='sequential', metadata={'graded': True, 'format': 'Homework'} ) vertical = ItemFactory.create( parent=sequential, start=start, due=due, category='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 range(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) assert 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'] chapter = grade_summary[0] section = chapter['sections'][0] progress_page_due_date = section.due.strftime("%Y-%m-%d %H:%M") assert progress_page_due_date == due @patch('lms.djangoapps.ccx.views.render_to_response', intercept_renderer) @patch('lms.djangoapps.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, str(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']) assert 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') assert response.status_code == 200 schedule = json.loads(response.content.decode('utf-8'))['schedule'] assert schedule[0]['hidden'] is False assert schedule[0]['start'] == start assert schedule[0]['children'][0]['start'] == start assert schedule[0]['children'][0]['due'] == due assert schedule[0]['children'][0]['children'][0]['due'] == due assert schedule[0]['children'][0]['children'][0]['start'] == start self.assert_progress_summary(ccx_course_key, due) @override_settings( XBLOCK_FIELD_DATA_WRAPPERS=['lms.djangoapps.courseware.field_overrides:OverrideModulestoreFieldData.wrap'], MODULESTORE_FIELD_OVERRIDE_PROVIDERS=['lms.djangoapps.ccx.overrides.CustomCoursesForEdxOverrideProvider'], ) @ddt.ddt class TestCoachDashboard(CcxTestCase, LoginEnrollmentTestCase): """ Tests for Custom Courses views. """ @classmethod def setUpClass(cls): super().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().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') assert CourseStaffRole(self.course.id).has_user(staff) # adding instructor to master course. instructor = UserFactory() allow_access(self.course, instructor, 'instructor') assert 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) assert 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': str(self.course.id)}) response = self.client.get(url) assert response.status_code == 200 assert re.search('