def get_oauth_access_token(self): """ Request OpenEdx API OAuth2 token. Token type: JWT (reference: https://jwt.io/). :return: access_token, expires_at """ url = "{host_url}{token_url}".format( host_url=self.content_source.host_url, token_url=self.TOKEN_URL) log.debug("Requesting oauth token: (url={})".format(url)) try: oauth_client = self.content_source.o_auth_client access_token, expires_at = super().get_oauth_access_token( url=url, client_id=oauth_client.client_id, client_secret=oauth_client.client_secret, token_type='jwt', ) except ObjectDoesNotExist: raise HttpClientError( "OAuth token request failure. Please, configure OAuth client in order to be able make API requests." ) except ValueError: log.exception( "You may want to check your OAuth registration on LTI Provider." "LTI Provider may be disabled (to enable: LMS config > FEATURES > ENABLE_OAUTH2_PROVIDER: true" ) raise HttpClientError("OAuth token request failure.") except RequestException: log.exception( 'OAuth2 token request to the OpenEdx LTI Provider failed.') raise HttpClientError("OAuth token request failure.") return access_token, expires_at
def get_available_blocks(source_id, course_id=''): """ Content Source API requester. Fetches all source blocks from the course with the given ID. Blocks data is filtered by `apply_data_filter`. :param course_id: Course id :param source_id: LtiConsumer id :return: (list) blocks data """ content_source = get_active_content_sources(source_id).first() all_blocks = [] # Get API client instance: api = api_client_factory(content_source=content_source) try: # filter function will be applied to api response all_blocks.extend( apply_data_filter(api.get_course_blocks(course_id), filters=[ 'id', 'block_id', 'display_name', 'lti_url', 'type', 'content_source_id' ], context_id=course_id, content_source_id=content_source.id)) except HttpNotFoundError: raise HttpClientError( _("Requested course not found. Check `course_id` url encoding.")) except HttpClientError as exc: raise HttpClientError(_("Not valid query: {}").format(exc.message)) return all_blocks
def test_bad_solitude_request(self): self.setup_generic_buyer() exc = HttpClientError('bad request') exc.content = {'nonce': ['This field is required.']} self.solitude.braintree.paymethod.post.side_effect = exc res, data = self.post() self.assert_form_error(res, ['nonce'])
def test_bad_solitude_request(self): exc = HttpClientError('bad request') exc.content = {'product_id': ['Invalid product.']} self.solitude.braintree.sale.post.side_effect = exc res, data = self.post() self.assert_error_response( res, msg_patterns={'product_id': exc.content['product_id'][0]})
def test_fatal_code(self): response_with_200 = HttpResponse(status=200) response_with_400 = HttpResponse(status=400) response_with_429 = HttpResponse(status=429) response_with_504 = HttpResponse(status=504) self.assertFalse(_fatal_code(HttpClientError(response=response_with_200))) # pylint: disable=protected-access self.assertTrue(_fatal_code(HttpClientError(response=response_with_400))) # pylint: disable=protected-access self.assertFalse(_fatal_code(HttpClientError(response=response_with_429))) # pylint: disable=protected-access self.assertFalse(_fatal_code(HttpClientError(response=response_with_504))) # pylint: disable=protected-access
def test_fatal_code(self): response_with_200 = HttpResponse(status=200) response_with_429 = HttpResponse(status=429) self.assertEqual(self.loader_class._fatal_code( # pylint: disable=protected-access HttpClientError(response=response_with_200)), True ) self.assertEqual(self.loader_class._fatal_code( # pylint: disable=protected-access HttpClientError(response=response_with_429)), False )
def test_fatal_code(self): response_with_200 = HttpResponse(status=200) response_with_400 = HttpResponse(status=400) response_with_429 = HttpResponse(status=429) response_with_504 = HttpResponse(status=504) assert not _fatal_code(HttpClientError(response=response_with_200)) assert _fatal_code(HttpClientError(response=response_with_400)) assert not _fatal_code(HttpClientError(response=response_with_429)) assert not _fatal_code(HttpClientError(response=response_with_504))
def test_bad_request_with_content(self): exc = HttpClientError('400') # Simulate a form error. exc.content = {'field-name': ['was invalid']} self.solitude.services.status.post.side_effect = exc res, data = self.post() eq_(data['error_message'], 'Bad Request') eq_(data['error_response'], exc.content) eq_(res.status_code, 400)
def test_bad_solitude_request(self): self.setup_generic_buyer() self.setup_no_subscription_yet() exc = HttpClientError('bad request') exc.content = {'nonce': ['This field is required.']} self.solitude.braintree.paymethod.post.side_effect = exc res, data = self.post() self.assert_error_response( res, msg_patterns={'nonce': exc.content['nonce'][0]})
def test_new_tenant_recoverable_error(self, OA_mock): with setup_fb_client() as fb_client_mock: response = MagicMock() response.json.return_value = { 'postal_code': { 'code': 'E1002', 'message': "Postal code can't start with 999" } } fb_client_mock.create.side_effect = HttpClientError( response=response) OA_mock.get_resource.side_effect = [{ 'companyName': 'fake_company', 'techContact': { 'email': '*****@*****.**' } }, { 'subscriptionId': 555 }] res = self.client.post('/connector/v1/tenant', headers=self.headers, data=self.new_tenant) fb_client_mock.create.assert_called() assert res.json['status'] == 'activationRequired' assert res.status_code == 202
def test_update_transcription_service_credentials_exceptions( self, mock_client, mock_logger): """ Tests that the update transcription service credentials logs the exception occurring during communication with edx-video-pipeline. """ error_content = '{"error_type": "1"}' mock_client.return_value.request = Mock(side_effect=HttpClientError( content=error_content)) # try updating the transcription service credentials credentials_payload = { 'org': 'mit', 'provider': 'ABC Provider', 'api_key': '61c56a8d0' } error_response, is_updated = update_3rd_party_transcription_service_credentials( **credentials_payload) # Assert the results. self.assertFalse(is_updated) self.assertDictEqual(error_response, json.loads(error_content)) mock_logger.exception.assert_called_with( 'Unable to update transcript credentials -- org={}, provider={}, response={}' .format(credentials_payload['org'], credentials_payload['provider'], error_content))
def test_new_tenant_recoverable_error_invalid_zip(self, OA_mock): with setup_fb_client() as fb_client_mock: response = MagicMock() response.json.return_value = { 'postal_code': { 'code': 'E1001', 'message': "Postal code must be a 5-digit number" } } fb_client_mock.create.side_effect = HttpClientError( response=response) OA_mock.get_resource.side_effect = [{ 'companyName': 'fake_company', 'techContact': { 'email': '*****@*****.**' } }, { 'subscriptionId': 555 }] res = self.client.post('/connector/v1/tenant', headers=self.headers, data=self.new_tenant) fb_client_mock.create.assert_called() assert res.json['status'] == 'activationRequired' assert res.status_code == 202 assert res.json['statusData']['perPropertyData'][0]['message']['text'] == \ 'The postal code must consist of five digits'
def test_new_tenant_unrecoverable_error(self, OA_mock): with setup_fb_client() as fb_client_mock: response = MagicMock() response.json.return_value = { 'unknown_error': 'Something went wrong' } response.text = 'Something went wrong' response.status_code = 400 fb_client_mock.create.side_effect = HttpClientError( response=response) OA_mock.get_resource.side_effect = [{ 'companyName': 'fake_company', 'techContact': { 'email': '*****@*****.**' }, 'addressPostal': { 'postalCode': '11111' } }, { 'subscriptionId': 555 }] res = self.client.post('/connector/v1/tenant', headers=self.headers, data=self.new_tenant) fb_client_mock.create.assert_called() assert res.status_code == 500
def get_available_courses(source_ids=None): """ Fetch all available courses from all sources of from source with provided ID. :param source_ids: content provider's ID or list of the IDs :return: (list) course_ids """ content_sources = get_active_content_sources( source_ids, not_allow_empty_source_id=False) all_courses = [] for content_source in content_sources: # Get API client instance: api = api_client_factory(content_source=content_source) try: all_courses.extend( apply_data_filter(api.get_provider_courses(), filters=[ 'id', 'course_id', 'name', 'org', 'content_source_id' ], content_source_id=content_source.id)) except HttpClientError as exc: raise HttpClientError(_("Not valid query: {}").format(exc.message)) return all_courses
def test_create_buyer_with_long_pin(self, slumber): slumber.generic.buyer.post.side_effect = HttpClientError( response=self.create_error_response( content={'pin': ['PIN_4_NUMBERS_LONG']})) buyer = client.create_buyer('with_long_pin', '12345') assert buyer.get('errors') eq_(buyer['errors'].get('pin'), [ERROR_STRINGS['PIN_4_NUMBERS_LONG']])
def test_create_buyer_with_existing_uuid(self, slumber): slumber.generic.buyer.post.side_effect = HttpClientError( response=self.create_error_response( content={'uuid': ['BUYER_UUID_ALREADY_EXISTS']})) buyer = client.create_buyer(self.uuid, '1234') assert buyer.get('errors') eq_(buyer['errors'].get('uuid'), [ERROR_STRINGS['BUYER_UUID_ALREADY_EXISTS']])
def test_create_buyer_with_short_pin(self, slumber): slumber.generic.buyer.post.side_effect = HttpClientError( response=self.create_error_response(content={ 'pin': [msg.PIN_4_NUMBERS_LONG] })) buyer = client.create_buyer('with_short_pin', self.email, pin='123') assert buyer.get('errors') eq_(buyer['errors'].get('pin'), [msg.PIN_4_NUMBERS_LONG])
def test_create_fail(self): err = {'broken': True} self.bango_patcher.package.post.side_effect = HttpClientError( content=err) res = self.client.post(self.payment_list, data=json.dumps(payment_data)) eq_(res.status_code, 500) eq_(json.loads(res.content), err)
def test_4xx_5xx(self): self.login() for status in [404, 403, 400, 500]: self.mocked_extract.get.side_effect = HttpClientError( response=mock.MagicMock(status_code=status)) response = self.client.get(self.url) self.assertEqual(response.status_code, status)
def test_unset_needs_pin_reset_with_wrong_etag(self, slumber): wrong_etag = 'etag:wrong' buyer = mock.Mock(return_value=self.buyer_data) buyer.patch.side_effect = HttpClientError( response=self.create_error_response( status_code=412, content={'ERROR': ['RESOURCE_MODIFIED']})) slumber.generic.buyer.return_value = buyer with self.assertRaises(ResourceModified): client.set_needs_pin_reset(self.uuid, False, etag=wrong_etag)
def test_set_new_pin_for_reset_with_alpha_pin(self, slumber): buyer = mock.Mock(return_value=self.buyer_data) buyer.patch.side_effect = HttpClientError( response=self.create_error_response( content={'new_pin': ['PIN_ONLY_NUMBERS']})) slumber.generic.buyer.return_value = buyer res = client.set_new_pin(self.uuid, 'meow') assert res.get('errors') eq_(res['errors'].get('new_pin'), [ERROR_STRINGS['PIN_ONLY_NUMBERS']])
def _instance_get(self, **kwargs): try: return get_example_popit_json('{0}_{1}_embed={2}.json'.format( self.collection, self.object_id, kwargs.get('embed', 'membership'))) except IOError as e: if e.errno == errno.ENOENT: raise HttpClientError('Client Error 404', content='Fake content not found') else: raise
def test_failure(self): connection = mock.MagicMock() connection.oauth2.access_token.post.side_effect = HttpClientError( content='{"error": "invalid grant"}') self.mocked_get_auth_connection.return_value = connection response = self.client.post(self.url, data=self.credentials, follow=True) self.assertFalse(response.context_data["form"].is_valid()) self.assertEqual(self.client.session.items(), [])
def test_proxy_error_responses(self): # Create a scenario where the proxied API raises an HTTP error. data = {'error': {'message': 'something not found'}} proxy_res = HttpResponse(data, content_type='application/json', status=404) proxy_res.json = data proxy_res.request = RequestFactory().get('http://api/some/endpoint') exc = HttpClientError(proxy_res.content, response=proxy_res) self.api.products.get.side_effect = exc res = self.request('get', '/reference/products?foo=bar', 'products') eq_(res.status_code, 404) eq_(res.json, {'error_message': 'something not found'})
def get_active_content_sources(source_id=None, not_allow_empty_source_id=True): """ Check that passed source_id parameter is valid. If there's only one active source provider - source_id parameter is not required, it will get first active. :param source_id: LtiConsumer object id :param not_allow_empty_source_id: if True - it will not allow empty source_id, if False - source_id could be None :return: queryset of content_sources :raise HttpClientError: if provided parameters are not valid """ if not_allow_empty_source_id and not source_id: # if source_id is not provided and more than one active content_provider raise HttpClientError( _("Parameter source_id is mandatory if there's more than one active content source." )) content_sources = get_content_providers(source_id=source_id) if not content_sources: # if no active content sources raise HttpClientError(_("No active Content Provider")) return content_sources
def test_get_catalog_results_with_exception(self): """ Verify `get_catalog_results` of CourseCatalogApiClient works as expected in case of exception. """ responses.add( responses.POST, url=urljoin(self.api.catalog_url, self.api.SEARCH_ALL_ENDPOINT), body=HttpClientError(content='boom'), ) logger = logging.getLogger('enterprise.api_client.discovery') handler = MockLoggingHandler(level="ERROR") logger.addHandler(handler) assert self.api.get_catalog_results(content_filter_query='query', query_params={'page': 2}) == { 'next': None, 'previous': None, 'results': [], } expected_message = 'Failed to retrieve data from the catalog API. content -- [boom]' assert handler.messages['error'][0] == expected_message
def test_get_catalog_results_with_exception(self): """ Verify `get_catalog_results` of CourseCatalogApiClient works as expected in case of exception. """ responses.add( responses.POST, url=urljoin(self.api.catalog_url, self.api.SEARCH_ALL_ENDPOINT), body=HttpClientError(content='boom'), ) logger = logging.getLogger('enterprise.api_client.discovery') handler = MockLoggingHandler(level="ERROR") logger.addHandler(handler) with self.assertRaises(HttpClientError): self.api.get_catalog_results( content_filter_query='query', query_params={u'page': 2} ) expected_message = ('Attempted to call course-discovery search/all/ endpoint with the following parameters: ' 'content_filter_query: query, query_params: {}, traverse_pagination: False. ' 'Failed to retrieve data from the catalog API. content -- [boom]').format({u'page': 2}) assert handler.messages['error'][0] == expected_message
def test_authenticate_invalid_credentials(self): backend = TestClaBackend() credentials = {"username": "******", "password": "******"} connection = mock.MagicMock() connection.oauth2.access_token.post.side_effect = HttpClientError( content='{"error": "invalid grant"}') self.mocked_get_auth_connection.return_value = connection self.assertEqual(backend.authenticate(**credentials), None) self.assertEqual(self.mocked_get_auth_connection.called, True) connection.oauth2.access_token.post.assert_called_with({ "client_id": base.DEFAULT_ZONE_PROFILE["CLIENT_ID"], "client_secret": base.DEFAULT_ZONE_PROFILE["CLIENT_SECRET"], "grant_type": "password", "username": credentials["username"], "password": credentials["password"], })
def test_non_existent_get_buyer(self, slumber): slumber.generic.buyer.get_object_or_404.side_effect = HttpClientError( response=self.create_error_response()) buyer = client.get_buyer('something-that-does-not-exist') assert 'errors' in buyer
def test_invalid_json_response(self, fake_log, slumber): slumber.generic.buyer.get_object_or_404.side_effect = HttpClientError( response=self.create_error_response(content='<not valid json>')) with self.assertRaises(ValueError): client.get_buyer('catastrophic-non-json-error') assert fake_log.error.called, 'expected response to be logged'
def test_verify_alpha_pin(self, slumber): slumber.generic.verify_pin.post.side_effect = HttpClientError( response=self.create_error_response( content={'pin': ['PIN_ONLY_NUMBERS']})) assert 'pin' in client.verify_pin(self.uuid, 'lame')['errors']
def test_notice_failure(self): post = self.slumber.provider.reference.notices.post post.side_effect = HttpClientError('bad stuff') res = self.success() eq_(res.status_code, 400)