Skip to content

Commit

Permalink
test: [AXM-653] Fix old tests, add new tests
Browse files Browse the repository at this point in the history
  • Loading branch information
KyryloKireiev committed Jun 23, 2024
1 parent 8cb8daa commit fc5be32
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 107 deletions.
48 changes: 37 additions & 11 deletions lms/djangoapps/mobile_api/tests/test_course_info_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
from common.djangoapps.student.tests.factories import UserFactory # pylint: disable=unused-import
from common.djangoapps.util.course import get_link_for_about_page
from lms.djangoapps.mobile_api.testutils import MobileAPITestCase, MobileAuthTestMixin, MobileCourseAccessTestMixin
from lms.djangoapps.mobile_api.utils import API_V1, API_V05
from lms.djangoapps.mobile_api.utils import API_V05, API_V1, API_V2, API_V3, API_V4
from lms.djangoapps.mobile_api.course_info.views import BlocksInfoInCourseView
from lms.djangoapps.course_api.blocks.tests.test_views import TestBlocksInCourseView
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
Expand Down Expand Up @@ -460,12 +462,12 @@ def test_extend_block_info_with_offline_data(
get_offline_block_content_path_mock: MagicMock,
default_storage_mock: MagicMock,
) -> None:
url = reverse('blocks_info_in_course', kwargs={'api_version': 'v4'})
url = reverse('blocks_info_in_course', kwargs={'api_version': API_V4})
offline_content_path_mock = '/offline_content_path_mock/'
created_time_mock = 'created_time_mock'
size_mock = 'size_mock'
get_offline_block_content_path_mock.return_value = offline_content_path_mock
default_storage_mock.get_created_time.return_value = created_time_mock
default_storage_mock.get_modified_time.return_value = created_time_mock
default_storage_mock.size.return_value = size_mock

