Remove use of read_cached_programs switch
The read_cached_programs switch was used to control the release of changes for reading programs exclusively from the cache. With those changes stable, the switch is no longer necessary. LEARNER-382
This commit is contained in:
@@ -7,7 +7,6 @@ import mock
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.cache import cache
|
||||
from django.test import TestCase
|
||||
from waffle.models import Switch
|
||||
|
||||
from openedx.core.djangoapps.catalog.cache import PROGRAM_CACHE_KEY_TPL, PROGRAM_UUIDS_CACHE_KEY
|
||||
from openedx.core.djangoapps.catalog.models import CatalogIntegration
|
||||
@@ -29,14 +28,9 @@ User = get_user_model() # pylint: disable=invalid-name
|
||||
|
||||
@skip_unless_lms
|
||||
@mock.patch(UTILS_MODULE + '.logger.warning')
|
||||
class TestGetCachedPrograms(CacheIsolationTestCase):
|
||||
class TestGetPrograms(CacheIsolationTestCase):
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(TestGetCachedPrograms, self).setUp()
|
||||
|
||||
Switch.objects.create(name='read_cached_programs', active=True)
|
||||
|
||||
def test_get_many(self, mock_warning):
|
||||
programs = ProgramFactory.create_batch(3)
|
||||
|
||||
@@ -120,130 +114,6 @@ class TestGetCachedPrograms(CacheIsolationTestCase):
|
||||
self.assertFalse(mock_warning.called)
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
@mock.patch(UTILS_MODULE + '.get_edx_api_data')
|
||||
class TestGetPrograms(CatalogIntegrationMixin, TestCase):
|
||||
"""Tests covering retrieval of programs from the catalog service."""
|
||||
def setUp(self):
|
||||
super(TestGetPrograms, self).setUp()
|
||||
|
||||
self.uuid = str(uuid.uuid4())
|
||||
self.types = ['Foo', 'Bar', 'FooBar']
|
||||
self.catalog_integration = self.create_catalog_integration(cache_ttl=1)
|
||||
|
||||
UserFactory(username=self.catalog_integration.service_username)
|
||||
|
||||
def assert_contract(self, call_args, program_uuid=None, types=None):
|
||||
"""Verify that API data retrieval utility is used correctly."""
|
||||
args, kwargs = call_args
|
||||
|
||||
for arg in (self.catalog_integration, 'programs'):
|
||||
self.assertIn(arg, args)
|
||||
|
||||
self.assertEqual(kwargs['resource_id'], program_uuid)
|
||||
|
||||
types_param = ','.join(types) if types and isinstance(types, list) else None
|
||||
cache_key = '{base}.programs{types}'.format(
|
||||
base=self.catalog_integration.CACHE_KEY,
|
||||
types='.' + types_param if types_param else ''
|
||||
)
|
||||
self.assertEqual(
|
||||
kwargs['cache_key'],
|
||||
cache_key if self.catalog_integration.is_cache_enabled else None
|
||||
)
|
||||
|
||||
self.assertEqual(kwargs['api']._store['base_url'], self.catalog_integration.internal_api_url) # pylint: disable=protected-access
|
||||
|
||||
querystring = {
|
||||
'exclude_utm': 1,
|
||||
'status': ('active', 'retired',),
|
||||
}
|
||||
if program_uuid:
|
||||
querystring['use_full_course_serializer'] = 1
|
||||
if types:
|
||||
querystring['types'] = types_param
|
||||
|
||||
self.assertEqual(kwargs['querystring'], querystring)
|
||||
|
||||
return args, kwargs
|
||||
|
||||
def test_get_programs(self, mock_get_edx_api_data):
|
||||
programs = ProgramFactory.create_batch(3)
|
||||
mock_get_edx_api_data.return_value = programs
|
||||
|
||||
data = get_programs()
|
||||
|
||||
self.assert_contract(mock_get_edx_api_data.call_args)
|
||||
self.assertEqual(data, programs)
|
||||
|
||||
def test_get_one_program(self, mock_get_edx_api_data):
|
||||
program = ProgramFactory()
|
||||
mock_get_edx_api_data.return_value = program
|
||||
|
||||
data = get_programs(uuid=self.uuid)
|
||||
|
||||
self.assert_contract(mock_get_edx_api_data.call_args, program_uuid=self.uuid)
|
||||
self.assertEqual(data, program)
|
||||
|
||||
def test_programs_unavailable(self, mock_get_edx_api_data):
|
||||
mock_get_edx_api_data.return_value = []
|
||||
|
||||
data = get_programs()
|
||||
|
||||
self.assert_contract(mock_get_edx_api_data.call_args)
|
||||
self.assertEqual(data, [])
|
||||
|
||||
def test_cache_disabled(self, mock_get_edx_api_data):
|
||||
self.catalog_integration = self.create_catalog_integration(cache_ttl=0)
|
||||
get_programs()
|
||||
self.assert_contract(mock_get_edx_api_data.call_args)
|
||||
|
||||
def test_config_missing(self, _mock_get_edx_api_data):
|
||||
"""
|
||||
Verify that no errors occur if this method is called when catalog config
|
||||
is missing.
|
||||
"""
|
||||
CatalogIntegration.objects.all().delete()
|
||||
|
||||
data = get_programs()
|
||||
self.assertEqual(data, [])
|
||||
|
||||
def test_service_user_missing(self, _mock_get_edx_api_data):
|
||||
"""
|
||||
Verify that no errors occur if this method is called when the catalog
|
||||
service user is missing.
|
||||
"""
|
||||
# Note: Deleting the service user would be ideal, but causes mysterious
|
||||
# errors on Jenkins.
|
||||
self.create_catalog_integration(service_username='nonexistent-user')
|
||||
|
||||
data = get_programs()
|
||||
self.assertEqual(data, [])
|
||||
|
||||
@mock.patch(UTILS_MODULE + '.get_programs')
|
||||
@mock.patch(UTILS_MODULE + '.get_program_types')
|
||||
def test_get_programs_with_type(self, mock_get_program_types, mock_get_programs, _mock_get_edx_api_data):
|
||||
"""Verify get_programs_with_type returns the expected list of programs."""
|
||||
programs_with_program_type = []
|
||||
programs = ProgramFactory.create_batch(2)
|
||||
program_types = []
|
||||
|
||||
for program in programs:
|
||||
program_type = ProgramTypeFactory(name=program['type'])
|
||||
program_types.append(program_type)
|
||||
|
||||
program_with_type = copy.deepcopy(program)
|
||||
program_with_type['type'] = program_type
|
||||
programs_with_program_type.append(program_with_type)
|
||||
|
||||
mock_get_programs.return_value = programs
|
||||
mock_get_program_types.return_value = program_types
|
||||
|
||||
actual = get_programs_with_type()
|
||||
|
||||
self.assertEqual(actual, programs_with_program_type)
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
@mock.patch(UTILS_MODULE + '.get_edx_api_data')
|
||||
class TestGetProgramTypes(CatalogIntegrationMixin, TestCase):
|
||||
|
||||
8
openedx/core/djangoapps/catalog/urls.py
Normal file
8
openedx/core/djangoapps/catalog/urls.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.conf.urls import url
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^management/cache_programs/$', views.cache_programs, name='cache_programs'),
|
||||
]
|
||||
@@ -2,7 +2,6 @@
|
||||
import copy
|
||||
import logging
|
||||
|
||||
import waffle
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.cache import cache
|
||||
@@ -39,62 +38,27 @@ def get_programs(uuid=None):
|
||||
list of dict, representing programs.
|
||||
dict, if a specific program is requested.
|
||||
"""
|
||||
if waffle.switch_is_active('read_cached_programs'):
|
||||
missing_details_msg_tpl = 'Details for program {uuid} are not cached.'
|
||||
missing_details_msg_tpl = 'Details for program {uuid} are not cached.'
|
||||
|
||||
if uuid:
|
||||
program = cache.get(PROGRAM_CACHE_KEY_TPL.format(uuid=uuid))
|
||||
if not program:
|
||||
logger.warning(missing_details_msg_tpl.format(uuid=uuid))
|
||||
|
||||
return program
|
||||
|
||||
uuids = cache.get(PROGRAM_UUIDS_CACHE_KEY, [])
|
||||
if not uuids:
|
||||
logger.warning('Program UUIDs are not cached.')
|
||||
|
||||
programs = cache.get_many([PROGRAM_CACHE_KEY_TPL.format(uuid=uuid) for uuid in uuids])
|
||||
programs = list(programs.values())
|
||||
|
||||
missing_uuids = set(uuids) - set(program['uuid'] for program in programs)
|
||||
for uuid in missing_uuids:
|
||||
if uuid:
|
||||
program = cache.get(PROGRAM_CACHE_KEY_TPL.format(uuid=uuid))
|
||||
if not program:
|
||||
logger.warning(missing_details_msg_tpl.format(uuid=uuid))
|
||||
|
||||
return programs
|
||||
else:
|
||||
# Old implementation which may request programs in-process. To be removed
|
||||
# as part of LEARNER-382.
|
||||
catalog_integration = CatalogIntegration.current()
|
||||
if catalog_integration.enabled:
|
||||
try:
|
||||
user = User.objects.get(username=catalog_integration.service_username)
|
||||
except User.DoesNotExist:
|
||||
return []
|
||||
return program
|
||||
|
||||
api = create_catalog_api_client(user, catalog_integration)
|
||||
uuids = cache.get(PROGRAM_UUIDS_CACHE_KEY, [])
|
||||
if not uuids:
|
||||
logger.warning('Program UUIDs are not cached.')
|
||||
|
||||
cache_key = '{base}.programs'.format(
|
||||
base=catalog_integration.CACHE_KEY
|
||||
)
|
||||
programs = cache.get_many([PROGRAM_CACHE_KEY_TPL.format(uuid=uuid) for uuid in uuids])
|
||||
programs = list(programs.values())
|
||||
|
||||
querystring = {
|
||||
'exclude_utm': 1,
|
||||
'status': ('active', 'retired',),
|
||||
}
|
||||
missing_uuids = set(uuids) - set(program['uuid'] for program in programs)
|
||||
for uuid in missing_uuids:
|
||||
logger.warning(missing_details_msg_tpl.format(uuid=uuid))
|
||||
|
||||
if uuid:
|
||||
querystring['use_full_course_serializer'] = 1
|
||||
|
||||
return get_edx_api_data(
|
||||
catalog_integration,
|
||||
'programs',
|
||||
api=api,
|
||||
resource_id=uuid,
|
||||
cache_key=cache_key if catalog_integration.is_cache_enabled else None,
|
||||
querystring=querystring,
|
||||
)
|
||||
else:
|
||||
return []
|
||||
return programs
|
||||
|
||||
|
||||
def get_program_types(name=None):
|
||||
|
||||
21
openedx/core/djangoapps/catalog/views.py
Normal file
21
openedx/core/djangoapps/catalog/views.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.views.decorators.http import require_GET
|
||||
|
||||
|
||||
@require_GET
|
||||
def cache_programs(request):
|
||||
"""
|
||||
Call the cache_programs management command.
|
||||
|
||||
This view is intended for use in testing contexts where the LMS can only be
|
||||
reached over HTTP (e.g., Selenium-based browser tests). The discovery service
|
||||
API the management command attempts to call should be stubbed out first.
|
||||
"""
|
||||
if settings.FEATURES.get('EXPOSE_CACHE_PROGRAMS_ENDPOINT'):
|
||||
call_command('cache_programs')
|
||||
|
||||
return HttpResponse('Programs cached.')
|
||||
|
||||
raise Http404
|
||||
Reference in New Issue
Block a user