def test_secure_connection_unusual_port(self): """ Test that the connection class will default to secure (https) even when the port is an unusual (non 443, 80) number """ conn = Connection(secure=True, host='localhost', port=8081) conn.connect() self.assertEqual(conn.connection.host, 'https://localhost:8081') conn2 = Connection(url='https://localhost:8081') conn2.connect() self.assertEqual(conn2.connection.host, 'https://localhost:8081')
def test_insecure_connection_unusual_port(self): """ Test that the connection will allow unusual ports and insecure schemes """ conn = Connection(secure=False, host='localhost', port=8081) conn.connect() self.assertEqual(conn.connection.host, 'http://localhost:8081') conn2 = Connection(url='http://localhost:8081') conn2.connect() self.assertEqual(conn2.connection.host, 'http://localhost:8081')
def test_implicit_port(self): """ Test that the port is not included in the URL if the protocol implies the port, e.g. http implies 80 """ conn = Connection(secure=True, host='localhost', port=443) conn.connect() self.assertEqual(conn.connection.host, 'https://localhost') conn2 = Connection(secure=False, host='localhost', port=80) conn2.connect() self.assertEqual(conn2.connection.host, 'http://localhost')
def test_context_is_reset_after_request_has_finished(self): context = {'foo': 'bar'} def responseCls(connection, response): connection.called = True self.assertEqual(connection.context, context) con = Connection() con.called = False con.connection = Mock() con.responseCls = responseCls con.set_context(context) self.assertEqual(con.context, context) con.request('/') # Context should have been reset self.assertTrue(con.called) self.assertEqual(con.context, {}) # Context should also be reset if a method inside request throws con = Connection() con.connection = Mock() con.set_context(context) self.assertEqual(con.context, context) con.connection.request = Mock(side_effect=ssl.SSLError()) try: con.request('/') except ssl.SSLError: pass self.assertEqual(con.context, {}) con.connection = Mock() con.set_context(context) self.assertEqual(con.context, context) con.responseCls = Mock(side_effect=ValueError()) try: con.request('/') except ValueError: pass self.assertEqual(con.context, {})
def test_context_is_reset_after_request_has_finished(self): context = {"foo": "bar"} def responseCls(connection, response) -> mock.MagicMock: connection.called = True self.assertEqual(connection.context, context) return mock.MagicMock(spec=Response) con = Connection() con.called = False con.connection = Mock() con.responseCls = responseCls con.set_context(context) self.assertEqual(con.context, context) con.request("/") # Context should have been reset self.assertTrue(con.called) self.assertEqual(con.context, {}) # Context should also be reset if a method inside request throws con = Connection(timeout=1, retry_delay=0.1) con.connection = Mock() con.set_context(context) self.assertEqual(con.context, context) con.connection.request = Mock(side_effect=ssl.SSLError()) try: con.request("/") except ssl.SSLError: pass self.assertEqual(con.context, {}) con.connection = Mock() con.set_context(context) self.assertEqual(con.context, context) con.responseCls = Mock(side_effect=ValueError()) try: con.request("/") except ValueError: pass self.assertEqual(con.context, {})
def test_cache_busting(self): params1 = {'foo1': 'bar1', 'foo2': 'bar2'} params2 = [('foo1', 'bar1'), ('foo2', 'bar2')] con = Connection() con.connection = Mock() con.pre_connect_hook = Mock() con.pre_connect_hook.return_value = {}, {} con.cache_busting = False con.request(action='/path', params=params1) args, kwargs = con.pre_connect_hook.call_args self.assertFalse('cache-busting' in args[0]) self.assertEqual(args[0], params1) con.request(action='/path', params=params2) args, kwargs = con.pre_connect_hook.call_args self.assertFalse('cache-busting' in args[0]) self.assertEqual(args[0], params2) con.cache_busting = True con.request(action='/path', params=params1) args, kwargs = con.pre_connect_hook.call_args self.assertTrue('cache-busting' in args[0]) con.request(action='/path', params=params2) args, kwargs = con.pre_connect_hook.call_args self.assertTrue('cache-busting' in args[0][len(params2)])
def get_response_object(url, method='GET', headers=None, retry_failed=None): """ Utility function which uses libcloud's connection class to issue an HTTP request. :param url: URL to send the request to. :type url: ``str`` :param method: HTTP method. :type method: ``str`` :param headers: Optional request headers. :type headers: ``dict`` :param retry_failed: True to retry failed requests. :return: Response object. :rtype: :class:`Response`. """ parsed_url = urlparse.urlparse(url) parsed_qs = parse_qs(parsed_url.query) secure = parsed_url.scheme == 'https' headers = headers or {} method = method.upper() con = Connection(secure=secure, host=parsed_url.netloc) response = con.request(action=parsed_url.path, params=parsed_qs, headers=headers, method=method, retry_failed=retry_failed) return response
def test_retry_rate_limit_error_forever_with_old_retry_class( self, mock_connect): con = Connection() con.connection = Mock() self.retry_counter = 0 def mock_connect_side_effect(*args, **kwargs): self.retry_counter += 1 if self.retry_counter < 4: headers = {'retry-after': 0.1} raise RateLimitReachedError(headers=headers) return 'success' mock_connect.__name__ = 'mock_connect' headers = {'retry-after': 0.2} mock_connect.side_effect = mock_connect_side_effect retry_request = RetryForeverOnRateLimitError(timeout=0.1, retry_delay=0.1, backoff=1) retry_request(con.request)(action='/') # We have waited longer the timeout but continue to retry result = retry_request(con.request)(action='/') self.assertEqual(result, "success") self.assertEqual(mock_connect.call_count, 5, 'Retry logic failed')
def test_parse_errors_can_be_retried(self): class RetryableThrowingError(Response): parse_error_counter: int = 0 success_counter: int = 0 def __init__(self, *_, **__): super().__init__(mock.MagicMock(), mock.MagicMock()) def parse_body(self): return super().parse_body() def parse_error(self): RetryableThrowingError.parse_error_counter += 1 if RetryableThrowingError.parse_error_counter > 1: return "success" else: raise RateLimitReachedError() def success(self): RetryableThrowingError.success_counter += 1 if RetryableThrowingError.success_counter > 1: return True else: return False con = Connection() con.connection = Mock() con.responseCls = RetryableThrowingError result = con.request(action="/", retry_failed=True) self.assertEqual(result.success(), True)
def test_cache_busting(self): params1 = {"foo1": "bar1", "foo2": "bar2"} params2 = [("foo1", "bar1"), ("foo2", "bar2")] con = Connection() con.connection = Mock() con.pre_connect_hook = Mock() con.pre_connect_hook.return_value = {}, {} con.cache_busting = False con.request(action="/path", params=params1) args, kwargs = con.pre_connect_hook.call_args self.assertFalse("cache-busting" in args[0]) self.assertEqual(args[0], params1) con.request(action="/path", params=params2) args, kwargs = con.pre_connect_hook.call_args self.assertFalse("cache-busting" in args[0]) self.assertEqual(args[0], params2) con.cache_busting = True con.request(action="/path", params=params1) args, kwargs = con.pre_connect_hook.call_args self.assertTrue("cache-busting" in args[0]) con.request(action="/path", params=params2) args, kwargs = con.pre_connect_hook.call_args self.assertTrue("cache-busting" in args[0][len(params2)])
def test_RawResponse_class_read_method(self): """ Test that the RawResponse class includes a response property which exhibits the same properties and methods as httplib.HTTPResponse for backward compat <1.5.0 """ TEST_DATA = '1234abcd' conn = Connection(host='mock.com', port=80, secure=False) conn.connect() with requests_mock.Mocker() as m: m.register_uri('GET', 'http://mock.com/raw_data', text=TEST_DATA, headers={'test': 'value'}) response = conn.request('/raw_data', raw=True) data = response.response.read() self.assertEqual(data, TEST_DATA) header_value = response.response.getheader('test') self.assertEqual(header_value, 'value') headers = response.response.getheaders() self.assertEqual(headers, [('test', 'value')]) self.assertEqual(response.response.status, 200)
def test_retry_connection(self): connection = Connection(timeout=1, retry_delay=0.2) connection.connection = Mock(request=Mock( side_effect=socket.gaierror(''))) self.assertRaises(socket.gaierror, connection.request, '/') self.assertEqual(connection.connection.request.call_count, 6)
def test_RawResponse_class_read_method(self): """ Test that the RawResponse class includes a response property which exhibits the same properties and methods as httplib.HTTPResponse for backward compat <1.5.0 """ TEST_DATA = "1234abcd" conn = Connection(host="mock.com", port=80, secure=False) conn.connect() with requests_mock.Mocker() as m: m.register_uri( "GET", "http://mock.com/raw_data", text=TEST_DATA, headers={"test": "value"}, ) response = conn.request("/raw_data", raw=True) data = response.response.read() self.assertEqual(data, TEST_DATA) header_value = response.response.getheader("test") self.assertEqual(header_value, "value") headers = response.response.getheaders() self.assertEqual(headers, [("test", "value")]) self.assertEqual(response.response.status, 200)
def test_secure_by_default(self): """ Test that the connection class will default to secure (https) """ conn = Connection(host='localhost', port=8081) conn.connect() self.assertEqual(conn.connection.host, 'https://localhost:8081')
def test_content_length(self): con = Connection() con.connection = Mock() ## GET method # No data, no content length should be present con.request('/test', method='GET', data=None) call_kwargs = con.connection.request.call_args[1] self.assertTrue('Content-Length' not in call_kwargs['headers']) # '' as data, no content length should be present con.request('/test', method='GET', data='') call_kwargs = con.connection.request.call_args[1] self.assertTrue('Content-Length' not in call_kwargs['headers']) # 'a' as data, content length should be present (data in GET is not # corect, but anyways) con.request('/test', method='GET', data='a') call_kwargs = con.connection.request.call_args[1] self.assertEqual(call_kwargs['headers']['Content-Length'], '1') ## POST, PUT method # No data, content length should be present for method in ['POST', 'PUT', 'post', 'put']: con.request('/test', method=method, data=None) call_kwargs = con.connection.request.call_args[1] self.assertEqual(call_kwargs['headers']['Content-Length'], '0') # '' as data, content length should be present for method in ['POST', 'PUT', 'post', 'put']: con.request('/test', method=method, data='') call_kwargs = con.connection.request.call_args[1] self.assertEqual(call_kwargs['headers']['Content-Length'], '0') # No data, raw request, do not touch Content-Length if present for method in ['POST', 'PUT', 'post', 'put']: con.request('/test', method=method, data=None, headers={'Content-Length': '42'}, raw=True) putheader_call_list = con.connection.putheader.call_args_list self.assertIn(call('Content-Length', '42'), putheader_call_list) # '' as data, raw request, do not touch Content-Length if present for method in ['POST', 'PUT', 'post', 'put']: con.request('/test', method=method, data=None, headers={'Content-Length': '42'}, raw=True) putheader_call_list = con.connection.putheader.call_args_list self.assertIn(call('Content-Length', '42'), putheader_call_list) # 'a' as data, content length should be present for method in ['POST', 'PUT', 'post', 'put']: con.request('/test', method=method, data='a') call_kwargs = con.connection.request.call_args[1] self.assertEqual(call_kwargs['headers']['Content-Length'], '1')
def test_retry_with_backoff(self, mock_connect): con = Connection() con.connection = Mock() mock_connect.side_effect = socket.gaierror("") retry_request = Retry(timeout=1, retry_delay=0.1, backoff=1) self.assertRaises(socket.gaierror, retry_request(con.request), action="/") self.assertGreater(mock_connect.call_count, 1, "Retry logic failed")
def test_dont_allow_insecure(self): Connection.allow_insecure = True Connection(secure=False) Connection.allow_insecure = False expected_msg = r"Non https connections are not allowed \(use " r"secure=True\)" assertRaisesRegex(self, ValueError, expected_msg, Connection, secure=False)
def test_dont_allow_insecure(self): Connection.allow_insecure = True Connection(secure=False) Connection.allow_insecure = False expected_msg = (r'Non https connections are not allowed \(use ' 'secure=True\)') self.assertRaisesRegexp(ValueError, expected_msg, Connection, secure=False)
def test_retry_rate_limit_error_timeout(self, mock_connect): con = Connection() con.connection = Mock() mock_connect.__name__ = "mock_connect" headers = {"retry-after": 0.2} mock_connect.side_effect = RateLimitReachedError(headers=headers) retry_request = Retry(timeout=1, retry_delay=0.1, backoff=1) self.assertRaises(RateLimitReachedError, retry_request(con.request), action="/") self.assertGreater(mock_connect.call_count, 1, "Retry logic failed")
def get_response_object(url): parsed_url = urlparse.urlparse(url) parsed_qs = parse_qs(parsed_url.query) secure = parsed_url.scheme == 'https' con = Connection(secure=secure, host=parsed_url.netloc) response = con.request(method='GET', action=parsed_url.path, params=parsed_qs) return response
def test_retry_connection(self): con = Connection(timeout=0.2, retry_delay=0.1) con.connection = Mock() connect_method = 'libcloud.common.base.Connection.request' with patch(connect_method) as mock_connect: try: mock_connect.side_effect = socket.gaierror('') con.request('/') except socket.gaierror: pass
def test_connection_url_merging(self): """ Test that the connection class will parse URLs correctly """ conn = Connection(url='http://test.com/') conn.connect() self.assertEqual(conn.connection.host, 'http://test.com') with requests_mock.mock() as m: m.get('http://test.com/test', text='data') response = conn.request('/test') self.assertEqual(response.body, 'data')
def test_retry_connection_ssl_error(self): conn = Connection(timeout=1, retry_delay=0.1) with patch.object(conn, 'connect', Mock()): with patch.object(conn, 'connection') as connection: connection.request = MagicMock( __name__='request', side_effect=ssl.SSLError(TRANSIENT_SSL_ERROR)) self.assertRaises(ssl.SSLError, conn.request, '/') self.assertGreater(connection.request.call_count, 1)
def test_connection_url_merging(self): """ Test that the connection class will parse URLs correctly """ conn = Connection(url="http://test.com/") conn.connect() self.assertEqual(conn.connection.host, "http://test.com") with requests_mock.mock() as m: m.get("http://test.com/test", text="data") response = conn.request("/test") self.assertEqual(response.body, "data")
def test_retry_rate_limit_error_timeout(self, mock_connect): con = Connection() con.connection = Mock() mock_connect.__name__ = 'mock_connect' headers = {'retry-after': 0.2} mock_connect.side_effect = RateLimitReachedError(headers=headers) retry_request = Retry(timeout=0.4, retry_delay=0.1, backoff=1) self.assertRaises(RateLimitReachedError, retry_request(con.request), action='/') self.assertEqual(mock_connect.call_count, 2, 'Retry logic failed')
def test_connect_with_prefix(self): """ Test that a connection with a base path (e.g. /v1/) will add the base path to requests """ conn = Connection(url='http://test.com/') conn.connect() conn.request_path = '/v1' self.assertEqual(conn.connection.host, 'http://test.com') with requests_mock.mock() as m: m.get('http://test.com/v1/test', text='data') response = conn.request('/test') self.assertEqual(response.body, 'data')
def test_connect_with_prefix(self): """ Test that a connection with a base path (e.g. /v1/) will add the base path to requests """ conn = Connection(url="http://test.com/") conn.connect() conn.request_path = "/v1" self.assertEqual(conn.connection.host, "http://test.com") with requests_mock.mock() as m: m.get("http://test.com/v1/test", text="data") response = conn.request("/test") self.assertEqual(response.body, "data")
def test_debug_log_class_handles_request(self): with StringIO() as fh: libcloud.enable_debug(fh) conn = Connection(url='http://test.com/') conn.connect() self.assertEqual(conn.connection.host, 'http://test.com') with requests_mock.mock() as m: m.get('http://test.com/test', text='data') conn.request('/test') log = fh.getvalue() self.assertTrue(isinstance(conn.connection, LoggingConnection)) self.assertIn('-i -X GET', log) self.assertIn('data', log)
def test_debug_log_class_handles_request(self): with StringIO() as fh: libcloud.enable_debug(fh) conn = Connection(url="http://test.com/") conn.connect() self.assertEqual(conn.connection.host, "http://test.com") with requests_mock.mock() as m: m.get("http://test.com/test", text="data") conn.request("/test") log = fh.getvalue() self.assertTrue(isinstance(conn.connection, LoggingConnection)) self.assertIn("-i -X GET", log) self.assertIn("data", log)
def test_retry_with_backoff(self): con = Connection() con.connection = Mock() connect_method = 'libcloud.common.base.Connection.request' with patch(connect_method) as mock_connect: mock_connect.__name__ = 'mock_connect' with self.assertRaises(socket.gaierror): mock_connect.side_effect = socket.gaierror('') retry_request = retry(timeout=0.2, retry_delay=0.1, backoff=1) retry_request(con.request)(action='/') self.assertGreater(mock_connect.call_count, 1, 'Retry logic failed')