"""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"'
def test_process_url_no_match_starts_with_xblock():
def processor(original, prefix, quote, rest): # pylint: disable=unused-argument, redefined-outer-name
return quote + 'test' + prefix + rest + quote
assert process_static_urls(
'"/static/xblock-file.png"',
processor,
data_dir=DATA_DIRECTORY
) == '"test/static/xblock-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