""" Provide tests for sysadmin dashboard feature in sysadmin.py """ import glob import os import re import shutil import unittest from datetime import datetime from uuid import uuid4 import mongoengine from django.conf import settings from django.test.client import Client from django.test.utils import override_settings from django.urls import reverse from opaque_keys.edx.locator import CourseLocator from pytz import UTC from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, SharedModuleStoreTestCase from xmodule.modulestore.tests.mongo_connection import MONGO_HOST, MONGO_PORT_NUM from common.djangoapps.student.roles import CourseStaffRole, GlobalStaff from common.djangoapps.student.tests.factories import UserFactory from common.djangoapps.util.date_utils import DEFAULT_DATE_TIME_FORMAT, get_time_display from lms.djangoapps.dashboard.git_import import GitImportErrorNoDir from lms.djangoapps.dashboard.models import CourseImportLog from openedx.core.djangolib.markup import Text TEST_MONGODB_LOG = { 'host': MONGO_HOST, 'port': MONGO_PORT_NUM, 'user': '', 'password': '', 'db': 'test_xlog', } class SysadminBaseTestCase(SharedModuleStoreTestCase): """ Base class with common methods used in XML and Mongo tests """ TEST_REPO = 'https://github.com/edx/edx4edx_lite.git' TEST_BRANCH = 'testing_do_not_delete' TEST_BRANCH_COURSE = CourseLocator.from_string('course-v1:MITx+edx4edx_branch+edx4edx') MODULESTORE = TEST_DATA_SPLIT_MODULESTORE def setUp(self): """Setup test case by adding primary user.""" super().setUp() self.user = UserFactory.create(username='test_user', email='test_user+sysadmin@edx.org', password='foo') self.client = Client() def _setstaff_login(self): """Makes the test user staff and logs them in""" GlobalStaff().add_users(self.user) self.client.login(username=self.user.username, password='foo') def _add_edx4edx(self, branch=None): """Adds the edx4edx sample course""" post_dict = {'repo_location': self.TEST_REPO, 'action': 'add_course', } if branch: post_dict['repo_branch'] = branch return self.client.post(reverse('sysadmin_courses'), post_dict) def _rm_edx4edx(self): """Deletes the sample course from the XML store""" def_ms = modulestore() course_path = '{}/edx4edx_lite'.format( os.path.abspath(settings.DATA_DIR)) try: # using XML store course = def_ms.courses.get(course_path, None) except AttributeError: # Using mongo store course = def_ms.get_course(CourseLocator('MITx', 'edx4edx', 'edx4edx')) # Delete git loaded course response = self.client.post( reverse('sysadmin_courses'), { 'course_id': str(course.id), 'action': 'del_course', } ) self.addCleanup(self._rm_glob, f'{course_path}_deleted_*') return response def _rm_glob(self, path): """ Create a shell expansion of passed in parameter and iteratively remove them. Must only expand to directories. """ for path in glob.glob(path): # lint-amnesty, pylint: disable=redefined-argument-from-local shutil.rmtree(path) def _mkdir(self, path): """ Create directory and add the cleanup for it. """ os.mkdir(path) self.addCleanup(shutil.rmtree, path) @override_settings( MONGODB_LOG=TEST_MONGODB_LOG, GIT_REPO_DIR=settings.TEST_ROOT / f"course_repos_{uuid4().hex}" ) @unittest.skipUnless(settings.FEATURES.get('ENABLE_SYSADMIN_DASHBOARD'), "ENABLE_SYSADMIN_DASHBOARD not set") class TestSysAdminMongoCourseImport(SysadminBaseTestCase): """ Check that importing into the mongo module store works """ @classmethod def tearDownClass(cls): """Delete mongo log entries after test.""" super().tearDownClass() try: mongoengine.connect(TEST_MONGODB_LOG['db']) CourseImportLog.objects.all().delete() except mongoengine.connection.ConnectionFailure: pass def _setstaff_login(self): """ Makes the test user staff and logs them in """ self.user.is_staff = True self.user.save() self.client.login(username=self.user.username, password='foo') def test_missing_repo_dir(self): """ Ensure that we handle a missing repo dir """ self._setstaff_login() if os.path.isdir(settings.GIT_REPO_DIR): shutil.rmtree(settings.GIT_REPO_DIR) # Create git loaded course response = self._add_edx4edx() self.assertContains(response, Text(str(GitImportErrorNoDir(settings.GIT_REPO_DIR)))) def test_mongo_course_add_delete(self): """ This is the same as TestSysadmin.test_xml_course_add_delete, but it uses a mongo store """ self._setstaff_login() self._mkdir(settings.GIT_REPO_DIR) def_ms = modulestore() assert 'xml' != def_ms.get_modulestore_type(None) self._add_edx4edx() course = def_ms.get_course(CourseLocator('MITx', 'edx4edx', 'edx4edx')) assert course is not None self._rm_edx4edx() course = def_ms.get_course(CourseLocator('MITx', 'edx4edx', 'edx4edx')) assert course is None def test_course_info(self): """ Check to make sure we are getting git info for courses """ # Regex of first 3 columns of course information table row for # test course loaded from git. Would not have sha1 if # git_info_for_course failed. table_re = re.compile("""