"""Tests for static_replace""" from functools import partial import re from io import BytesIO from unittest.mock import Mock, patch from urllib.parse import parse_qsl, quote, urlparse, urlunparse, urlencode import ddt import pytest from django.test import override_settings from opaque_keys.edx.keys import CourseKey from PIL import Image from web_fragments.fragment import Fragment from common.djangoapps.static_replace import ( _url_replace_regex, make_static_urls_absolute, process_static_urls, replace_course_urls, replace_static_urls, replace_jump_to_id_urls, ) from common.djangoapps.static_replace.services import ReplaceURLService from common.djangoapps.static_replace.wrapper import replace_urls_wrapper from xmodule.assetstore.assetmgr import AssetManager # lint-amnesty, pylint: disable=wrong-import-order from xmodule.contentstore.content import StaticContent # lint-amnesty, pylint: disable=wrong-import-order from xmodule.contentstore.django import contentstore # lint-amnesty, pylint: disable=wrong-import-order from xmodule.exceptions import NotFoundError # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.mongo import MongoModuleStore # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.xml import XMLModuleStore # lint-amnesty, pylint: disable=wrong-import-order DATA_DIRECTORY = 'data_dir' COURSE_KEY = CourseKey.from_string('org/course/run') STATIC_SOURCE = '"/static/file.png"' def encode_unicode_characters_in_url(url): """ Encodes all Unicode characters to their percent-encoding representation in both the path portion and query parameter portion of the given URL. """ scheme, netloc, path, params, query, fragment = urlparse(url) query_params = parse_qsl(query) updated_query_params = [] for query_name, query_val in query_params: updated_query_params.append((query_name, quote(query_val))) return urlunparse((scheme, netloc, quote(path, '/:+@'), params, urlencode(query_params), fragment)) def test_multi_replace(): course_source = '"/course/file.png"' assert replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY) == \ replace_static_urls(replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY), DATA_DIRECTORY) assert replace_course_urls(course_source, COURSE_KEY) == \ replace_course_urls(replace_course_urls(course_source, COURSE_KEY), COURSE_KEY) def test_process_url(): def processor(__, prefix, quote, rest): # pylint: disable=redefined-outer-name return quote + 'test' + prefix + rest + quote assert process_static_urls(STATIC_SOURCE, processor) == '"test/static/file.png"' def test_process_url_data_dir_exists(): base = f'"/static/{DATA_DIRECTORY}/file.png"' def processor(original, prefix, quote, rest): # pylint: disable=unused-argument, redefined-outer-name return quote + 'test' + rest + quote assert process_static_urls(base, processor, data_dir=DATA_DIRECTORY) == base def test_process_url_no_match(): def processor(__, prefix, quote, rest): # pylint: disable=redefined-outer-name return quote + 'test' + prefix + rest + quote assert process_static_urls(STATIC_SOURCE, processor) == '"test/static/file.png"' @patch('django.http.HttpRequest', autospec=True) def test_static_urls(mock_request): mock_request.build_absolute_uri = lambda url: 'http://' + url result = make_static_urls_absolute(mock_request, STATIC_SOURCE) assert result == '\"http:///static/file.png\"' @patch('common.djangoapps.static_replace.staticfiles_storage', autospec=True) def test_storage_url_exists(mock_storage): mock_storage.exists.return_value = True mock_storage.url.return_value = '/static/file.png' assert replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY) == '"/static/file.png"' mock_storage.exists.assert_called_once_with('file.png') mock_storage.url.assert_called_once_with('file.png') @patch('common.djangoapps.static_replace.staticfiles_storage', autospec=True) def test_storage_url_not_exists(mock_storage): mock_storage.exists.return_value = False mock_storage.url.return_value = '/static/data_dir/file.png' assert replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY) == '"/static/data_dir/file.png"' mock_storage.exists.assert_called_once_with('file.png') mock_storage.url.assert_called_once_with('data_dir/file.png') @patch('common.djangoapps.static_replace.StaticContent', autospec=True) @patch('xmodule.modulestore.django.modulestore', autospec=True) @patch('common.djangoapps.static_replace.models.AssetBaseUrlConfig.get_base_url') @patch('common.djangoapps.static_replace.models.AssetExcludedExtensionsConfig.get_excluded_extensions') def test_mongo_filestore(mock_get_excluded_extensions, mock_get_base_url, mock_modulestore, mock_static_content): mock_modulestore.return_value = Mock(MongoModuleStore) mock_static_content.get_canonicalized_asset_path.return_value = "c4x://mock_url" mock_get_base_url.return_value = '' mock_get_excluded_extensions.return_value = ['foobar'] # No namespace => no change to path assert replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY) == '"/static/data_dir/file.png"' # Namespace => content url assert '"' + mock_static_content.get_canonicalized_asset_path.return_value + '"' == \ replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY, course_id=COURSE_KEY) mock_static_content.get_canonicalized_asset_path.assert_called_once_with(COURSE_KEY, 'file.png', '', ['foobar']) @patch('common.djangoapps.static_replace.settings', autospec=True) @patch('xmodule.modulestore.django.modulestore', autospec=True) @patch('common.djangoapps.static_replace.staticfiles_storage', autospec=True) def test_data_dir_fallback(mock_storage, mock_modulestore, mock_settings): # lint-amnesty, pylint: disable=unused-argument mock_modulestore.return_value = Mock(XMLModuleStore) mock_storage.url.side_effect = Exception mock_storage.exists.return_value = True assert replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY) == '"/static/data_dir/file.png"' mock_storage.exists.return_value = False assert replace_static_urls(STATIC_SOURCE, DATA_DIRECTORY) == '"/static/data_dir/file.png"' def test_raw_static_check(): """ Make sure replace_static_urls leaves alone things that end in '.raw' """ path = '"/static/foo.png?raw"' assert replace_static_urls(path, DATA_DIRECTORY) == path text = 'text
') test_replace = replace_urls_wrapper( block=self.course, view='baseview', frag=fragment, context=None, replace_url_service=partial(ReplaceURLService, jump_to_id_base_url='/base_url/'), ) assert isinstance(test_replace, Fragment) assert test_replace.content == replace_jump_to_id_urls(fragment.content, self.course.id, '/base_url/') assert test_replace.content == '' def test_replace_course_urls(self): """ Verify that the course URL has been replaced. """ fragment = Fragment('') test_replace = replace_urls_wrapper( block=self.course, view='baseview', frag=fragment, context=None, replace_url_service=ReplaceURLService, ) assert isinstance(test_replace, Fragment) assert test_replace.content == replace_course_urls(fragment.content, course_key=self.course.id) assert test_replace.content == '' def test_replace_static_urls(self): """ Verify that the static URL has been replaced. """ fragment = Fragment('') test_replace = replace_urls_wrapper( block=self.course, view='baseview', frag=fragment, context=None, replace_url_service=ReplaceURLService, static_replace_only=True, ) assert isinstance(test_replace, Fragment) assert test_replace.content == replace_static_urls(fragment.content, course_id=self.course.id) assert test_replace.content == ''