def test_api_client_not_provided(self): """Verify that an API client doesn't need to be provided.""" ClientFactory(name=CredentialsApiConfig.OAUTH2_CLIENT_NAME, client_type=CONFIDENTIAL) credentials_api_config = self.create_credentials_config() with mock.patch('openedx.core.lib.edx_api_utils.EdxRestApiClient.__init__') as mock_init: get_edx_api_data(credentials_api_config, self.user, 'credentials') self.assertTrue(mock_init.called)
def test_client_passed(self): """ Verify that when API client is passed edx_rest_api_client is not used. """ program_config = self.create_programs_config() api = ecommerce_api_client(self.user) with mock.patch('openedx.core.lib.edx_api_utils.EdxRestApiClient.__init__') as mock_init: get_edx_api_data(program_config, self.user, 'orders', api=api) self.assertFalse(mock_init.called)
def test_get_edx_api_data_cache(self): """Verify that when enabled, the cache is used.""" program_config = self.create_programs_config(cache_ttl=1) self.mock_programs_api() # Warm up the cache. get_edx_api_data(program_config, self.user, 'programs', cache_key='test.key') # Hit the cache. get_edx_api_data(program_config, self.user, 'programs', cache_key='test.key') # Verify only one request was made. self.assertEqual(len(httpretty.httpretty.latest_requests), 1)
def get_course_uuid_for_course(course_run_key): """ Retrieve the Course UUID for a given course key Arguments: course_run_key (CourseKey): A Key for a Course run that will be pulled apart to get just the information required for a Course (e.g. org+course) Returns: UUID: Course UUID and None if it was not retrieved. """ user, catalog_integration = check_catalog_integration_and_get_user(error_message_field='Course UUID') if user: api = create_catalog_api_client(user) run_cache_key = '{base}.course_run.{course_run_key}'.format( base=catalog_integration.CACHE_KEY, course_run_key=course_run_key ) course_run_data = get_edx_api_data( catalog_integration, 'course_runs', resource_id=unicode(course_run_key), api=api, cache_key=run_cache_key if catalog_integration.is_cache_enabled else None, long_term_cache=True, many=False, ) course_key_str = course_run_data.get('course', None) if course_key_str: run_cache_key = '{base}.course.{course_key}'.format( base=catalog_integration.CACHE_KEY, course_key=course_key_str ) data = get_edx_api_data( catalog_integration, 'courses', resource_id=course_key_str, api=api, cache_key=run_cache_key if catalog_integration.is_cache_enabled else None, long_term_cache=True, many=False, ) uuid_str = data.get('uuid', None) if uuid_str: return uuid.UUID(uuid_str) return None
def get_course_uuid_for_course(course_run_key): """ Retrieve the Course UUID for a given course key Arguments: course_run_key (CourseKey): A Key for a Course run that will be pulled apart to get just the information required for a Course (e.g. org+course) Returns: UUID: Course UUID and None if it was not retrieved. """ user, catalog_integration = check_catalog_integration_and_get_user( error_message_field='Course UUID') if user: api = create_catalog_api_client(user) run_cache_key = '{base}.course_run.{course_run_key}'.format( base=catalog_integration.CACHE_KEY, course_run_key=course_run_key) course_run_data = get_edx_api_data( catalog_integration, 'course_runs', resource_id=text_type(course_run_key), api=api, cache_key=run_cache_key if catalog_integration.is_cache_enabled else None, long_term_cache=True, many=False, ) course_key_str = course_run_data.get('course', None) if course_key_str: run_cache_key = '{base}.course.{course_key}'.format( base=catalog_integration.CACHE_KEY, course_key=course_key_str) data = get_edx_api_data( catalog_integration, 'courses', resource_id=course_key_str, api=api, cache_key=run_cache_key if catalog_integration.is_cache_enabled else None, long_term_cache=True, many=False, ) uuid_str = data.get('uuid', None) if uuid_str: return uuid.UUID(uuid_str) return None
def get_program_types(name=None): """Retrieve program types from the catalog service. Keyword Arguments: name (string): Name identifying a specific program. Returns: list of dict, representing program types. dict, if a specific program type is requested. """ catalog_integration = CatalogIntegration.current() if catalog_integration.enabled: try: user = catalog_integration.get_service_user() except ObjectDoesNotExist: return [] api = create_catalog_api_client(user) cache_key = '{base}.program_types'.format( base=catalog_integration.CACHE_KEY) data = get_edx_api_data(catalog_integration, 'program_types', api=api, cache_key=cache_key if catalog_integration.is_cache_enabled else None) # Filter by name if a name was provided if name: data = next(program_type for program_type in data if program_type['name'] == name) return data else: return []
def test_get_paginated_data(self): """Verify that paginated data can be retrieved.""" program_config = self.create_programs_config() expected_collection = ['some', 'test', 'data'] url = ProgramsApiConfig.current().internal_api_url.strip('/') + '/programs/?page={}' responses = [] for page, record in enumerate(expected_collection, start=1): data = { 'next': url.format(page + 1) if page < len(expected_collection) else None, 'results': [record], } body = json.dumps(data) responses.append( httpretty.Response(body=body, content_type='application/json') ) self._mock_programs_api(responses) actual_collection = get_edx_api_data(program_config, self.user, 'programs') self.assertEqual(actual_collection, expected_collection) self._assert_num_requests(len(expected_collection))
def _load_data(self, resource, default=DEFAULT_VALUE_SAFEGUARD, **kwargs): """ Load data from API client. Arguments: resource(string): type of resource to load default(any): value to return if API query returned empty result. Sensible values: [], {}, None etc. Returns: dict: Deserialized response from Course Catalog API """ default_val = default if default != self.DEFAULT_VALUE_SAFEGUARD else {} try: return get_edx_api_data( api_config=CatalogIntegration.current(), resource=resource, api=self.client, **kwargs ) or default_val except (SlumberBaseException, ConnectionError, Timeout) as exc: LOGGER.exception( 'Failed to load data from resource [%s] with kwargs [%s] due to: [%s]', resource, kwargs, str(exc) ) return default_val
def get_course_run(course_key, user): """Get a course run's data from the course catalog service. Arguments: course_key (CourseKey): Course key object identifying the run whose data we want. user (User): The user to authenticate as when making requests to the catalog service. Returns: dict, empty if no data could be retrieved. """ catalog_integration = CatalogIntegration.current() if catalog_integration.enabled: api = create_catalog_api_client(user, catalog_integration) data = get_edx_api_data( catalog_integration, user, 'course_runs', resource_id=unicode(course_key), cache_key=catalog_integration.CACHE_KEY if catalog_integration.is_cache_enabled else None, api=api, ) return data if data else {} else: return {}
def get_program_types(): """Retrieve all program types from the catalog service. Returns: list of dict, representing program types. """ catalog_integration = CatalogIntegration.current() if catalog_integration.enabled: try: user = User.objects.get(username=catalog_integration.service_username) except User.DoesNotExist: return [] api = create_catalog_api_client(user, catalog_integration) cache_key = '{base}.program_types'.format(base=catalog_integration.CACHE_KEY) return get_edx_api_data( catalog_integration, user, 'program_types', cache_key=cache_key if catalog_integration.is_cache_enabled else None, api=api ) else: return []
def get_credentials(user, program_uuid=None): """ Given a user, get credentials earned from the credentials service. Arguments: user (User): The user to authenticate as when requesting credentials. Keyword Arguments: program_uuid (str): UUID of the program whose credential to retrieve. Returns: list of dict, representing credentials returned by the Credentials service. """ credential_configuration = CredentialsApiConfig.current() querystring = {'username': user.username, 'status': 'awarded'} if program_uuid: querystring['program_uuid'] = program_uuid # Bypass caching for staff users, who may be generating credentials and # want to see them displayed immediately. use_cache = credential_configuration.is_cache_enabled and not user.is_staff cache_key = credential_configuration.CACHE_KEY + '.' + user.username if use_cache else None return get_edx_api_data( credential_configuration, user, 'credentials', querystring=querystring, cache_key=cache_key )
def get_programs(user, program_id=None): """Given a user, get programs from the Programs service. Returned value is cached depending on user permissions. Staff users making requests against Programs will receive unpublished programs, while regular users will only receive published programs. Arguments: user (User): The user to authenticate as when requesting programs. Keyword Arguments: program_id (int): Identifies a specific program for which to retrieve data. Returns: list of dict, representing programs returned by the Programs service. """ programs_config = ProgramsApiConfig.current() # Bypass caching for staff users, who may be creating Programs and want # to see them displayed immediately. cache_key = programs_config.CACHE_KEY if programs_config.is_cache_enabled and not user.is_staff else None return get_edx_api_data(programs_config, user, 'programs', resource_id=program_id, cache_key=cache_key)
def get_course_run(course_key, user): """Get a course run's data from the course catalog service. Arguments: course_key (CourseKey): Course key object identifying the run whose data we want. user (User): The user to authenticate as when making requests to the catalog service. Returns: dict, empty if no data could be retrieved. """ catalog_integration = CatalogIntegration.current() if catalog_integration.enabled: api = create_catalog_api_client(user, catalog_integration) data = get_edx_api_data( catalog_integration, user, 'course_runs', resource_id=unicode(course_key), cache_key=catalog_integration.CACHE_KEY if catalog_integration.is_cache_enabled else None, api=api, querystring={'exclude_utm': 1}, ) return data if data else {} else: return {}
def test_get_paginated_data_do_not_traverse_pagination(self): """ Verify that pagination is not traversed if traverse_pagination=False is passed as argument. """ catalog_integration = self.create_catalog_integration() api = create_catalog_api_client(self.user) url = CatalogIntegration.current().get_internal_api_url().strip('/') + '/programs/?page={}' responses = [ { 'next': url.format(2), 'results': ['some'], }, { 'next': url.format(None), 'results': ['test'], }, ] expected_response = responses[0] self._mock_catalog_api( [httpretty.Response(body=json.dumps(body), content_type='application/json') for body in responses] ) actual_collection = get_edx_api_data(catalog_integration, 'programs', api=api, traverse_pagination=False) self.assertEqual(actual_collection, expected_response) self._assert_num_requests(1)
def get_course_runs_for_course(course_uuid): catalog_integration = CatalogIntegration.current() if catalog_integration.is_enabled(): try: user = catalog_integration.get_service_user() except ObjectDoesNotExist: logger.error( 'Catalog service user with username [%s] does not exist. Course runs will not be retrieved.', catalog_integration.service_username, ) return [] api = create_catalog_api_client(user) cache_key = '{base}.course.{uuid}.course_runs'.format( base=catalog_integration.CACHE_KEY, uuid=course_uuid ) data = get_edx_api_data( catalog_integration, 'courses', resource_id=course_uuid, api=api, cache_key=cache_key if catalog_integration.is_cache_enabled else None, long_term_cache=True ) return data.get('course_runs', []) else: return []
def get_programs(user, program_id=None): """Given a user, get programs from the Programs service. Returned value is cached depending on user permissions. Staff users making requests against Programs will receive unpublished programs, while regular users will only receive published programs. Arguments: user (User): The user to authenticate as when requesting programs. Keyword Arguments: program_id (int): Identifies a specific program for which to retrieve data. Returns: list of dict, representing programs returned by the Programs service. dict, if a specific program is requested. """ programs_config = ProgramsApiConfig.current() # Bypass caching for staff users, who may be creating Programs and want # to see them displayed immediately. cache_key = programs_config.CACHE_KEY if programs_config.is_cache_enabled and not user.is_staff else None programs = get_edx_api_data(programs_config, user, 'programs', resource_id=program_id, cache_key=cache_key) # Mix in munged MicroMasters data from the catalog. if not program_id: programs += [ munge_catalog_program(micromaster) for micromaster in get_catalog_programs(user, type='MicroMasters') ] return programs
def get_program_types(name=None): """Retrieve program types from the catalog service. Keyword Arguments: name (string): Name identifying a specific program. Returns: list of dict, representing program types. dict, if a specific program type is requested. """ user, catalog_integration = check_catalog_integration_and_get_user(error_message_field='Program types') if user: api = create_catalog_api_client(user) cache_key = '{base}.program_types'.format(base=catalog_integration.CACHE_KEY) data = get_edx_api_data(catalog_integration, 'program_types', api=api, cache_key=cache_key if catalog_integration.is_cache_enabled else None) # Filter by name if a name was provided if name: data = next(program_type for program_type in data if program_type['name'] == name) return data else: return []
def test_get_paginated_data(self): """Verify that paginated data can be retrieved.""" catalog_integration = self.create_catalog_integration() api = create_catalog_api_client(self.user) expected_collection = ['some', 'test', 'data'] url = CatalogIntegration.current().get_internal_api_url().strip( '/') + '/programs/?page={}' responses = [] for page, record in enumerate(expected_collection, start=1): data = { 'next': url.format(page + 1) if page < len(expected_collection) else None, 'results': [record], } body = json.dumps(data) responses.append( httpretty.Response(body=body, content_type='application/json')) self._mock_catalog_api(responses) actual_collection = get_edx_api_data(catalog_integration, 'programs', api=api) assert actual_collection == expected_collection self._assert_num_requests(len(expected_collection))
def get_programs(uuid=None, types=None): # pylint: disable=redefined-builtin """Retrieve marketable programs from the catalog service. Keyword Arguments: uuid (string): UUID identifying a specific program. types (list of string): List of program type names used to filter programs by type (e.g., ["MicroMasters"] will only return MicroMasters programs, ["MicroMasters", "XSeries"] will return MicroMasters and XSeries programs). Returns: list of dict, representing programs. dict, if a specific program is requested. """ catalog_integration = CatalogIntegration.current() if catalog_integration.enabled: try: user = User.objects.get( username=catalog_integration.service_username) except User.DoesNotExist: return [] api = create_catalog_api_client(user, catalog_integration) types_param = ','.join(types) if types else None cache_key = '{base}.programs{types}'.format( base=catalog_integration.CACHE_KEY, types='.' + types_param if types_param else '') querystring = { 'exclude_utm': 1, } # TODO ECOM-7650: Remove this after https://github.com/edx/course-discovery/pull/805 is merged and released. if waffle.switch_is_active( 'display_retired_programs_on_learner_dashboard'): querystring['status'] = ( 'active', 'retired', ) else: querystring['marketable'] = 1 if uuid: querystring['use_full_course_serializer'] = 1 if types_param: querystring['types'] = types_param return get_edx_api_data( catalog_integration, user, 'programs', resource_id=uuid, cache_key=cache_key if catalog_integration.is_cache_enabled else None, api=api, querystring=querystring, ) else: return []
def get_credentials(user, program_uuid=None): """ Given a user, get credentials earned from the credentials service. Arguments: user (User): The user to authenticate as when requesting credentials. Keyword Arguments: program_uuid (str): UUID of the program whose credential to retrieve. Returns: list of dict, representing credentials returned by the Credentials service. """ credential_configuration = CredentialsApiConfig.current() querystring = {'username': user.username, 'status': 'awarded'} if program_uuid: querystring['program_uuid'] = program_uuid # Bypass caching for staff users, who may be generating credentials and # want to see them displayed immediately. use_cache = credential_configuration.is_cache_enabled and not user.is_staff cache_key = credential_configuration.CACHE_KEY + '.' + user.username if use_cache else None api = get_credentials_api_client(user) return get_edx_api_data( credential_configuration, 'credentials', api=api, querystring=querystring, cache_key=cache_key )
def get_course_run_details(course_run_key, fields): """ Retrieve information about the course run with the given id Arguments: course_run_key: key for the course_run about which we are retrieving information Returns: dict with language, start date, end date, and max_effort details about specified course run """ catalog_integration = CatalogIntegration.current() course_run_details = dict() if catalog_integration.enabled: try: user = catalog_integration.get_service_user() except ObjectDoesNotExist: msg = 'Catalog service user {} does not exist. Data for course_run {} will not be retrieved'.format( catalog_integration.service_username, course_run_key ) logger.error(msg) return course_run_details api = create_catalog_api_client(user) cache_key = '{base}.course_runs'.format(base=catalog_integration.CACHE_KEY) course_run_details = get_edx_api_data(catalog_integration, 'course_runs', api, resource_id=course_run_key, cache_key=cache_key, many=False, traverse_pagination=False, fields=fields) else: msg = 'Unable to retrieve details about course_run {} because Catalog Integration is not enabled'.format( course_run_key ) logger.error(msg) return course_run_details
def test_get_specific_resource_with_falsey_id(self): """ Verify that a specific resource can be retrieved, and pagination parsing is not attempted, if the ID passed is falsey (e.g., 0). The expected resource contains a "results" key, as a paginatable item would have, so if the function looks for falsey values in the resource_id field, rather than specifically None, the function will return the value of that "results" key. """ catalog_integration = self.create_catalog_integration() api = create_catalog_api_client(self.user) resource_id = 0 url = '{api_root}/programs/{resource_id}/'.format( api_root=CatalogIntegration.current().get_internal_api_url().strip('/'), resource_id=resource_id, ) expected_resource = {'key': 'value', 'results': []} self._mock_catalog_api( [httpretty.Response(body=json.dumps(expected_resource), content_type='application/json')], url=url ) actual_resource = get_edx_api_data(catalog_integration, 'programs', api=api, resource_id=resource_id) self.assertEqual(actual_resource, expected_resource) self._assert_num_requests(1)
def get_course_run(course_key, user): """Get a course run's data from the course catalog service. Arguments: course_key (CourseKey): Course key object identifying the run whose data we want. user (User): The user to authenticate as when making requests to the catalog service. Returns: dict, empty if no data could be retrieved. """ catalog_integration = CatalogIntegration.current() if catalog_integration.enabled: scopes = ['email', 'profile'] expires_in = settings.OAUTH_ID_TOKEN_EXPIRATION jwt = JwtBuilder(user).build_token(scopes, expires_in) api = EdxRestApiClient(catalog_integration.internal_api_url, jwt=jwt) data = get_edx_api_data( catalog_integration, user, 'course_runs', resource_id=unicode(course_key), cache_key=catalog_integration.CACHE_KEY if catalog_integration.is_cache_enabled else None, api=api, ) return data if data else {} else: return {}
def test_get_paginated_data_do_not_traverse_pagination(self): """ Verify that pagination is not traversed if traverse_pagination=False is passed as argument. """ catalog_integration = self.create_catalog_integration() api = create_catalog_api_client(self.user) url = CatalogIntegration.current().get_internal_api_url().strip( '/') + '/programs/?page={}' responses = [ { 'next': url.format(2), 'results': ['some'], }, { 'next': url.format(None), 'results': ['test'], }, ] expected_response = responses[0] self._mock_catalog_api([ httpretty.Response(body=json.dumps(body), content_type='application/json') for body in responses ]) actual_collection = get_edx_api_data(catalog_integration, 'programs', api=api, traverse_pagination=False) assert actual_collection == expected_response self._assert_num_requests(1)
def get_user_orders(user): """Given a user, get the detail of all the orders from the Ecommerce service. Args: user (User): The user to authenticate as when requesting ecommerce. Returns: list of dict, representing orders returned by the Ecommerce service. """ user_orders = [] commerce_configuration = CommerceConfiguration.current() user_query = {'username': user.username} use_cache = commerce_configuration.is_cache_enabled cache_key = commerce_configuration.CACHE_KEY + '.' + str(user.id) if use_cache else None api = ecommerce_api_client(user) commerce_user_orders = get_edx_api_data( commerce_configuration, 'orders', api=api, querystring=user_query, cache_key=cache_key ) log.info('----------------------------------------Orders------------------------------------%s', commerce_user_orders) for order in commerce_user_orders: if order['status'].lower() == 'complete': date_placed = datetime.strptime(order['date_placed'], "%Y-%m-%dT%H:%M:%SZ") order_data = { 'number': order['number'], 'price': order['total_excl_tax'], 'order_date': strftime_localized(date_placed, 'SHORT_DATE'), 'receipt_url': EcommerceService().get_receipt_page_url(order['number']), 'lines': order['lines'], } user_orders.append(order_data) return user_orders
def get_program_types(name=None): """Retrieve program types from the catalog service. Keyword Arguments: name (string): Name identifying a specific program. Returns: list of dict, representing program types. dict, if a specific program type is requested. """ catalog_integration = CatalogIntegration.current() if catalog_integration.enabled: try: user = catalog_integration.get_service_user() except ObjectDoesNotExist: return [] api = create_catalog_api_client(user) cache_key = '{base}.program_types'.format(base=catalog_integration.CACHE_KEY) data = get_edx_api_data(catalog_integration, 'program_types', api=api, cache_key=cache_key if catalog_integration.is_cache_enabled else None) # Filter by name if a name was provided if name: data = next(program_type for program_type in data if program_type['name'] == name) return data else: return []
def get_programs(user, program_id=None): """Given a user, get programs from the Programs service. Returned value is cached depending on user permissions. Staff users making requests against Programs will receive unpublished programs, while regular users will only receive published programs. Arguments: user (User): The user to authenticate as when requesting programs. Keyword Arguments: program_id (int): Identifies a specific program for which to retrieve data. Returns: list of dict, representing programs returned by the Programs service. """ programs_config = ProgramsApiConfig.current() # Bypass caching for staff users, who may be creating Programs and want # to see them displayed immediately. cache_key = programs_config.CACHE_KEY if programs_config.is_cache_enabled and not user.is_staff else None data = get_edx_api_data(programs_config, user, 'programs', resource_id=program_id, cache_key=cache_key) # TODO: Temporary, to be removed once category names are cased for display. ECOM-5018. if data and program_id: data['category'] = data['category'].lower() else: for program in data: program['category'] = program['category'].lower() return data
def get_course_runs(): """ Retrieve all the course runs from the catalog service. Returns: list of dict with each record representing a course run. """ catalog_integration = CatalogIntegration.current() course_runs = [] if catalog_integration.enabled: try: user = catalog_integration.get_service_user() except ObjectDoesNotExist: logger.error( 'Catalog service user with username [%s] does not exist. Course runs will not be retrieved.', catalog_integration.service_username, ) return course_runs api = create_catalog_api_client(user) querystring = { 'page_size': catalog_integration.page_size, 'exclude_utm': 1, } course_runs = get_edx_api_data(catalog_integration, 'course_runs', api=api, querystring=querystring) return course_runs
def get_course_runs_for_course(course_uuid): catalog_integration = CatalogIntegration.current() if catalog_integration.is_enabled(): try: user = catalog_integration.get_service_user() except ObjectDoesNotExist: logger.error( 'Catalog service user with username [%s] does not exist. Course runs will not be retrieved.', catalog_integration.service_username, ) return [] api = create_catalog_api_client(user) cache_key = '{base}.course.{uuid}.course_runs'.format( base=catalog_integration.CACHE_KEY, uuid=course_uuid) data = get_edx_api_data( catalog_integration, 'courses', resource_id=course_uuid, api=api, cache_key=cache_key if catalog_integration.is_cache_enabled else None, long_term_cache=True, ) return data.get('course_runs', []) else: return []
def get_course_run_details(course_run_key, fields): """ Retrieve information about the course run with the given id Arguments: course_run_key: key for the course_run about which we are retrieving information Returns: dict with language, start date, end date, and max_effort details about specified course run """ course_run_details = dict() user, catalog_integration = check_catalog_integration_and_get_user( error_message_field='Data for course_run {}'.format(course_run_key)) if user: api = create_catalog_api_client(user) cache_key = '{base}.course_runs'.format( base=catalog_integration.CACHE_KEY) course_run_details = get_edx_api_data(catalog_integration, 'course_runs', api, resource_id=course_run_key, cache_key=cache_key, many=False, traverse_pagination=False, fields=fields) return course_run_details
def get_user_orders(user): """Given a user, get the detail of all the orders from the Ecommerce service. Args: user (User): The user to authenticate as when requesting ecommerce. Returns: list of dict, representing orders returned by the Ecommerce service. """ no_data = [] user_orders = [] allowed_course_modes = ['professional', 'verified', 'credit'] commerce_configuration = CommerceConfiguration.current() user_query = {'username': user.username} use_cache = commerce_configuration.is_cache_enabled cache_key = commerce_configuration.CACHE_KEY + '.' + str( user.id) if use_cache else None api = ecommerce_api_client(user) commerce_user_orders = get_edx_api_data(commerce_configuration, user, 'orders', api=api, querystring=user_query, cache_key=cache_key) for order in commerce_user_orders: if order['status'].lower() == 'complete': for line in order['lines']: product = line.get('product') if product: for attribute in product['attribute_values']: if attribute[ 'name'] == 'certificate_type' and attribute[ 'value'] in allowed_course_modes: try: date_placed = datetime.strptime( order['date_placed'], "%Y-%m-%dT%H:%M:%SZ") order_data = { 'number': order['number'], 'price': order['total_excl_tax'], 'title': order['lines'][0]['title'], 'order_date': strftime_localized( date_placed.replace(tzinfo=pytz.UTC), 'SHORT_DATE'), 'receipt_url': EcommerceService().get_receipt_page_url( order['number']) } user_orders.append(order_data) except KeyError: log.exception('Invalid order structure: %r', order) return no_data return user_orders
def test_get_specific_resource(self): """Verify that a specific resource can be retrieved.""" catalog_integration = self.create_catalog_integration() api = create_catalog_api_client(self.user) resource_id = 1 url = '{api_root}/programs/{resource_id}/'.format( api_root=CatalogIntegration.current().get_internal_api_url().strip( '/'), resource_id=resource_id, ) expected_resource = {'key': 'value'} self._mock_catalog_api([ httpretty.Response(body=json.dumps(expected_resource), content_type='application/json') ], url=url) actual_resource = get_edx_api_data(catalog_integration, 'programs', api=api, resource_id=resource_id) assert actual_resource == expected_resource self._assert_num_requests(1)
def get_course_run(course_key, user): """Get a course run's data from the course catalog service. Arguments: course_key (CourseKey): Course key object identifying the run whose data we want. user (User): The user to authenticate as when making requests to the catalog service. Returns: dict, empty if no data could be retrieved. """ catalog_integration = CatalogIntegration.current() scopes = ['email', 'profile'] expires_in = settings.OAUTH_ID_TOKEN_EXPIRATION jwt = JwtBuilder(user).build_token(scopes, expires_in) api = EdxRestApiClient(catalog_integration.internal_api_url, jwt=jwt) data = get_edx_api_data( catalog_integration, user, 'course_runs', resource_id=unicode(course_key), cache_key=catalog_integration.CACHE_KEY if catalog_integration.is_cache_enabled else None, api=api, ) return data if data else {}
def test_get_specific_resource_with_falsey_id(self): """ Verify that a specific resource can be retrieved, and pagination parsing is not attempted, if the ID passed is falsey (e.g., 0). The expected resource contains a "results" key, as a paginatable item would have, so if the function looks for falsey values in the resource_id field, rather than specifically None, the function will return the value of that "results" key. """ catalog_integration = self.create_catalog_integration() api = create_catalog_api_client(self.user) resource_id = 0 url = '{api_root}/programs/{resource_id}/'.format( api_root=CatalogIntegration.current().get_internal_api_url().strip( '/'), resource_id=resource_id, ) expected_resource = {'key': 'value', 'results': []} self._mock_catalog_api([ httpretty.Response(body=json.dumps(expected_resource), content_type='application/json') ], url=url) actual_resource = get_edx_api_data(catalog_integration, 'programs', api=api, resource_id=resource_id) assert actual_resource == expected_resource self._assert_num_requests(1)
def get_currency_data(): """Retrieve currency data from the catalog service. Returns: list of dict, representing program types. dict, if a specific program type is requested. """ catalog_integration = CatalogIntegration.current() if catalog_integration.enabled: try: user = catalog_integration.get_service_user() except ObjectDoesNotExist: return [] api = create_catalog_api_client(user) cache_key = '{base}.currency'.format( base=catalog_integration.CACHE_KEY) return get_edx_api_data(catalog_integration, 'currency', api=api, traverse_pagination=False, cache_key=cache_key if catalog_integration.is_cache_enabled else None) else: return []
def test_get_unpaginated_data(self): """Verify that unpaginated data can be retrieved.""" catalog_integration = self.create_catalog_integration() api = create_catalog_api_client(self.user) expected_collection = ['some', 'test', 'data'] data = { 'next': None, 'results': expected_collection, } self._mock_catalog_api([ httpretty.Response(body=json.dumps(data), content_type='application/json') ]) with mock.patch('edx_rest_api_client.client.EdxRestApiClient.__init__' ) as mock_init: actual_collection = get_edx_api_data(catalog_integration, 'programs', api=api) # Verify that the helper function didn't initialize its own client. assert not mock_init.called assert actual_collection == expected_collection # Verify the API was actually hit (not the cache) self._assert_num_requests(1)
def test_get_specific_resource(self): """Verify that a specific resource can be retrieved.""" program_config = self.create_programs_config() resource_id = 1 url = '{api_root}/programs/{resource_id}/'.format( api_root=ProgramsApiConfig.current().internal_api_url.strip('/'), resource_id=resource_id, ) expected_resource = {'key': 'value'} self._mock_programs_api([ httpretty.Response(body=json.dumps(expected_resource), content_type='application/json') ], url=url) actual_resource = get_edx_api_data(program_config, self.user, 'programs', resource_id=resource_id) self.assertEqual(actual_resource, expected_resource) self._assert_num_requests(1)
def get_program_types(name=None): """Retrieve program types from the catalog service. Keyword Arguments: name (string): Name identifying a specific program type. Returns: list of dict, representing program types. dict, if a specific program type is requested. """ user, catalog_integration = check_catalog_integration_and_get_user(error_message_field='Program types') if user: api = create_catalog_api_client(user) cache_key = '{base}.program_types'.format(base=catalog_integration.CACHE_KEY) data = get_edx_api_data(catalog_integration, 'program_types', api=api, cache_key=cache_key if catalog_integration.is_cache_enabled else None) # Filter by name if a name was provided if name: data = next(program_type for program_type in data if program_type['name'] == name) return data else: return []
def get_user_orders(user): """Given a user, get the detail of all the orders from the Ecommerce service. Args: user (User): The user to authenticate as when requesting ecommerce. Returns: list of dict, representing orders returned by the Ecommerce service. """ user_orders = [] commerce_configuration = CommerceConfiguration.current() user_query = {'username': user.username} use_cache = commerce_configuration.is_cache_enabled cache_key = commerce_configuration.CACHE_KEY + '.' + str(user.id) if use_cache else None api = ecommerce_api_client(user) commerce_user_orders = get_edx_api_data( commerce_configuration, 'orders', api=api, querystring=user_query, cache_key=cache_key ) for order in commerce_user_orders: if order['status'].lower() == 'complete': date_placed = datetime.strptime(order['date_placed'], "%Y-%m-%dT%H:%M:%SZ") order_data = { 'number': order['number'], 'price': order['total_excl_tax'], 'order_date': strftime_localized(date_placed, 'SHORT_DATE'), 'receipt_url': EcommerceService().get_receipt_page_url(order['number']), 'lines': order['lines'], } user_orders.append(order_data) return user_orders
def test_get_specific_fields_from_cache_response(self): """Verify that resource response is cached and get required fields from cached response""" catalog_integration = self.create_catalog_integration(cache_ttl=5) api = create_catalog_api_client(self.user) response = {'lang': 'en', 'weeks_to_complete': '5'} resource_id = 'course-v1:testX+testABC+1T2019' url = '{api_root}/course_runs/{resource_id}/'.format( api_root=CatalogIntegration.current().get_internal_api_url().strip( '/'), resource_id=resource_id, ) expected_resource_for_lang = {'lang': 'en'} expected_resource_for_weeks_to_complete = {'weeks_to_complete': '5'} self._mock_catalog_api([ httpretty.Response(body=json.dumps(response), content_type='application/json') ], url=url) cache_key = CatalogIntegration.current().CACHE_KEY # get response and set the cache. actual_resource_for_lang = get_edx_api_data(catalog_integration, 'course_runs', resource_id=resource_id, api=api, cache_key=cache_key, fields=['lang']) self.assertEqual(actual_resource_for_lang, expected_resource_for_lang) # Hit the cache actual_resource = get_edx_api_data(catalog_integration, 'course_runs', api=api, resource_id=resource_id, cache_key=cache_key, fields=['weeks_to_complete']) self.assertEqual(actual_resource, expected_resource_for_weeks_to_complete) # Verify that only one requests were made, not three. self._assert_num_requests(1)
def test_api_config_disabled(self, mock_warning): """Verify that no data is retrieved if the provided config model is disabled.""" catalog_integration = self.create_catalog_integration(enabled=False) actual = get_edx_api_data(catalog_integration, 'programs', api=None) self.assertTrue(mock_warning.called) self.assertEqual(actual, [])
def test_api_config_disabled(self, mock_warning): """Verify that no data is retrieved if the provided config model is disabled.""" program_config = self.create_programs_config(enabled=False) actual = get_edx_api_data(program_config, self.user, 'programs') self.assertTrue(mock_warning.called) self.assertEqual(actual, [])
def test_api_config_disabled(self, mock_warning): """Verify that no data is retrieved if the provided config model is disabled.""" catalog_integration = self.create_catalog_integration(enabled=False) actual = get_edx_api_data(catalog_integration, 'programs', api=None) assert mock_warning.called assert actual == []
def test_get_edx_api_data_retrieval_failure(self): """Verify exception is logged when data can't be retrieved from API.""" program_config = self.create_programs_config() self.mock_programs_api(status_code=500) with LogCapture(LOGGER_NAME) as logger: actual = get_edx_api_data(program_config, self.user, "programs") logger.check((LOGGER_NAME, "ERROR", u"Failed to retrieve data from the programs API.")) self.assertEqual(actual, [])
def test_get_edx_api_data_multiple_page(self): """Verify that all data is retrieve for multiple page response.""" credentials_config = self.create_credentials_config() self.mock_credentials_api(self.user, is_next_page=True) querystring = {'username': self.user.username} actual = get_edx_api_data(credentials_config, self.user, 'user_credentials', querystring=querystring) expected_data = self.CREDENTIALS_NEXT_API_RESPONSE['results'] + self.CREDENTIALS_API_RESPONSE['results'] self.assertEqual(actual, expected_data)
def test_get_edx_api_data_retrieval_failure(self): """Verify exception is logged when data can't be retrieved from API.""" program_config = self.create_programs_config() self.mock_programs_api(status_code=500) with LogCapture(LOGGER_NAME) as logger: actual = get_edx_api_data(program_config, self.user, 'programs') logger.check((LOGGER_NAME, 'ERROR', u'Failed to retrieve data from the programs API.')) self.assertEqual(actual, [])
def test_get_edx_api_data_programs(self): """Verify programs data can be retrieved using get_edx_api_data.""" program_config = self.create_programs_config() self.mock_programs_api() actual = get_edx_api_data(program_config, self.user, "programs") self.assertEqual(actual, self.PROGRAMS_API_RESPONSE["results"]) # Verify the API was actually hit (not the cache). self.assertEqual(len(httpretty.httpretty.latest_requests), 1)
def test_client_initialization_failure(self, mock_exception, mock_init): """Verify that an exception is logged when the API client fails to initialize.""" mock_init.side_effect = Exception program_config = self.create_programs_config() actual = get_edx_api_data(program_config, self.user, 'programs') self.assertTrue(mock_exception.called) self.assertEqual(actual, [])
def get_programs(uuid=None, types=None): # pylint: disable=redefined-builtin """Retrieve marketable programs from the catalog service. Keyword Arguments: uuid (string): UUID identifying a specific program. types (list of string): List of program type names used to filter programs by type (e.g., ["MicroMasters"] will only return MicroMasters programs, ["MicroMasters", "XSeries"] will return MicroMasters and XSeries programs). Returns: list of dict, representing programs. dict, if a specific program is requested. """ catalog_integration = CatalogIntegration.current() if catalog_integration.enabled: try: user = User.objects.get(username=catalog_integration.service_username) except User.DoesNotExist: return [] api = create_catalog_api_client(user, catalog_integration) types_param = ','.join(types) if types else None cache_key = '{base}.programs{types}'.format( base=catalog_integration.CACHE_KEY, types='.' + types_param if types_param else '' ) querystring = { 'exclude_utm': 1, } # TODO ECOM-7650: Remove this after https://github.com/edx/course-discovery/pull/805 is merged and released. if waffle.switch_is_active('display_retired_programs_on_learner_dashboard'): querystring['status'] = ('active', 'retired',) else: querystring['marketable'] = 1 if uuid: querystring['use_full_course_serializer'] = 1 if types_param: querystring['types'] = types_param return get_edx_api_data( catalog_integration, user, 'programs', resource_id=uuid, cache_key=cache_key if catalog_integration.is_cache_enabled else None, api=api, querystring=querystring, ) else: return []
def test_get_edx_api_data_client_initialization_failure(self, mock_init): """Verify no data is retrieved and exception logged when API client fails to initialize. """ program_config = self.create_programs_config() mock_init.side_effect = Exception with LogCapture(LOGGER_NAME) as logger: actual = get_edx_api_data(program_config, self.user, "programs") logger.check((LOGGER_NAME, "ERROR", u"Failed to initialize the programs API client.")) self.assertEqual(actual, []) self.assertTrue(mock_init.called)
def test_data_retrieval_failure(self, mock_exception): """Verify that an exception is logged when data can't be retrieved.""" program_config = self.create_programs_config() self._mock_programs_api( [httpretty.Response(body='clunk', content_type='application/json', status_code=500)] ) actual = get_edx_api_data(program_config, self.user, 'programs') self.assertTrue(mock_exception.called) self.assertEqual(actual, [])
def test_data_retrieval_failure(self, mock_exception): """Verify that an exception is logged when data can't be retrieved.""" catalog_integration = self.create_catalog_integration() api = create_catalog_api_client(self.user) self._mock_catalog_api( [httpretty.Response(body='clunk', content_type='application/json', status_code=500)] ) actual = get_edx_api_data(catalog_integration, 'programs', api=api) self.assertTrue(mock_exception.called) self.assertEqual(actual, [])
def test_get_specific_fields_from_cache_response(self): """Verify that resource response is cached and get required fields from cached response""" catalog_integration = self.create_catalog_integration(cache_ttl=5) api = create_catalog_api_client(self.user) response = {'lang': 'en', 'weeks_to_complete': '5'} resource_id = 'course-v1:testX+testABC+1T2019' url = '{api_root}/course_runs/{resource_id}/'.format( api_root=CatalogIntegration.current().get_internal_api_url().strip('/'), resource_id=resource_id, ) expected_resource_for_lang = {'lang': 'en'} expected_resource_for_weeks_to_complete = {'weeks_to_complete': '5'} self._mock_catalog_api( [httpretty.Response(body=json.dumps(response), content_type='application/json')], url=url ) cache_key = CatalogIntegration.current().CACHE_KEY # get response and set the cache. actual_resource_for_lang = get_edx_api_data( catalog_integration, 'course_runs', resource_id=resource_id, api=api, cache_key=cache_key, fields=['lang'] ) self.assertEqual(actual_resource_for_lang, expected_resource_for_lang) # Hit the cache actual_resource = get_edx_api_data( catalog_integration, 'course_runs', api=api, resource_id=resource_id, cache_key=cache_key, fields=['weeks_to_complete'] ) self.assertEqual(actual_resource, expected_resource_for_weeks_to_complete) # Verify that only one requests were made, not three. self._assert_num_requests(1)
def test_api_config_disabled_with_id_and_not_collection(self, mock_warning): """Verify that no data is retrieved if the provided config model is disabled.""" catalog_integration = self.create_catalog_integration(enabled=False) actual = get_edx_api_data( catalog_integration, 'programs', api=None, resource_id=100, many=False ) self.assertTrue(mock_warning.called) self.assertEqual(actual, {})