Cleanup and remove deprecated RequestCache Django app

ARCH-223
This commit is contained in:
Nimisha Asthagiri
2018-08-30 14:32:29 -04:00
committed by Robert Raposa
parent 53d8a04b88
commit 700a902b68
54 changed files with 595 additions and 600 deletions

View File

@@ -1,65 +1,297 @@
# -*- coding: utf-8 -*-
"""
Tests for cache_utils.py
"""
from unittest import TestCase
import ddt
from mock import MagicMock
from mock import Mock
from openedx.core.lib.cache_utils import memoize_in_request_cache
from edx_django_utils.cache import RequestCache
from openedx.core.lib.cache_utils import request_cached
@ddt.ddt
class TestMemoizeInRequestCache(TestCase):
class TestRequestCachedDecorator(TestCase):
"""
Test the memoize_in_request_cache helper function.
Test the request_cached decorator.
"""
class TestCache(object):
"""
A test cache that provides a data dict for caching values, analogous to the request_cache.
"""
def __init__(self):
self.data = {}
def setUp(self):
super(TestMemoizeInRequestCache, self).setUp()
self.request_cache = self.TestCache()
RequestCache.clear_all_namespaces()
@memoize_in_request_cache('request_cache')
def func_to_memoize(self, param):
def test_request_cached_miss_and_then_hit(self):
"""
A test function whose results are to be memoized in the request_cache.
Ensure that after a cache miss, we fill the cache and can hit it.
"""
return self.func_to_count(param)
to_be_wrapped = Mock()
to_be_wrapped.return_value = 42
self.assertEqual(to_be_wrapped.call_count, 0)
@memoize_in_request_cache('request_cache')
def multi_param_func_to_memoize(self, param1, param2):
def mock_wrapper(*args, **kwargs):
"""Simple wrapper to let us decorate our mock."""
return to_be_wrapped(*args, **kwargs)
wrapped = request_cached()(mock_wrapper)
result = wrapped()
self.assertEqual(result, 42)
self.assertEqual(to_be_wrapped.call_count, 1)
result = wrapped()
self.assertEqual(result, 42)
self.assertEqual(to_be_wrapped.call_count, 1)
def test_request_cached_with_caches_despite_changing_wrapped_result(self):
"""
A test function with multiple parameters whose results are to be memoized in the request_cache.
Ensure that after caching a result, we always send it back, even if the underlying result changes.
"""
return self.func_to_count(param1, param2)
to_be_wrapped = Mock()
to_be_wrapped.side_effect = [1, 2, 3]
self.assertEqual(to_be_wrapped.call_count, 0)
def test_memoize_in_request_cache(self):
def mock_wrapper(*args, **kwargs):
"""Simple wrapper to let us decorate our mock."""
return to_be_wrapped(*args, **kwargs)
wrapped = request_cached()(mock_wrapper)
result = wrapped()
self.assertEqual(result, 1)
self.assertEqual(to_be_wrapped.call_count, 1)
result = wrapped()
self.assertEqual(result, 1)
self.assertEqual(to_be_wrapped.call_count, 1)
direct_result = mock_wrapper()
self.assertEqual(direct_result, 2)
self.assertEqual(to_be_wrapped.call_count, 2)
result = wrapped()
self.assertEqual(result, 1)
self.assertEqual(to_be_wrapped.call_count, 2)
direct_result = mock_wrapper()
self.assertEqual(direct_result, 3)
self.assertEqual(to_be_wrapped.call_count, 3)
def test_request_cached_with_changing_args(self):
"""
Tests the memoize_in_request_cache decorator for both single-param and multiple-param functions.
Ensure that calling a decorated function with different positional arguments
will not use a cached value invoked by a previous call with different arguments.
"""
funcs_to_test = (
(self.func_to_memoize, ['foo'], ['bar']),
(self.multi_param_func_to_memoize, ['foo', 'foo2'], ['foo', 'foo3']),
)
to_be_wrapped = Mock()
to_be_wrapped.side_effect = [1, 2, 3, 4, 5, 6]
self.assertEqual(to_be_wrapped.call_count, 0)
for func_to_memoize, arg_list1, arg_list2 in funcs_to_test:
self.func_to_count = MagicMock() # pylint: disable=attribute-defined-outside-init
self.assertFalse(self.func_to_count.called)
def mock_wrapper(*args, **kwargs):
"""Simple wrapper to let us decorate our mock."""
return to_be_wrapped(*args, **kwargs)
func_to_memoize(*arg_list1)
self.func_to_count.assert_called_once_with(*arg_list1)
wrapped = request_cached()(mock_wrapper)
func_to_memoize(*arg_list1)
self.func_to_count.assert_called_once_with(*arg_list1)
# This will be a miss, and make an underlying call.
result = wrapped(1)
self.assertEqual(result, 1)
self.assertEqual(to_be_wrapped.call_count, 1)
for _ in range(10):
func_to_memoize(*arg_list1)
func_to_memoize(*arg_list2)
# This will be a miss, and make an underlying call.
result = wrapped(2)
self.assertEqual(result, 2)
self.assertEqual(to_be_wrapped.call_count, 2)
self.assertEquals(self.func_to_count.call_count, 2)
# This is bypass of the decorator.
direct_result = mock_wrapper(3)
self.assertEqual(direct_result, 3)
self.assertEqual(to_be_wrapped.call_count, 3)
# These will be hits, and not make an underlying call.
result = wrapped(1)
self.assertEqual(result, 1)
self.assertEqual(to_be_wrapped.call_count, 3)
result = wrapped(2)
self.assertEqual(result, 2)
self.assertEqual(to_be_wrapped.call_count, 3)
def test_request_cached_with_changing_kwargs(self):
"""
Ensure that calling a decorated function with different keyword arguments
will not use a cached value invoked by a previous call with different arguments.
"""
to_be_wrapped = Mock()
to_be_wrapped.side_effect = [1, 2, 3, 4, 5, 6]
self.assertEqual(to_be_wrapped.call_count, 0)
def mock_wrapper(*args, **kwargs):
"""Simple wrapper to let us decorate our mock."""
return to_be_wrapped(*args, **kwargs)
wrapped = request_cached()(mock_wrapper)
# This will be a miss, and make an underlying call.
result = wrapped(1, foo=1)
self.assertEqual(result, 1)
self.assertEqual(to_be_wrapped.call_count, 1)
# This will be a miss, and make an underlying call.
result = wrapped(2, foo=2)
self.assertEqual(result, 2)
self.assertEqual(to_be_wrapped.call_count, 2)
# This is bypass of the decorator.
direct_result = mock_wrapper(3, foo=3)
self.assertEqual(direct_result, 3)
self.assertEqual(to_be_wrapped.call_count, 3)
# These will be hits, and not make an underlying call.
result = wrapped(1, foo=1)
self.assertEqual(result, 1)
self.assertEqual(to_be_wrapped.call_count, 3)
result = wrapped(2, foo=2)
self.assertEqual(result, 2)
self.assertEqual(to_be_wrapped.call_count, 3)
# Since we're changing foo, this will be a miss.
result = wrapped(2, foo=5)
self.assertEqual(result, 4)
self.assertEqual(to_be_wrapped.call_count, 4)
# Since we're adding bar, this will be a miss.
result = wrapped(2, foo=1, bar=2)
self.assertEqual(result, 5)
self.assertEqual(to_be_wrapped.call_count, 5)
# Should be a hit, even when kwargs are in a different order
result = wrapped(2, bar=2, foo=1)
self.assertEqual(result, 5)
self.assertEqual(to_be_wrapped.call_count, 5)
def test_request_cached_mixed_unicode_str_args(self):
"""
Ensure that request_cached can work with mixed str and Unicode parameters.
"""
def dummy_function(arg1, arg2):
"""
A dummy function that expects an str and unicode arguments.
"""
assert isinstance(arg1, str), 'First parameter has to be of type `str`'
assert isinstance(arg2, unicode), 'Second parameter has to be of type `unicode`'
return True
self.assertTrue(dummy_function('Hello', u'World'), 'Should be callable with ASCII chars')
self.assertTrue(dummy_function('H∂llå', u'Wørld'), 'Should be callable with non-ASCII chars')
wrapped = request_cached()(dummy_function)
self.assertTrue(wrapped('Hello', u'World'), 'Wrapper should handle ASCII only chars')
self.assertTrue(wrapped('H∂llå', u'Wørld'), 'Wrapper should handle non-ASCII chars')
def test_request_cached_with_none_result(self):
"""
Ensure that calling a decorated function that returns None
properly caches the result and doesn't recall the underlying
function.
"""
to_be_wrapped = Mock()
to_be_wrapped.side_effect = [None, None, None, 1, 1]
self.assertEqual(to_be_wrapped.call_count, 0)
def mock_wrapper(*args, **kwargs):
"""Simple wrapper to let us decorate our mock."""
return to_be_wrapped(*args, **kwargs)
wrapped = request_cached()(mock_wrapper)
# This will be a miss, and make an underlying call.
result = wrapped(1)
self.assertEqual(result, None)
self.assertEqual(to_be_wrapped.call_count, 1)
# This will be a miss, and make an underlying call.
result = wrapped(2)
self.assertEqual(result, None)
self.assertEqual(to_be_wrapped.call_count, 2)
# This is bypass of the decorator.
direct_result = mock_wrapper(3)
self.assertEqual(direct_result, None)
self.assertEqual(to_be_wrapped.call_count, 3)
# These will be hits, and not make an underlying call.
result = wrapped(1)
self.assertEqual(result, None)
self.assertEqual(to_be_wrapped.call_count, 3)
result = wrapped(2)
self.assertEqual(result, None)
self.assertEqual(to_be_wrapped.call_count, 3)
def test_request_cached_with_request_cache_getter(self):
"""
Ensure that calling a decorated function uses
request_cache_getter if supplied.
"""
to_be_wrapped = Mock()
to_be_wrapped.side_effect = [1, 2, 3]
self.assertEqual(to_be_wrapped.call_count, 0)
def mock_wrapper(*args, **kwargs):
"""Simple wrapper to let us decorate our mock."""
return to_be_wrapped(*args, **kwargs)
request_cache_getter = lambda args, kwargs: RequestCache('test')
wrapped = request_cached(request_cache_getter=request_cache_getter)(mock_wrapper)
# This will be a miss, and make an underlying call.
result = wrapped(1)
self.assertEqual(result, 1)
self.assertEqual(to_be_wrapped.call_count, 1)
# This will be a miss, and make an underlying call.
result = wrapped(2)
self.assertEqual(result, 2)
self.assertEqual(to_be_wrapped.call_count, 2)
# These will be a hits, and not make an underlying call.
result = wrapped(1)
self.assertEqual(result, 1)
self.assertEqual(to_be_wrapped.call_count, 2)
# Ensure the appropriate request cache was used
self.assertFalse(RequestCache().data)
self.assertTrue(RequestCache('test').data)
def test_request_cached_with_arg_map_function(self):
"""
Ensure that calling a decorated function uses
arg_map_function to determined the cache key.
"""
to_be_wrapped = Mock()
to_be_wrapped.side_effect = [1, 2, 3]
self.assertEqual(to_be_wrapped.call_count, 0)
def mock_wrapper(*args, **kwargs):
"""Simple wrapper to let us decorate our mock."""
return to_be_wrapped(*args, **kwargs)
arg_map_function = lambda arg: unicode(arg == 1)
wrapped = request_cached(arg_map_function=arg_map_function)(mock_wrapper)
# This will be a miss, and make an underlying call.
result = wrapped(1)
self.assertEqual(result, 1)
self.assertEqual(to_be_wrapped.call_count, 1)
# This will be a miss, and make an underlying call.
result = wrapped(2)
self.assertEqual(result, 2)
self.assertEqual(to_be_wrapped.call_count, 2)
# These will be a hits, and not make an underlying call.
result = wrapped(1)
self.assertEqual(result, 1)
self.assertEqual(to_be_wrapped.call_count, 2)
result = wrapped(3)
self.assertEqual(result, 2)
self.assertEqual(to_be_wrapped.call_count, 2)

