def test_no_response_doesnt_get_cached(self): """ Response doesn't get cached when empty. """ uuid = str(self.enterprise_customer.uuid) api_resource_name = 'enterprise-customer' cache_key = get_cache_key( resource=api_resource_name, querystring={}, traverse_pagination=False, resource_id=uuid, ) cached_enterprise_api_response = cache.get(cache_key) assert cached_enterprise_api_response is None self.mock_empty_response('enterprise-customer-courses', uuid) client = enterprise_api.EnterpriseApiClient(self.user) response = client._load_data( # pylint: disable=protected-access resource=api_resource_name, detail_resource='courses', resource_id=uuid, ) assert not response # The empty response is not cached. cached_api_response = cache.get(cache_key) assert not cached_api_response
def test_get_content_metadata_with_enterprise_catalogs(self): """ Verify that the client method `get_content_metadata` works as expected. """ EnterpriseCustomerCatalogFactory( enterprise_customer=self.enterprise_customer, ) uuid = str(self.enterprise_customer.uuid) course_run_ids = ['course-v1:edX+DemoX+Demo_Course_1', 'course-v1:edX+DemoX+Demo_Course_2'] self.mock_ent_courses_api_with_pagination( enterprise_uuid=uuid, course_run_ids=course_run_ids ) enterprise_catalog_uuid = str(self.enterprise_customer.enterprise_customer_catalogs.first().uuid) self.mock_enterprise_customer_catalogs(enterprise_catalog_uuid) api_resource_name = 'enterprise-customer' cache_key = get_cache_key( resource=api_resource_name, querystring={}, resource_id=uuid, traverse_pagination=False, ) cached_enterprise_api_response = cache.get(cache_key) self.assertIsNone(cached_enterprise_api_response) # Verify that by default enterprise client fetches all the course runs associated with the catalog. client = enterprise_api.EnterpriseApiClient(self.user) course_runs = client.get_content_metadata(self.enterprise_customer) assert len(course_runs) == 5
def test_get_content_metadata(self): """ Verify that the client method `get_content_metadata` works as expected. """ uuid = str(self.enterprise_customer.uuid) course_run_ids = ['course-v1:edX+DemoX+Demo_Course_1', 'course-v1:edX+DemoX+Demo_Course_2'] self.mock_ent_courses_api_with_pagination( enterprise_uuid=uuid, course_run_ids=course_run_ids ) api_resource_name = 'enterprise-customer' cache_key = get_cache_key( resource=api_resource_name, querystring={}, resource_id=uuid, traverse_pagination=False, ) cached_enterprise_api_response = cache.get(cache_key) self.assertIsNone(cached_enterprise_api_response) # Verify that by default enterprise client fetches all the course runs associated with the catalog. client = enterprise_api.EnterpriseApiClient(self.user) api_response = client.get_content_metadata(self.enterprise_customer) self._assert_enterprise_courses_api_response( ['course-v1:edX+DemoX+Demo_Course_1', 'course-v1:edX+DemoX+Demo_Course_2'], api_response, expected_count=2 ) # Verify the enterprise API was hit twice self._assert_num_requests(2)
def test_no_response_doesnt_get_cached(self): """ Response doesn't get cached when empty. """ EnterpriseCustomerCatalogFactory( enterprise_customer=self.enterprise_customer, ) enterprise_catalog_uuid = str( self.enterprise_customer.enterprise_customer_catalogs.first().uuid) api_resource_name = 'enterprise_catalogs' cache_key = get_cache_key( resource=api_resource_name, querystring={}, traverse_pagination=False, resource_id=enterprise_catalog_uuid, ) cached_enterprise_api_response = cache.get(cache_key) assert cached_enterprise_api_response is None self.mock_empty_response('enterprise-catalogs-detail', enterprise_catalog_uuid) client = enterprise_api.EnterpriseApiClient(self.user) response = client._load_data( # pylint: disable=protected-access resource=api_resource_name, resource_id=enterprise_catalog_uuid, ) assert not response # The empty response is not cached. cached_api_response = cache.get(cache_key) assert not cached_api_response
def test_get_content_metadata_with_enterprise_catalog_set_to_none(self): """ Verify that the client method `get_content_metadata` returns courses from associated EnterpriseCustomerCatalog objects only if EnterpriseCustomer.catalog is set to None. """ EnterpriseCustomerCatalogFactory( enterprise_customer=self.enterprise_customer, ) enterprise_catalog_uuid = str( self.enterprise_customer.enterprise_customer_catalogs.first().uuid) self.mock_enterprise_customer_catalogs(enterprise_catalog_uuid) api_resource_name = 'enterprise-customer' cache_key = get_cache_key( resource=api_resource_name, querystring={}, resource_id=str(self.enterprise_customer.uuid), traverse_pagination=False, ) cached_enterprise_api_response = cache.get(cache_key) self.assertIsNone(cached_enterprise_api_response) # Verify that by default enterprise client fetches all the course runs associated with the enterprise catalog. client = enterprise_api.EnterpriseApiClient(self.user) course_runs = client.get_content_metadata(self.enterprise_customer) assert len(course_runs) == 3
def get_course_duration(self, obj): """ Get course's duration as a timedelta. Arguments: obj (CourseOverview): CourseOverview object Returns: (timedelta): Duration of a course. """ course_run = None duration = None site = self.context.get('site') if site: cache_key = get_cache_key(course_id=obj.id, site=site) cached_response = TieredCache.get_cached_response(cache_key) if cached_response.is_found: course_run = cached_response.value else: try: _, course_run = CourseCatalogApiServiceClient(site).get_course_and_course_run(str(obj.id)) TieredCache.set_all_tiers(cache_key, course_run, CACHE_TIMEOUT) except ImproperlyConfigured: LOGGER.warning('CourseCatalogApiServiceClient is improperly configured.') if course_run and course_run.get('max_effort') and course_run.get('weeks_to_complete'): duration = '{effort} hours per week for {weeks} weeks.'.format( effort=course_run['max_effort'], weeks=course_run['weeks_to_complete'] ) return duration or ''
def get_catalog_results(self, content_filter_query, query_params=None, traverse_pagination=False): """ Return results from the cache or discovery service's search/all endpoint. Arguments: content_filter_query (dict): query parameters used to filter catalog results. query_params (dict): query parameters used to paginate results. traverse_pagination (bool): True to return all results, False to return the paginated response. Defaults to False. Returns: dict: Paginated response or all the records. """ query_params = query_params or {} try: cache_key = utils.get_cache_key( service='discovery', endpoint=self.SEARCH_ALL_ENDPOINT, query=content_filter_query, traverse_pagination=traverse_pagination, **query_params) response = cache.get(cache_key) if not response: LOGGER.info( 'ENT-2390-1 | Calling discovery service for search/all/ ' 'data with content_filter_query %s and query_params %s', content_filter_query, query_params, ) # Response is not cached, so make a call. response = self.get_catalog_results_from_discovery( content_filter_query, query_params, traverse_pagination) response_as_string = pickle.dumps(response) LOGGER.info( 'ENT-2489 | Response from content_filter_query %s is %d bytes long.', content_filter_query, len(response_as_string)) cache.set(cache_key, response, settings.ENTERPRISE_API_CACHE_TIMEOUT) else: LOGGER.info( 'ENT-2390-2 | Got search/all/ data from the cache with ' 'content_filter_query %s and query_params %s', content_filter_query, query_params, ) except Exception as ex: # pylint: disable=broad-except LOGGER.exception( 'Attempted to call course-discovery search/all/ endpoint with the following parameters: ' 'content_filter_query: %s, query_params: %s, traverse_pagination: %s. ' 'Failed to retrieve data from the catalog API. content -- [%s]', content_filter_query, query_params, traverse_pagination, getattr(ex, 'content', '')) # We need to bubble up failures when we encounter them instead of masking them! raise ex return response
def test_skip_request_if_response_cached(self): """ We skip the request portion of the API's logic if the response is already cached. """ cache_key = get_cache_key( resource='resource', querystring={}, traverse_pagination=False, resource_id=None, ) cache_value = {'fake': 'response'} cache.set(cache_key, cache_value, settings.ENTERPRISE_API_CACHE_TIMEOUT) client = enterprise_api.EnterpriseApiClient(self.user) response = client._load_data('resource') # pylint: disable=protected-access assert response == cache_value
def _load_data( self, resource, detail_resource=None, resource_id=None, querystring=None, traverse_pagination=False, default=DEFAULT_VALUE_SAFEGUARD, ): """ Loads a response from a call to one of the Enterprise endpoints. :param resource: The endpoint resource name. :param detail_resource: The sub-resource to append to the path. :param resource_id: The resource ID for the specific detail to get from the endpoint. :param querystring: Optional query string parameters. :param traverse_pagination: Whether to traverse pagination or return paginated response. :param default: The default value to return in case of no response content. :return: Data returned by the API. """ default_val = default if default != self.DEFAULT_VALUE_SAFEGUARD else {} querystring = querystring if querystring else {} cache_key = utils.get_cache_key( resource=resource, querystring=querystring, traverse_pagination=traverse_pagination, resource_id=resource_id) response = cache.get(cache_key) if not response: # Response is not cached, so make a call. endpoint = getattr(self.client, resource)(resource_id) endpoint = getattr( endpoint, detail_resource) if detail_resource else endpoint response = endpoint.get(**querystring) if traverse_pagination: results = utils.traverse_pagination(response, endpoint) response = { 'count': len(results), 'next': 'None', 'previous': 'None', 'results': results, } if response: # Now that we've got a response, cache it. cache.set(cache_key, response, settings.ENTERPRISE_API_CACHE_TIMEOUT) return response or default_val