diff options
-rw-r--r-- | tests/test_client.py | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/tests/test_client.py b/tests/test_client.py new file mode 100644 index 0000000..c6452c8 --- /dev/null +++ b/tests/test_client.py @@ -0,0 +1,520 @@ +# Copyright (c) 2010-2012 OpenStack, LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO: More tests +import socket +import unittest +from urlparse import urlparse + +# TODO: mock http connection class with more control over headers +from test.unit.proxy.test_server import fake_http_connect + +from swiftclient import client as c + + +class TestClientException(unittest.TestCase): + + def test_is_exception(self): + self.assertTrue(issubclass(c.ClientException, Exception)) + + def test_format(self): + exc = c.ClientException('something failed') + self.assertTrue('something failed' in str(exc)) + test_kwargs = ( + 'scheme', + 'host', + 'port', + 'path', + 'query', + 'status', + 'reason', + 'device', + ) + for value in test_kwargs: + kwargs = { + 'http_%s' % value: value, + } + exc = c.ClientException('test', **kwargs) + self.assertTrue(value in str(exc)) + + +class TestJsonImport(unittest.TestCase): + + def tearDown(self): + try: + import json + except ImportError: + pass + else: + reload(json) + + try: + import simplejson + except ImportError: + pass + else: + reload(simplejson) + + def test_any(self): + self.assertTrue(hasattr(c, 'json_loads')) + + def test_no_simplejson(self): + # break simplejson + try: + import simplejson + except ImportError: + # not installed, so we don't have to break it for these tests + pass + else: + delattr(simplejson, 'loads') + reload(c) + + try: + from json import loads + except ImportError: + # this case is stested in _no_json + pass + else: + self.assertEquals(loads, c.json_loads) + + +class MockHttpTest(unittest.TestCase): + + def setUp(self): + def fake_http_connection(*args, **kwargs): + _orig_http_connection = c.http_connection + return_read = kwargs.get('return_read') + + def wrapper(url, proxy=None): + parsed, _conn = _orig_http_connection(url, proxy=proxy) + conn = fake_http_connect(*args, **kwargs)() + + def request(*args, **kwargs): + return + conn.request = request + + conn.has_been_read = False + _orig_read = conn.read + + def read(*args, **kwargs): + conn.has_been_read = True + return _orig_read(*args, **kwargs) + conn.read = return_read or read + + return parsed, conn + return wrapper + self.fake_http_connection = fake_http_connection + + def tearDown(self): + reload(c) + + +class TestHttpHelpers(MockHttpTest): + + def test_quote(self): + value = 'standard string' + self.assertEquals('standard%20string', c.quote(value)) + value = u'\u0075nicode string' + self.assertEquals('unicode%20string', c.quote(value)) + + def test_http_connection(self): + url = 'http://www.test.com' + _junk, conn = c.http_connection(url) + self.assertTrue(isinstance(conn, c.HTTPConnection)) + url = 'https://www.test.com' + _junk, conn = c.http_connection(url) + self.assertTrue(isinstance(conn, c.HTTPSConnection)) + url = 'ftp://www.test.com' + self.assertRaises(c.ClientException, c.http_connection, url) + + def test_json_request(self): + def read(*args, **kwargs): + body = {'a': '1', + 'b': '2'} + return c.json_dumps(body) + c.http_connection = self.fake_http_connection(200, return_read=read) + url = 'http://www.test.com' + _junk, conn = c.json_request('GET', url, body={'username': 'user1', + 'password': 'secure'}) + self.assertTrue(type(conn) is dict) + +# TODO: following tests are placeholders, need more tests, better coverage + + +class TestGetAuth(MockHttpTest): + + def test_ok(self): + c.http_connection = self.fake_http_connection(200) + url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf') + self.assertEquals(url, None) + self.assertEquals(token, None) + + def test_auth_v1(self): + c.http_connection = self.fake_http_connection(200) + url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf', + auth_version="1.0") + self.assertEquals(url, None) + self.assertEquals(token, None) + + def test_auth_v2(self): + def read(*args, **kwargs): + acct_url = 'http://127.0.01/AUTH_FOO' + body = {'access': {'serviceCatalog': + [{u'endpoints': [{'publicURL': acct_url}], + 'type': 'object-store'}], + 'token': {'id': 'XXXXXXX'}}} + return c.json_dumps(body) + c.http_connection = self.fake_http_connection(200, return_read=read) + url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf', + tenant_name='asdf', auth_version="2.0") + self.assertTrue(url.startswith("http")) + self.assertTrue(token) + + def test_auth_v2_no_tenant_name(self): + def read(*args, **kwargs): + acct_url = 'http://127.0.01/AUTH_FOO' + body = {'access': {'serviceCatalog': + [{u'endpoints': [{'publicURL': acct_url}], + 'type': 'object-store'}], + 'token': {'id': 'XXXXXXX'}}} + return c.json_dumps(body) + c.http_connection = self.fake_http_connection(200, return_read=read) + self.assertRaises(c.ClientException, c.get_auth, + 'http://www.tests.com', 'asdf', 'asdf', + auth_version='2.0') + + +class TestGetAccount(MockHttpTest): + + def test_no_content(self): + c.http_connection = self.fake_http_connection(204) + value = c.get_account('http://www.test.com', 'asdf')[1] + self.assertEquals(value, []) + + +class TestHeadAccount(MockHttpTest): + + def test_ok(self): + c.http_connection = self.fake_http_connection(200) + value = c.head_account('http://www.tests.com', 'asdf') + # TODO: Hmm. This doesn't really test too much as it uses a fake that + # always returns the same dict. I guess it "exercises" the code, so + # I'll leave it for now. + self.assertEquals(type(value), dict) + + def test_server_error(self): + body = 'c' * 65 + c.http_connection = self.fake_http_connection(500, body=body) + self.assertRaises(c.ClientException, c.head_account, + 'http://www.tests.com', 'asdf') + try: + value = c.head_account('http://www.tests.com', 'asdf') + except c.ClientException as e: + new_body = "[first 60 chars of response] " + body[0:60] + self.assertEquals(e.__str__()[-89:], new_body) + + +class TestGetContainer(MockHttpTest): + + def test_no_content(self): + c.http_connection = self.fake_http_connection(204) + value = c.get_container('http://www.test.com', 'asdf', 'asdf')[1] + self.assertEquals(value, []) + + +class TestHeadContainer(MockHttpTest): + + def test_server_error(self): + body = 'c' * 60 + c.http_connection = self.fake_http_connection(500, body=body) + self.assertRaises(c.ClientException, c.head_container, + 'http://www.test.com', 'asdf', 'asdf', + ) + try: + value = c.head_container('http://www.test.com', 'asdf', 'asdf') + except c.ClientException as e: + self.assertEquals(e.http_response_content, body) + + +class TestPutContainer(MockHttpTest): + + def test_ok(self): + c.http_connection = self.fake_http_connection(200) + value = c.put_container('http://www.test.com', 'asdf', 'asdf') + self.assertEquals(value, None) + + def test_server_error(self): + body = 'c' * 60 + c.http_connection = self.fake_http_connection(500, body=body) + self.assertRaises(c.ClientException, c.put_container, + 'http://www.test.com', 'asdf', 'asdf', + ) + try: + value = c.put_container('http://www.test.com', 'asdf', 'asdf') + except c.ClientException as e: + self.assertEquals(e.http_response_content, body) + + +class TestDeleteContainer(MockHttpTest): + + def test_ok(self): + c.http_connection = self.fake_http_connection(200) + value = c.delete_container('http://www.test.com', 'asdf', 'asdf') + self.assertEquals(value, None) + + +class TestGetObject(MockHttpTest): + + def test_server_error(self): + c.http_connection = self.fake_http_connection(500) + self.assertRaises(c.ClientException, c.get_object, + 'http://www.test.com', 'asdf', 'asdf', 'asdf') + + +class TestHeadObject(MockHttpTest): + + def test_server_error(self): + c.http_connection = self.fake_http_connection(500) + self.assertRaises(c.ClientException, c.head_object, + 'http://www.test.com', 'asdf', 'asdf', 'asdf') + + +class TestPutObject(MockHttpTest): + + def test_ok(self): + c.http_connection = self.fake_http_connection(200) + args = ('http://www.test.com', 'asdf', 'asdf', 'asdf', 'asdf') + value = c.put_object(*args) + self.assertTrue(isinstance(value, basestring)) + + def test_server_error(self): + body = 'c' * 60 + c.http_connection = self.fake_http_connection(500, body=body) + args = ('http://www.test.com', 'asdf', 'asdf', 'asdf', 'asdf') + self.assertRaises(c.ClientException, c.put_object, *args) + try: + value = c.put_object(*args) + except c.ClientException as e: + self.assertEquals(e.http_response_content, body) + + +class TestPostObject(MockHttpTest): + + def test_ok(self): + c.http_connection = self.fake_http_connection(200) + args = ('http://www.test.com', 'asdf', 'asdf', 'asdf', {}) + value = c.post_object(*args) + + def test_server_error(self): + body = 'c' * 60 + c.http_connection = self.fake_http_connection(500, body=body) + args = ('http://www.test.com', 'asdf', 'asdf', 'asdf', {}) + self.assertRaises(c.ClientException, c.post_object, *args) + try: + value = c.post_object(*args) + except c.ClientException as e: + self.assertEquals(e.http_response_content, body) + + +class TestDeleteObject(MockHttpTest): + + def test_ok(self): + c.http_connection = self.fake_http_connection(200) + value = c.delete_object('http://www.test.com', 'asdf', 'asdf', 'asdf') + + def test_server_error(self): + c.http_connection = self.fake_http_connection(500) + self.assertRaises(c.ClientException, c.delete_object, + 'http://www.test.com', 'asdf', 'asdf', 'asdf') + + +class TestConnection(MockHttpTest): + + def test_instance(self): + conn = c.Connection('http://www.test.com', 'asdf', 'asdf') + self.assertEquals(conn.retries, 5) + + def test_retry(self): + c.http_connection = self.fake_http_connection(500) + + def quick_sleep(*args): + pass + c.sleep = quick_sleep + conn = c.Connection('http://www.test.com', 'asdf', 'asdf') + self.assertRaises(c.ClientException, conn.head_account) + self.assertEquals(conn.attempts, conn.retries + 1) + + def test_resp_read_on_server_error(self): + c.http_connection = self.fake_http_connection(500) + conn = c.Connection('http://www.test.com', 'asdf', 'asdf', retries=0) + + def get_auth(*args, **kwargs): + return 'http://www.new.com', 'new' + conn.get_auth = get_auth + self.url, self.token = conn.get_auth() + + method_signatures = ( + (conn.head_account, []), + (conn.get_account, []), + (conn.head_container, ('asdf',)), + (conn.get_container, ('asdf',)), + (conn.put_container, ('asdf',)), + (conn.delete_container, ('asdf',)), + (conn.head_object, ('asdf', 'asdf')), + (conn.get_object, ('asdf', 'asdf')), + (conn.put_object, ('asdf', 'asdf', 'asdf')), + (conn.post_object, ('asdf', 'asdf', {})), + (conn.delete_object, ('asdf', 'asdf')), + ) + + for method, args in method_signatures: + self.assertRaises(c.ClientException, method, *args) + try: + self.assertTrue(conn.http_conn[1].has_been_read) + except AssertionError: + msg = '%s did not read resp on server error' % method.__name__ + self.fail(msg) + except Exception, e: + raise e.__class__("%s - %s" % (method.__name__, e)) + + def test_reauth(self): + c.http_connection = self.fake_http_connection(401) + + def get_auth(*args, **kwargs): + return 'http://www.new.com', 'new' + + def swap_sleep(*args): + self.swap_sleep_called = True + c.get_auth = get_auth + c.http_connection = self.fake_http_connection(200) + c.sleep = swap_sleep + self.swap_sleep_called = False + + conn = c.Connection('http://www.test.com', 'asdf', 'asdf', + preauthurl='http://www.old.com', + preauthtoken='old', + ) + + self.assertEquals(conn.attempts, 0) + self.assertEquals(conn.url, 'http://www.old.com') + self.assertEquals(conn.token, 'old') + + value = conn.head_account() + + self.assertTrue(self.swap_sleep_called) + self.assertEquals(conn.attempts, 2) + self.assertEquals(conn.url, 'http://www.new.com') + self.assertEquals(conn.token, 'new') + + def test_reset_stream(self): + + class LocalContents(object): + + def __init__(self, tell_value=0): + self.already_read = False + self.seeks = [] + self.tell_value = tell_value + + def tell(self): + return self.tell_value + + def seek(self, position): + self.seeks.append(position) + self.already_read = False + + def read(self, size=-1): + if self.already_read: + return '' + else: + self.already_read = True + return 'abcdef' + + class LocalConnection(object): + + def __init__(self, parsed_url=None): + self.reason = "" + if parsed_url: + self.host = parsed_url.netloc + self.port = parsed_url.netloc + + def putrequest(self, *args, **kwargs): + return + + def putheader(self, *args, **kwargs): + return + + def endheaders(self, *args, **kwargs): + return + + def send(self, *args, **kwargs): + raise socket.error('oops') + + def request(self, *args, **kwargs): + return + + def getresponse(self, *args, **kwargs): + self.status = 200 + return self + + def getheader(self, *args, **kwargs): + return 'header' + + def read(self, *args, **kwargs): + return '' + + def local_http_connection(url, proxy=None): + parsed = urlparse(url) + return parsed, LocalConnection() + + orig_conn = c.http_connection + try: + c.http_connection = local_http_connection + conn = c.Connection('http://www.example.com', 'asdf', 'asdf', + retries=1, starting_backoff=.0001) + + contents = LocalContents() + exc = None + try: + conn.put_object('c', 'o', contents) + except socket.error, err: + exc = err + self.assertEquals(contents.seeks, [0]) + self.assertEquals(str(exc), 'oops') + + contents = LocalContents(tell_value=123) + exc = None + try: + conn.put_object('c', 'o', contents) + except socket.error, err: + exc = err + self.assertEquals(contents.seeks, [123]) + self.assertEquals(str(exc), 'oops') + + contents = LocalContents() + contents.tell = None + exc = None + try: + conn.put_object('c', 'o', contents) + except c.ClientException, err: + exc = err + self.assertEquals(contents.seeks, []) + self.assertEquals(str(exc), "put_object('c', 'o', ...) failure " + "and no ability to reset contents for reupload.") + finally: + c.http_connection = orig_conn + + +if __name__ == '__main__': + unittest.main() |