View File

@@ -0,0 +1,84 @@
"""Tests for request_utils module."""
import unittest
from django.conf import settings
from django.core.exceptions import SuspiciousOperation
from django.test.client import RequestFactory
from openedx.core.lib.request_utils import get_request_or_stub, course_id_from_url, safe_get_host
class RequestUtilTestCase(unittest.TestCase):
"""
Tests for request_utils module.
"""
def setUp(self):
super(RequestUtilTestCase, self).setUp()
self.old_site_name = settings.SITE_NAME
self.old_allowed_hosts = settings.ALLOWED_HOSTS
def tearDown(self):
super(RequestUtilTestCase, self).tearDown()
settings.SITE_NAME = self.old_site_name
settings.ALLOWED_HOSTS = self.old_allowed_hosts
def test_get_request_or_stub(self):
"""
Outside the context of the request, we should still get a request
that allows us to build an absolute URI.
"""
stub = get_request_or_stub()
expected_url = "http://{site_name}/foobar".format(site_name=settings.SITE_NAME)
self.assertEqual(stub.build_absolute_uri("foobar"), expected_url)
def test_safe_get_host(self):
""" Tests that the safe_get_host function returns the desired host """
settings.SITE_NAME = 'siteName.com'
factory = RequestFactory()
request = factory.request()
request.META['HTTP_HOST'] = 'www.userProvidedHost.com'
# If ALLOWED_HOSTS is not set properly, safe_get_host should return SITE_NAME
settings.ALLOWED_HOSTS = None
self.assertEqual(safe_get_host(request), "siteName.com")
settings.ALLOWED_HOSTS = ["*"]
self.assertEqual(safe_get_host(request), "siteName.com")
settings.ALLOWED_HOSTS = ["foo.com", "*"]
self.assertEqual(safe_get_host(request), "siteName.com")
# If ALLOWED_HOSTS is set properly, and the host is valid, we just return the user-provided host
settings.ALLOWED_HOSTS = [request.META['HTTP_HOST']]
self.assertEqual(safe_get_host(request), request.META['HTTP_HOST'])
# If ALLOWED_HOSTS is set properly but the host is invalid, we should get a SuspiciousOperation
settings.ALLOWED_HOSTS = ["the_valid_website.com"]
with self.assertRaises(SuspiciousOperation):
safe_get_host(request)
def test_course_id_from_url(self):
""" Test course_id_from_url(). """
self.assertIsNone(course_id_from_url('/login'))
self.assertIsNone(course_id_from_url('/course/edX/maths/2020'))
self.assertIsNone(course_id_from_url('/courses/edX/maths/'))
self.assertIsNone(course_id_from_url('/api/courses/v1/blocks/edX/maths/2020'))
self.assertIsNone(course_id_from_url('/api/courses/v1/blocks/course-v1:incidental+courseid+formatting'))
self.assertIsNone(course_id_from_url('/api/courses/v41/notcourses/course-v1:incidental+courseid+formatting'))
course_id = course_id_from_url('/courses/course-v1:edX+maths+2020')
self.assertCourseIdFieldsMatch(course_id=course_id, org="edX", course='maths', run='2020')
course_id = course_id_from_url('/courses/edX/maths/2020')
self.assertCourseIdFieldsMatch(course_id=course_id, org='edX', course='maths', run='2020')
course_id = course_id_from_url('/api/courses/v1/courses/course-v1:edX+maths+2020')
self.assertCourseIdFieldsMatch(course_id=course_id, org='edX', course='maths', run='2020')
course_id = course_id_from_url('/api/courses/v1/courses/edX/maths/2020')
self.assertCourseIdFieldsMatch(course_id=course_id, org='edX', course='maths', run='2020')
def assertCourseIdFieldsMatch(self, course_id, org, course, run):
""" Asserts that the passed-in course id matches the specified fields"""
self.assertEqual(course_id.org, org)
self.assertEqual(course_id.course, course)
self.assertEqual(course_id.run, run)