""" Tests for the course import API views """ from datetime import datetime from unittest.mock import MagicMock, patch from uuid import uuid4 import ddt import factory from django.conf import settings from django.contrib.auth import get_user_model from django.test.utils import override_settings from django.urls import reverse from rest_framework import status from rest_framework.test import APITestCase from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.course_modes.tests.factories import CourseModeFactory from common.djangoapps.student.tests.factories import StaffFactory, UserFactory from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import BlockFactory, CourseFactory User = get_user_model() @ddt.ddt @override_settings( PROCTORING_BACKENDS={ "DEFAULT": "test_proctoring_provider", "test_proctoring_provider": {"requires_escalation_email": True}, } ) class CourseValidationViewTest(SharedModuleStoreTestCase, APITestCase): """ Test course validation view via a RESTful API """ @classmethod def setUpClass(cls): super().setUpClass() cls.course = CourseFactory.create( display_name='test course', run="Testing_course", proctoring_provider='test_proctoring_provider', proctoring_escalation_email='test@example.com', ) cls.course_key = cls.course.id cls.password = 'test' cls.student = UserFactory(username='dummy', password=cls.password) cls.staff = StaffFactory(course_key=cls.course.id, password=cls.password) cls.initialize_course(cls.course) @classmethod def initialize_course(cls, course): """ Sets up test course structure. """ course.start = datetime.now() course.self_paced = True cls.store.update_item(course, cls.staff.id) update_key = course.id.make_usage_key('course_info', 'updates') cls.store.create_item( cls.staff.id, update_key.course_key, update_key.block_type, block_id=update_key.block_id, fields=dict(data="
  1. Date

    Hello world!
"), ) section = BlockFactory.create( parent_location=course.location, category="chapter", ) BlockFactory.create( parent_location=section.location, category="sequential", ) def get_url(self, course_id): """ Helper function to create the url """ return reverse( 'courses_api:course_validation', kwargs={ 'course_id': course_id, } ) def test_student_fails(self): self.client.login(username=self.student.username, password=self.password) resp = self.client.get(self.get_url(self.course_key)) self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) @ddt.data( (False, False), (True, False), (False, True), (True, True), ) @ddt.unpack def test_staff_succeeds(self, certs_html_view, with_modes): features = dict(settings.FEATURES, CERTIFICATES_HTML_VIEW=certs_html_view) with override_settings(FEATURES=features): if with_modes: CourseModeFactory.create_batch( 2, course_id=self.course.id, mode_slug=factory.Iterator([CourseMode.AUDIT, CourseMode.VERIFIED]), ) self.client.login(username=self.staff.username, password=self.password) resp = self.client.get(self.get_url(self.course_key), {'all': 'true'}) self.assertEqual(resp.status_code, status.HTTP_200_OK) expected_data = { 'assignments': { 'total_number': 1, 'total_visible': 1, 'assignments_with_dates_before_start': [], 'assignments_with_dates_after_end': [], 'assignments_with_ora_dates_after_end': [], 'assignments_with_ora_dates_before_start': [], }, 'dates': { 'has_start_date': True, 'has_end_date': False, }, 'updates': { 'has_update': True, }, 'certificates': { 'is_enabled': with_modes, 'is_activated': False, 'has_certificate': False, }, 'grades': { 'has_grading_policy': False, 'sum_of_weights': 1.0, }, 'proctoring': { 'needs_proctoring_escalation_email': True, 'has_proctoring_escalation_email': True, }, 'is_self_paced': True, } self.assertDictEqual(resp.data, expected_data) class TestMigrationViewSetCreate(SharedModuleStoreTestCase, APITestCase): """ Test the MigrationViewSet.create() endpoint. Focus: validation, return codes, serialization/deserialization. """ @classmethod def setUpClass(cls): super().setUpClass() cls.course = CourseFactory.create( display_name='test course', run="Testing_course", proctoring_provider='test_proctoring_provider', proctoring_escalation_email='test@example.com', ) cls.course_key = cls.course.id cls.password = 'test' cls.student = UserFactory(username='dummy', password=cls.password) cls.staff = StaffFactory(course_key=cls.course.id, password=cls.password) cls.initialize_course(cls.course) @classmethod def initialize_course(cls, course): """ Sets up test course structure. """ section = BlockFactory.create( parent_location=course.location, category="chapter", ) subsection = BlockFactory.create( parent_location=section.location, category="sequential", ) unit = BlockFactory.create( parent_location=subsection.location, category="vertical", ) cls.block1 = BlockFactory.create( parent_location=unit.location, category="library_content", ) cls.block2 = BlockFactory.create( parent_location=unit.location, category="library_content", ) @patch('cms.djangoapps.contentstore.api.views.utils.has_course_author_access') @patch('cms.djangoapps.contentstore.api.views.course_validation.UserTaskStatus') @patch('xmodule.library_content_block.LegacyLibraryContentBlock.is_ready_to_migrate_to_v2') def test_create_update_reference_success(self, mock_block, mock_user_task_status, mock_auth): """ Test successful migration creation with minimal required fields. Validates: - 201 status code is returned - Response contains expected serialized fields - Request data is properly deserialized - Permission checks are performed for both source and target """ mock_auth.return_value = True mock_task_status = MagicMock(autospec=True) mock_task_status.uuid = uuid4() mock_task_status.state = 'Pending' mock_task_status.state_text = 'Pending' mock_task_status.completed_steps = 0 mock_task_status.total_steps = 10 mock_task_status.attempts = 1 mock_task_status.created = '2025-01-01T00:00:00Z' mock_task_status.modified = '2025-01-01T00:00:00Z' mock_task_status.artifacts = [] mock_task_status.migrations.all.return_value = [] mock_user_task_status.objects.get.return_value = mock_task_status mock_block.return_value = True self.client.login(username=self.staff.username, password=self.password) response = self.client.post( f'/api/courses/v1/migrate_legacy_content_blocks/{self.course_key}/', ) assert response.status_code == status.HTTP_201_CREATED assert 'uuid' in response.data assert 'state' in response.data assert 'state_text' in response.data assert 'completed_steps' in response.data assert 'total_steps' in response.data mock_auth.assert_called_once() @patch('cms.djangoapps.contentstore.api.views.utils.has_course_author_access') @patch('xmodule.library_content_block.LegacyLibraryContentBlock.is_ready_to_migrate_to_v2') def test_list_ready_to_update_reference_success(self, mock_block, mock_auth): """ Test successful migration creation with minimal required fields. Validates: - 201 status code is returned - Response contains expected serialized fields - Request data is properly deserialized - Permission checks are performed for both source and target """ mock_auth.return_value = True mock_block.return_value = True self.client.login(username=self.staff.username, password=self.password) response = self.client.get( f'/api/courses/v1/migrate_legacy_content_blocks/{self.course_key}/', ) assert response.status_code == status.HTTP_200_OK data = response.json() self.assertListEqual(data, [ {'usage_key': str(self.block1.location)}, {'usage_key': str(self.block2.location)}, ]) mock_auth.assert_called_once()