diff --git a/cms/envs/production.py b/cms/envs/production.py index a4eff6e726b2..da5642b53c6c 100644 --- a/cms/envs/production.py +++ b/cms/envs/production.py @@ -169,6 +169,7 @@ def get_env_setting(setting): ENTERPRISE_CONSENT_API_URL = ENV_TOKENS.get('ENTERPRISE_CONSENT_API_URL', LMS_INTERNAL_ROOT_URL + '/consent/api/v1/') AUTHORING_API_URL = ENV_TOKENS.get('AUTHORING_API_URL', '') # Note that FEATURES['PREVIEW_LMS_BASE'] gets read in from the environment file. + CHAT_COMPLETION_API = ENV_TOKENS.get('CHAT_COMPLETION_API', '') CHAT_COMPLETION_API_KEY = ENV_TOKENS.get('CHAT_COMPLETION_API_KEY', '') LEARNER_ENGAGEMENT_PROMPT_FOR_ACTIVE_CONTRACT = ENV_TOKENS.get('LEARNER_ENGAGEMENT_PROMPT_FOR_ACTIVE_CONTRACT', '') diff --git a/lms/djangoapps/mobile_api/tests/test_course_info_views.py b/lms/djangoapps/mobile_api/tests/test_course_info_views.py index 979f7df24d2c..109853f38603 100644 --- a/lms/djangoapps/mobile_api/tests/test_course_info_views.py +++ b/lms/djangoapps/mobile_api/tests/test_course_info_views.py @@ -23,8 +23,6 @@ from lms.djangoapps.mobile_api.utils import API_V05, API_V1, API_V2, API_V3, API_V4 from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.features.course_experience import ENABLE_COURSE_GOALS -from openedx.features.offline_mode.constants import DEFAULT_OFFLINE_SUPPORTED_XBLOCKS -from openedx.features.offline_mode.toggles import ENABLE_OFFLINE_MODE from xmodule.html_block import CourseInfoBlock # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order @@ -489,30 +487,6 @@ def test_not_extend_block_info_with_offline_data_for_version_less_v4_and_any_waf for block_info in response.data['blocks'].values(): self.assertNotIn('offline_download', block_info) - @override_waffle_flag(ENABLE_OFFLINE_MODE, active=True) - @patch('openedx.features.offline_mode.html_manipulator.save_mathjax_to_xblock_assets') - def test_create_offline_content_integration_test(self, save_mathjax_to_xblock_assets_mock: MagicMock) -> None: - UserFactory.create(username='offline_mode_worker', password='password', is_staff=True) - handle_course_published_url = reverse('offline_mode:handle_course_published') - self.client.login(username='offline_mode_worker', password='password') - - handler_response = self.client.post(handle_course_published_url, {'course_id': str(self.course.id)}) - self.assertEqual(handler_response.status_code, status.HTTP_200_OK) - - url = reverse('blocks_info_in_course', kwargs={'api_version': API_V4}) - - response = self.verify_response(url=url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - for block_info in response.data['blocks'].values(): - if block_type := block_info.get('type'): - if block_type in DEFAULT_OFFLINE_SUPPORTED_XBLOCKS: - expected_offline_content_url = f'/uploads/{self.course.id}/{block_info["block_id"]}.zip' - self.assertIn('offline_download', block_info) - self.assertIn('file_url', block_info['offline_download']) - self.assertIn('last_modified', block_info['offline_download']) - self.assertIn('file_size', block_info['offline_download']) - self.assertEqual(expected_offline_content_url, block_info['offline_download']['file_url']) - class TestCourseEnrollmentDetailsView(MobileAPITestCase, MilestonesTestCaseMixin): # lint-amnesty, pylint: disable=test-inherits-tests """ diff --git a/openedx/features/offline_mode/tests/test_assets_management.py b/openedx/features/offline_mode/tests/test_assets_management.py index e386d10e2b57..c40629860ad1 100644 --- a/openedx/features/offline_mode/tests/test_assets_management.py +++ b/openedx/features/offline_mode/tests/test_assets_management.py @@ -273,7 +273,7 @@ def test_block_storage_path_exists(self) -> None: result = block_storage_path(xblock_mock) - self.assertEqual(result, 'course_key_mock/') + self.assertEqual(result, 'offline_content/course_key_mock/') def test_block_storage_path_does_not_exists(self) -> None: result = block_storage_path() diff --git a/openedx/features/offline_mode/tests/test_storage_management.py b/openedx/features/offline_mode/tests/test_storage_management.py index f77f2ba614cd..e8e9d3c093bb 100644 --- a/openedx/features/offline_mode/tests/test_storage_management.py +++ b/openedx/features/offline_mode/tests/test_storage_management.py @@ -243,11 +243,13 @@ def setUp(self): def test_generate_offline_content(self, save_mathjax_to_xblock_assets_mock): OfflineContentGenerator(self.html_block, self.html_data).generate_offline_content() - expected_offline_content_path = 'test_root/uploads/course-v1:RaccoonGang+1+2024/HTML_xblock_for_Offline.zip' + expected_offline_content_path = ( + 'test_root/uploads/offline_content/course-v1:RaccoonGang+1+2024/HTML_xblock_for_Offline.zip' + ) save_mathjax_to_xblock_assets_mock.assert_called_once() self.assertTrue(os.path.exists(expected_offline_content_path)) - shutil.rmtree('test_root/uploads/course-v1:RaccoonGang+1+2024', ignore_errors=True) + shutil.rmtree('test_root/uploads/offline_content/course-v1:RaccoonGang+1+2024', ignore_errors=True) def test_save_xblock_html_to_temp_dir(self): shutil.rmtree('test_root/assets', ignore_errors=True) diff --git a/openedx/features/offline_mode/tests/test_tasks.py b/openedx/features/offline_mode/tests/test_tasks.py index ef468d825d7b..10d1304a0b0d 100644 --- a/openedx/features/offline_mode/tests/test_tasks.py +++ b/openedx/features/offline_mode/tests/test_tasks.py @@ -2,140 +2,89 @@ Tests for the testing Offline Mode tacks. """ -from unittest import TestCase +import pytest from unittest.mock import MagicMock, Mock, call, patch -from ddt import data, ddt, unpack +from django.conf import settings from django.http.response import Http404 -from opaque_keys.edx.keys import CourseKey, UsageKey -from openedx.features.offline_mode.constants import MAX_RETRY_ATTEMPTS, OFFLINE_SUPPORTED_XBLOCKS +from common.djangoapps.student.tests.factories import UserFactory from openedx.features.offline_mode.tasks import ( generate_offline_content_for_block, generate_offline_content_for_course, ) +from .base import CourseForOfflineTestCase -@ddt -class GenerateOfflineContentTasksTestCase(TestCase): + +@pytest.mark.django_db +class GenerateOfflineContentTasksTestCase(CourseForOfflineTestCase): """ Test case for the testing generating offline content tacks. """ + def setUp(self) -> None: + self.user = UserFactory(username=settings.OFFLINE_SERVICE_WORKER_USERNAME) + super().setUp() + @patch('openedx.features.offline_mode.tasks.OfflineContentGenerator') - @patch('openedx.features.offline_mode.tasks.modulestore') + @patch('openedx.features.offline_mode.tasks.is_modified', return_value=True) def test_generate_offline_content_for_block_success( self, - modulestore_mock: MagicMock, + is_modified_mock: MagicMock, offline_content_generator_mock: MagicMock, ) -> None: - block_id_mock = 'block-v1:a+a+a+type@problem+block@fb81e4dbfd4945cb9318d6bc460a956c' - - generate_offline_content_for_block(block_id_mock) + generate_offline_content_for_block(str(self.html_block.location)) - modulestore_mock.assert_called_once_with() - modulestore_mock.return_value.get_item.assert_called_once_with(UsageKey.from_string(block_id_mock)) - offline_content_generator_mock.assert_called_once_with(modulestore_mock.return_value.get_item.return_value) - offline_content_generator_mock.return_value.generate_offline_content.assert_called_once_with() + is_modified_mock.assert_called() + offline_content_generator_mock.assert_called_once() + offline_content_generator_mock.return_value.generate_offline_content.assert_called_once() @patch('openedx.features.offline_mode.tasks.OfflineContentGenerator') @patch('openedx.features.offline_mode.tasks.modulestore', side_effect=Http404) - def test_generate_offline_content_for_block_with_exception_in_modulestore( - self, - modulestore_mock: MagicMock, - offline_content_generator_mock: MagicMock, - ) -> None: - block_id_mock = 'block-v1:a+a+a+type@problem+block@fb81e4dbfd4945cb9318d6bc460a956c' - - generate_offline_content_for_block.delay(block_id_mock) - - self.assertEqual(modulestore_mock.call_count, MAX_RETRY_ATTEMPTS + 1) - offline_content_generator_mock.assert_not_called() - - @patch('openedx.features.offline_mode.tasks.OfflineContentGenerator', side_effect=Http404) - @patch('openedx.features.offline_mode.tasks.modulestore') def test_generate_offline_content_for_block_with_exception_in_offline_content_generation( self, modulestore_mock: MagicMock, offline_content_generator_mock: MagicMock, ) -> None: - block_id_mock = 'block-v1:a+a+a+type@problem+block@fb81e4dbfd4945cb9318d6bc460a956c' - - generate_offline_content_for_block.delay(block_id_mock) + with pytest.raises(Http404): + generate_offline_content_for_block(str(self.html_block.location)) - self.assertEqual(modulestore_mock.call_count, MAX_RETRY_ATTEMPTS + 1) - self.assertEqual(offline_content_generator_mock.call_count, MAX_RETRY_ATTEMPTS + 1) + modulestore_mock.assert_called_once() + offline_content_generator_mock.assert_not_called() + offline_content_generator_mock.return_value.generate_offline_content.assert_not_called() @patch('openedx.features.offline_mode.tasks.generate_offline_content_for_block') @patch('openedx.features.offline_mode.tasks.is_modified') - @patch('openedx.features.offline_mode.tasks.modulestore') def test_generate_offline_content_for_course_supported_block_types( self, - modulestore_mock: MagicMock, is_modified_mock: MagicMock, generate_offline_content_for_block_mock: MagicMock, ) -> None: - course_id_mock = 'course-v1:a+a+a' - xblock_location_mock = 'xblock_location_mock' - modulestore_mock.return_value.get_items.return_value = [ - Mock(location=xblock_location_mock, closed=Mock(return_value=False)) - ] - - expected_call_args_for_modulestore_get_items = [ - call(CourseKey.from_string(course_id_mock), qualifiers={'category': offline_supported_block_type}) - for offline_supported_block_type in OFFLINE_SUPPORTED_XBLOCKS - ] - expected_call_args_is_modified_mock = [ - call(modulestore_mock.return_value.get_items.return_value[0]) for _ in OFFLINE_SUPPORTED_XBLOCKS - ] - expected_call_args_for_generate_offline_content_for_block_mock = [ - call([xblock_location_mock]) for _ in OFFLINE_SUPPORTED_XBLOCKS - ] - - generate_offline_content_for_course(course_id_mock) - - self.assertEqual(modulestore_mock.call_count, len(OFFLINE_SUPPORTED_XBLOCKS)) - self.assertListEqual( - modulestore_mock.return_value.get_items.call_args_list, expected_call_args_for_modulestore_get_items - ) - self.assertListEqual(is_modified_mock.call_args_list, expected_call_args_is_modified_mock) - self.assertListEqual( - generate_offline_content_for_block_mock.apply_async.call_args_list, - expected_call_args_for_generate_offline_content_for_block_mock + is_modified_mock.return_value = True + + generate_offline_content_for_course(str(self.course.id)) + + generate_offline_content_for_block_mock.assert_has_calls( + [ + call.apply_async([str(self.html_block.location)]), + call.apply_async([str(self.problem_block.location)]), + ], ) @patch('openedx.features.offline_mode.tasks.generate_offline_content_for_block') @patch('openedx.features.offline_mode.tasks.is_modified') - @patch('openedx.features.offline_mode.tasks.modulestore') - @data( - (False, False), - (True, False), - (False, True), - ) - @unpack - def test_generate_offline_content_for_course_supported_block_types_for_closed_or_not_modified_xblock( + @patch('openedx.features.offline_mode.tasks.get_course_blocks') + def test_generate_offline_content_for_course_unsupported_block_type( self, - is_modified_value_mock: bool, - is_closed_value_mock: bool, - modulestore_mock: MagicMock, + get_course_blocks_mock: MagicMock, is_modified_mock: MagicMock, generate_offline_content_for_block_mock: MagicMock, ) -> None: - course_id_mock = 'course-v1:a+a+a' - xblock_location_mock = 'xblock_location_mock' - modulestore_mock.return_value.get_items.return_value = [ - Mock(location=xblock_location_mock, closed=Mock(return_value=is_closed_value_mock)) - ] - is_modified_mock.return_value = is_modified_value_mock - - expected_call_args_for_modulestore_get_items = [ - call(CourseKey.from_string(course_id_mock), qualifiers={'category': offline_supported_block_type}) - for offline_supported_block_type in OFFLINE_SUPPORTED_XBLOCKS - ] - - generate_offline_content_for_course(course_id_mock) - - self.assertEqual(modulestore_mock.call_count, len(OFFLINE_SUPPORTED_XBLOCKS)) - self.assertListEqual( - modulestore_mock.return_value.get_items.call_args_list, expected_call_args_for_modulestore_get_items - ) + is_modified_mock.return_value = True + xblock_mock = Mock(closed=Mock(return_value=False)) + get_course_blocks_mock().get_item.return_value = xblock_mock + xblock_mock.block_type = 'unsupported_block_type' + + generate_offline_content_for_course(str(self.course.id)) + generate_offline_content_for_block_mock.assert_not_called()