expected_offline_download_data = {
Expand All @@ -483,14 +485,14 @@ def test_extend_block_info_with_offline_data(

@patch('lms.djangoapps.mobile_api.course_info.views.is_offline_mode_enabled')
@ddt.data(
('v0.5', True),
('v0.5', False),
('v1', True),
('v1', False),
('v2', True),
('v2', False),
('v3', True),
('v3', False),
(API_V05, True),
(API_V05, False),
(API_V1, True),
(API_V1, False),
(API_V2, True),
(API_V2, False),
(API_V3, True),
(API_V3, False),
)
@ddt.unpack
def test_not_extend_block_info_with_offline_data_for_version_less_v4_and_any_waffle_flag(
Expand All @@ -507,3 +509,27 @@ def test_not_extend_block_info_with_offline_data_for_version_less_v4_and_any_waf
self.assertEqual(response.status_code, status.HTTP_200_OK)
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'])
47 changes: 46 additions & 1 deletion openedx/features/offline_mode/tests/test_html_manipulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from bs4 import BeautifulSoup
from unittest import TestCase
from unittest.mock import MagicMock, Mock, patch
from unittest.mock import MagicMock, Mock, call, patch

from openedx.features.offline_mode.constants import MATHJAX_CDN_URL, MATHJAX_STATIC_PATH
from openedx.features.offline_mode.html_manipulator import HtmlManipulator
Expand All @@ -17,6 +17,8 @@ class HtmlManipulatorTestCase(TestCase):

@patch('openedx.features.offline_mode.html_manipulator.HtmlManipulator._replace_iframe')
@patch('openedx.features.offline_mode.html_manipulator.BeautifulSoup', return_value='soup_mock')
@patch('openedx.features.offline_mode.html_manipulator.HtmlManipulator._copy_platform_fonts')
@patch('openedx.features.offline_mode.html_manipulator.HtmlManipulator._replace_external_links')
@patch('openedx.features.offline_mode.html_manipulator.HtmlManipulator._replace_mathjax_link')
@patch('openedx.features.offline_mode.html_manipulator.HtmlManipulator._replace_static_links')
@patch('openedx.features.offline_mode.html_manipulator.HtmlManipulator._replace_asset_links')
Expand All @@ -25,6 +27,8 @@ def test_process_html(
replace_asset_links_mock: MagicMock,
replace_static_links_mock: MagicMock,
replace_mathjax_link_mock: MagicMock,
replace_external_links: MagicMock,
copy_platform_fonts: MagicMock,
beautiful_soup_mock: MagicMock,
replace_iframe_mock: MagicMock,
) -> None:
Expand All @@ -39,6 +43,8 @@ def test_process_html(
replace_asset_links_mock.assert_called_once_with()
replace_static_links_mock.assert_called_once_with()
replace_mathjax_link_mock.assert_called_once_with()
replace_external_links.assert_called_once_with()
copy_platform_fonts.assert_called_once_with()
beautiful_soup_mock.assert_called_once_with(html_manipulator.html_data, 'html.parser')
replace_iframe_mock.assert_called_once_with(beautiful_soup_mock.return_value)
self.assertEqual(result, expected_result)
Expand Down Expand Up @@ -116,3 +122,42 @@ def test_replace_iframe(self):
HtmlManipulator._replace_iframe(soup) # lint-amnesty, pylint: disable=protected-access

self.assertEqual(f'{soup.find_all("p")[0]}', expected_html_markup)

@patch('openedx.features.offline_mode.html_manipulator.save_external_file')
def test_replace_external_links(self, save_external_file_mock: MagicMock) -> None:
xblock_mock = Mock()
temp_dir_mock = 'temp_dir_mock'
html_data_mock = """
<img src="https://cdn.example.com/image.jpg" alt="Example Image">
<script src="https://ajax.libs.jquery/3.6.0/jquery.min.js"></script>
"""

html_manipulator = HtmlManipulator(xblock_mock, html_data_mock, temp_dir_mock)
html_manipulator._replace_external_links() # lint-amnesty, pylint: disable=protected-access

self.assertEqual(save_external_file_mock.call_count, 2)

@patch('openedx.features.offline_mode.html_manipulator.uuid.uuid4')
@patch('openedx.features.offline_mode.html_manipulator.save_external_file')
def test_replace_external_link(
self,
save_external_file_mock: MagicMock,
uuid_mock: MagicMock,
) -> None:
xblock_mock = Mock()
temp_dir_mock = 'temp_dir_mock'
html_data_mock = 'html_data_mock'
external_url_mock = 'https://cdn.example.com/image.jpg'
uuid_result_mock = '123e4567-e89b-12d3-a456-426655440000'
uuid_mock.return_value = uuid_result_mock
mock_match = MagicMock()
mock_match.group.side_effect = [external_url_mock, 'jpg']

expected_result = 'assets/external/123e4567-e89b-12d3-a456-426655440000.jpg'
expected_save_external_file_args = [call(temp_dir_mock, external_url_mock, expected_result)]

html_manipulator = HtmlManipulator(xblock_mock, html_data_mock, temp_dir_mock)
result = html_manipulator._replace_external_link(mock_match) # lint-amnesty, pylint: disable=protected-access

self.assertListEqual(save_external_file_mock.call_args_list, expected_save_external_file_args)
self.assertEqual(result, expected_result)
98 changes: 32 additions & 66 deletions openedx/features/offline_mode/tests/test_storage_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,99 +18,69 @@ class OfflineContentGeneratorTestCase(TestCase):
"""
Test case for the testing Offline Mode utils.
"""

@patch('openedx.features.offline_mode.storage_management.shutil.rmtree')
@patch('openedx.features.offline_mode.storage_management.OfflineContentGenerator.create_zip_file')
@patch('openedx.features.offline_mode.storage_management.OfflineContentGenerator.save_xblock_html')
@patch('openedx.features.offline_mode.storage_management.mkdtemp')
@patch('openedx.features.offline_mode.storage_management.clean_outdated_xblock_files')
@patch('openedx.features.offline_mode.storage_management.block_storage_path')
@patch('openedx.features.offline_mode.storage_management.is_modified', return_value=True)
def test_generate_offline_content_for_modified_xblock(
self,
is_modified_mock: MagicMock,
block_storage_path_mock: MagicMock,
clean_outdated_xblock_files_mock: MagicMock,
mkdtemp_mock: MagicMock,
save_xblock_html_mock: MagicMock,
create_zip_file_mock: MagicMock,
shutil_rmtree_mock: MagicMock,
) -> None:
@patch('openedx.features.offline_mode.storage_management.XBlockRenderer')
def test_render_block_html_data_successful(self, xblock_renderer_mock: MagicMock) -> None:
xblock_mock = Mock()
html_data_mock = 'html_markup_data_mock'

OfflineContentGenerator(xblock_mock, html_data_mock).generate_offline_content()
result = OfflineContentGenerator(xblock_mock, html_data_mock).render_block_html_data()

is_modified_mock.assert_called_once_with(xblock_mock)
block_storage_path_mock.assert_called_once_with(xblock_mock)
clean_outdated_xblock_files_mock.assert_called_once_with(xblock_mock)
mkdtemp_mock.assert_called_once_with()
save_xblock_html_mock.assert_called_once_with(mkdtemp_mock.return_value)
create_zip_file_mock.assert_called_once_with(
mkdtemp_mock.return_value,
block_storage_path_mock.return_value,
f'{xblock_mock.location.block_id}.zip'
xblock_renderer_mock.assert_called_once_with(str(xblock_mock.location))
xblock_renderer_mock.return_value.render_xblock_from_lms.assert_called_once_with()
self.assertEqual(result, xblock_renderer_mock.return_value.render_xblock_from_lms.return_value)

@patch('openedx.features.offline_mode.storage_management.XBlockRenderer')
def test_render_block_html_data_successful_no_html_data(self, xblock_renderer_mock: MagicMock) -> None:
xblock_mock = Mock()
expected_xblock_renderer_args_list = [call(str(xblock_mock.location)), call(str(xblock_mock.location))]

result = OfflineContentGenerator(xblock_mock).render_block_html_data()

self.assertListEqual(xblock_renderer_mock.call_args_list, expected_xblock_renderer_args_list)
self.assertListEqual(
xblock_renderer_mock.return_value.render_xblock_from_lms.call_args_list, [call(), call()]
)
shutil_rmtree_mock.assert_called_once_with(mkdtemp_mock.return_value, ignore_errors=True)
self.assertEqual(result, xblock_renderer_mock.return_value.render_xblock_from_lms.return_value)

@patch('openedx.features.offline_mode.storage_management.shutil.rmtree')
@patch('openedx.features.offline_mode.storage_management.OfflineContentGenerator.create_zip_file')
@patch('openedx.features.offline_mode.storage_management.OfflineContentGenerator.save_xblock_html')
@patch('openedx.features.offline_mode.storage_management.mkdtemp')
@patch('openedx.features.offline_mode.storage_management.clean_outdated_xblock_files')
@patch('openedx.features.offline_mode.storage_management.block_storage_path')
@patch('openedx.features.offline_mode.storage_management.is_modified', return_value=False)
def test_generate_offline_content_is_not_modified(
@patch('openedx.features.offline_mode.storage_management.log.error')
@patch('openedx.features.offline_mode.storage_management.XBlockRenderer', side_effect=Http404)
def test_render_block_html_data_http404(
self,
is_modified_mock: MagicMock,
block_storage_path_mock: MagicMock,
clean_outdated_xblock_files_mock: MagicMock,
mkdtemp_mock: MagicMock,
save_xblock_html_mock: MagicMock,
create_zip_file_mock: MagicMock,
shutil_rmtree_mock: MagicMock,
xblock_renderer_mock: MagicMock,
logger_mock: MagicMock,
) -> None:
xblock_mock = Mock()
html_data_mock = 'html_markup_data_mock'

OfflineContentGenerator(xblock_mock, html_data_mock).generate_offline_content()
with self.assertRaises(Http404):
OfflineContentGenerator(xblock_mock, html_data_mock).render_block_html_data()

is_modified_mock.assert_called_once_with(xblock_mock)
block_storage_path_mock.assert_not_called()
clean_outdated_xblock_files_mock.assert_not_called()
mkdtemp_mock.assert_not_called()
save_xblock_html_mock.assert_not_called()
create_zip_file_mock.assert_not_called()
shutil_rmtree_mock.assert_not_called()
xblock_renderer_mock.assert_called_once_with(str(xblock_mock.location))
logger_mock.assert_called_once_with(
f'Block {str(xblock_mock.location)} cannot be fetched from course'
f' {xblock_mock.location.course_key} during offline content generation.'
)

@patch('openedx.features.offline_mode.storage_management.shutil.rmtree')
@patch('openedx.features.offline_mode.storage_management.log.error')
@patch(
'openedx.features.offline_mode.storage_management.OfflineContentGenerator.create_zip_file',
side_effect=Http404,
)
@patch('openedx.features.offline_mode.storage_management.OfflineContentGenerator.create_zip_file')
@patch('openedx.features.offline_mode.storage_management.OfflineContentGenerator.save_xblock_html')
@patch('openedx.features.offline_mode.storage_management.mkdtemp')
@patch('openedx.features.offline_mode.storage_management.clean_outdated_xblock_files')
@patch('openedx.features.offline_mode.storage_management.block_storage_path')
@patch('openedx.features.offline_mode.storage_management.is_modified', return_value=True)
def test_generate_offline_content_is_not_modified_with_http_404_status(
def test_generate_offline_content_for_modified_xblock(
self,
is_modified_mock: MagicMock,
block_storage_path_mock: MagicMock,
clean_outdated_xblock_files_mock: MagicMock,
mkdtemp_mock: MagicMock,
save_xblock_html_mock: MagicMock,
create_zip_file_mock: MagicMock,
logger_mock: MagicMock,
shutil_rmtree_mock: MagicMock,
) -> None:
xblock_mock = Mock()
html_data_mock = 'html_markup_data_mock'

OfflineContentGenerator(xblock_mock, html_data_mock).generate_offline_content()

is_modified_mock.assert_called_once_with(xblock_mock)
block_storage_path_mock.assert_called_once_with(xblock_mock)
clean_outdated_xblock_files_mock.assert_called_once_with(xblock_mock)
mkdtemp_mock.assert_called_once_with()
Expand All @@ -120,10 +90,6 @@ def test_generate_offline_content_is_not_modified_with_http_404_status(
block_storage_path_mock.return_value,
f'{xblock_mock.location.block_id}.zip'
)
logger_mock.assert_called_once_with(
f'Block {xblock_mock.location.block_id} cannot be fetched from course'
f' {xblock_mock.location.course_key} during offline content generation.'
)
shutil_rmtree_mock.assert_called_once_with(mkdtemp_mock.return_value, ignore_errors=True)

@patch('openedx.features.offline_mode.storage_management.os.path.join')
Expand Down
Loading

0 comments on commit fc5be32

Please sign in to comment.