Esempio n. 1
0
 def setUp(self):
     self.url = 'https://www.example.com/pithos'
     self.token = 'p17h0570k3n'
     self.client = SC(self.url, self.token)
     self.client.account = user_id
     self.client.container = 'c0nt@1n3r_i'
Esempio n. 2
0
 def setUp(self):
     self.url = 'https://www.example.com/pithos'
     self.token = 'p17h0570k3n'
     self.client = SC(self.url, self.token)
     self.client.account = user_id
     self.client.container = 'c0nt@1n3r_i'
Esempio n. 3
0
class StorageClient(TestCase):

    files = []

    def _create_temp_file(self, num_of_blocks):
        self.files.append(NamedTemporaryFile())
        tmpFile = self.files[-1]
        file_size = num_of_blocks * 4 * 1024 * 1024
        print('\n\tCreate tmp file')
        tmpFile.write(urandom(file_size))
        tmpFile.flush()
        tmpFile.seek(0)
        print('\t\tDone')
        return tmpFile

    def assert_dicts_are_equal(self, d1, d2):
        for k, v in d1.items():
            self.assertTrue(k in d2)
            if isinstance(v, dict):
                self.assert_dicts_are_equal(v, d2[k])
            else:
                self.assertEqual(unicode(v), unicode(d2[k]))

    def setUp(self):
        self.url = 'https://www.example.com/pithos'
        self.token = 'p17h0570k3n'
        self.client = SC(self.url, self.token)
        self.client.account = user_id
        self.client.container = 'c0nt@1n3r_i'

    def tearDown(self):
        FR.headers = dict()
        FR.status_code = 200
        FR.json = dict()
        FR.content = FR.json
        for f in self.files:
            f.close()

    #  Pithos+ methods that extend storage API

    @patch('%s.head' % client_pkg, return_value=FR())
    def test_get_account_info(self, head):
        FR.headers = account_info
        r = self.client.get_account_info()
        self.assert_dicts_are_equal(account_info, r)
        head.assert_called_once_with('/%s' % self.client.account,
                                     success=(204, 401))
        FR.status_code = 401
        self.assertRaises(ClientError, self.client.get_account_info)

    @patch('%s.post' % storage_pkg, return_value=FR())
    @patch('%s.set_header' % storage_pkg)
    def test_replace_account_meta(self, SH, post):
        metas = dict(k1='v1', k2='v2', k3='v3')
        self.client.replace_account_meta(metas)
        prfx = 'X-Account-Meta-'
        expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
        self.assertEqual(SH.mock_calls, expected)
        post.assert_called_once_with('/%s' % self.client.account, success=202)

    @patch('%s.post' % storage_pkg, return_value=FR())
    @patch('%s.get_account_info' % storage_pkg, return_value=account_info)
    def test_del_account_meta(self, GAI, post):
        prfx = 'x-account-meta-'
        keys = [k[len(prfx):] for k in account_info if k.startswith(prfx)]
        for key in keys:
            self.client.del_account_meta(key)
            self.assertEqual(post.mock_calls[-1],
                             call('/%s' % self.client.account, success=202))
        self.assertEqual(len(keys), len(post.mock_calls))
        self.assertRaises(ClientError, self.client.del_account_meta, 'k4')

    @patch('%s.put' % storage_pkg, return_value=FR())
    def test_create_container(self, put):
        cont = 's0m3c0n731n3r'
        self.client.create_container(cont)
        args = (user_id, cont)
        put.assert_called_once_with('/%s/%s' % args, success=(201, 202))
        FR.status_code = 202
        self.assertRaises(ClientError, self.client.create_container, cont)

    @patch('%s.head' % storage_pkg, return_value=FR())
    def test_get_container_info(self, head):
        FR.headers = container_info
        cont = self.client.container
        r = self.client.get_container_info(cont)
        self.assert_dicts_are_equal(r, container_info)
        path = '/%s/%s' % (self.client.account, cont)
        head.assert_called_once_with(path, success=(204, 404))
        FR.status_code = 404
        self.assertRaises(ClientError, self.client.get_container_info, cont)

    @patch('%s.delete' % storage_pkg, return_value=FR())
    def test_delete_container(self, delete):
        FR.status_code = 204
        cont = 's0m3c0n731n3r'
        self.client.delete_container(cont)
        for err_code in (404, 409):
            FR.status_code = err_code
            self.assertRaises(ClientError, self.client.delete_container, cont)
        acall = call('/%s/%s' % (user_id, cont), success=(204, 404, 409))
        self.assertEqual(delete.mock_calls, [acall] * 3)

    @patch('%s.get' % storage_pkg, return_value=FR())
    @patch('%s.set_param' % storage_pkg)
    def test_list_containers(self, SP, get):
        FR.json, acc = container_list, self.client.account
        r = self.client.list_containers()
        SP.assert_called_once_with('format', 'json')
        get.assert_called_once_with('/%s' % acc, success=(200, 204))
        for i in range(len(r)):
            self.assert_dicts_are_equal(r[i], container_list[i])

    @patch('%s.put' % storage_pkg, return_value=FR())
    def test_upload_object(self, put):
        (acc, cont) = (self.client.account, self.client.container)
        num_of_blocks = 4
        tmpFile = self._create_temp_file(num_of_blocks)
        tmpFile.seek(0, 2)
        sizes = [None, (tmpFile.tell() / num_of_blocks) / 2]
        for size in sizes:
            tmpFile.seek(0)
            self.client.upload_object(obj, tmpFile, size)
            tmpFile.seek(0)
            self.assertEqual(
                put.mock_calls[-1],
                call('/%s/%s/%s' % (acc, cont, obj),
                     data=tmpFile.read(size) if size else tmpFile.read(),
                     success=201))

    @patch('%s.put' % storage_pkg, return_value=FR())
    @patch('%s.set_header' % storage_pkg)
    def test_create_object(self, SH, put):
        cont = self.client.container
        ctype = 'c0n73n7/typ3'
        exp_shd = [
            call('Content-Type', 'application/octet-stream'),
            call('Content-length', '0'),
            call('Content-Type', ctype),
            call('Content-length', '42')
        ]
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)] * 2
        self.client.create_object(obj)
        self.client.create_object(obj, content_type=ctype, content_length=42)
        self.assertEqual(SH.mock_calls, exp_shd)
        self.assertEqual(put.mock_calls, exp_put)

    @patch('%s.put' % storage_pkg, return_value=FR())
    @patch('%s.set_header' % client_pkg)
    def test_create_directory(self, SH, put):
        cont = self.client.container
        exp_shd = [
            call('Content-Type', 'application/directory'),
            call('Content-length', '0')
        ]
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)]
        self.client.create_directory(obj)
        self.assertEqual(SH.mock_calls, exp_shd)
        self.assertEqual(put.mock_calls, exp_put)

    @patch('%s.head' % storage_pkg, return_value=FR())
    def test_get_object_info(self, head):
        FR.headers = object_info
        path = '/%s/%s/%s' % (self.client.account, self.client.container, obj)
        r = self.client.get_object_info(obj)
        head.assert_called_once_with(path, success=200)
        self.assertEqual(r, object_info)

    @patch('%s.get_object_info' % storage_pkg, return_value=object_info)
    def test_get_object_meta(self, GOI):
        r = self.client.get_object_meta(obj)
        GOI.assert_called_once_with(obj)
        prfx = 'x-object-meta-'
        for k in [k for k in object_info if k.startswith(prfx)]:
            self.assertEqual(r.pop(k[len(prfx):]), object_info[k])
        self.assertFalse(len(r))

    @patch('%s.post' % storage_pkg, return_value=FR())
    @patch('%s.set_header' % storage_pkg)
    def test_del_object_meta(self, SH, post):
        key = '50m3m3t4k3y'
        self.client.del_object_meta(obj, key)
        prfx = 'X-Object-Meta-'
        SH.assert_called_once_with('%s%s' % (prfx, key), '')
        exp = '/%s/%s/%s' % (self.client.account, self.client.container, obj)
        post.assert_called_once_with(exp, success=202)

    @patch('%s.post' % client_pkg, return_value=FR())
    @patch('%s.set_header' % client_pkg)
    def test_replace_object_meta(self, SH, post):
        metas = dict(k1='new1', k2='new2', k3='new3')
        cont = self.client.container
        self.client.replace_object_meta(metas)
        post.assert_called_once_with('/%s/%s' % (user_id, cont), success=202)
        prfx = 'X-Object-Meta-'
        expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
        self.assertEqual(SH.mock_calls, expected)

    @patch('%s.put' % storage_pkg, return_value=FR())
    @patch('%s.set_header' % storage_pkg)
    def test_copy_object(self, SH, put):
        src_cont = 'src-c0nt41n3r'
        src_obj = 'src-0bj'
        dst_cont = 'dst-c0nt41n3r'
        for dst_obj in (None, 'dst-0bj'):
            dst_path = dst_obj or src_obj
            path = '/%s/%s/%s' % (self.client.account, dst_cont, dst_path)
            self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
            self.assertEqual(put.mock_calls[-1], call(path, success=201))
            kwargs = {
                'X-Copy-From': '/%s/%s' % (src_cont, src_obj),
                'Content-Length': 0
            }
            self.assertEqual(SH.mock_calls[-2:],
                             [call(k, v) for k, v in kwargs.items()])

    @patch('%s.put' % storage_pkg, return_value=FR())
    @patch('%s.set_header' % storage_pkg)
    def test_move_object(self, SH, put):
        src_cont = 'src-c0nt41n3r'
        src_obj = 'src-0bj'
        dst_cont = 'dst-c0nt41n3r'
        for dst_obj in (None, 'dst-0bj'):
            dst_path = dst_obj or src_obj
            path = '/%s/%s/%s' % (self.client.account, dst_cont, dst_path)
            self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
            self.assertEqual(put.mock_calls[-1], call(path, success=201))
            kwargs = {
                'X-Move-From': '/%s/%s' % (src_cont, src_obj),
                'Content-Length': 0
            }
            self.assertEqual(SH.mock_calls[-2:],
                             [call(k, v) for k, v in kwargs.items()])

    @patch('%s.delete' % storage_pkg, return_value=FR())
    def test_delete_object(self, delete):
        cont = self.client.container
        self.client.delete_object(obj)
        exp = '/%s/%s/%s' % (user_id, cont, obj)
        delete.assert_called_once_with(exp, success=(204, 404))
        FR.status_code = 404
        self.assertRaises(ClientError, self.client.delete_object, obj)

    @patch('%s.get' % client_pkg, return_value=FR())
    @patch('%s.set_param' % client_pkg)
    def test_list_objects(self, SP, get):
        FR.json = object_list
        acc, cont = self.client.account, self.client.container
        r = self.client.list_objects()
        for i in range(len(r)):
            self.assert_dicts_are_equal(r[i], object_list[i])
        exp = '/%s/%s' % (acc, cont)
        get.assert_called_once_with(exp, success=(200, 204, 304, 404))
        self.assertEqual(SP.mock_calls, [
            call('format', 'json'),
            call('limit', None, iff=None),
            call('marker', None, iff=None),
            call('prefix', None, iff=None),
            call('delimiter', None, iff=None)
        ])
        self.client.list_objects(format='xml',
                                 limit=10,
                                 marker='X',
                                 path='/lala')
        self.assertEqual(SP.mock_calls[-4:], [
            call('format', 'xml'),
            call('limit', 10, iff=10),
            call('marker', 'X', iff='X'),
            call('path', '/lala')
        ])
        self.client.list_objects(delimiter='X', prefix='/lala')
        self.assertEqual(SP.mock_calls[-5:], [
            call('format', 'json'),
            call('limit', None, iff=None),
            call('marker', None, iff=None),
            call('prefix', '/lala', iff='/lala'),
            call('delimiter', 'X', iff='X'),
        ])
        FR.status_code = 304
        self.assertEqual(self.client.list_objects(), [])
        FR.status_code = 404
        self.assertRaises(ClientError, self.client.list_objects)

    @patch('%s.get' % client_pkg, return_value=FR())
    @patch('%s.set_param' % client_pkg)
    def test_list_objects_in_path(self, SP, get):
        FR.json = object_list
        path = '/some/awsome/path'
        acc, cont = self.client.account, self.client.container
        self.client.list_objects_in_path(path)
        exp = '/%s/%s' % (acc, cont)
        get.assert_called_once_with(exp, success=(200, 204, 404))
        self.assertEqual(SP.mock_calls,
                         [call('format', 'json'),
                          call('path', path)])
        FR.status_code = 404
        self.assertRaises(ClientError, self.client.list_objects)
Esempio n. 4
0
class StorageClient(TestCase):

    files = []

    def _create_temp_file(self, num_of_blocks):
        self.files.append(NamedTemporaryFile())
        tmpFile = self.files[-1]
        file_size = num_of_blocks * 4 * 1024 * 1024
        print('\n\tCreate tmp file')
        tmpFile.write(urandom(file_size))
        tmpFile.flush()
        tmpFile.seek(0)
        print('\t\tDone')
        return tmpFile

    def assert_dicts_are_equal(self, d1, d2):
        for k, v in d1.items():
            self.assertTrue(k in d2)
            if isinstance(v, dict):
                self.assert_dicts_are_equal(v, d2[k])
            else:
                self.assertEqual(unicode(v), unicode(d2[k]))

    def setUp(self):
        self.url = 'https://www.example.com/pithos'
        self.token = 'p17h0570k3n'
        self.client = SC(self.url, self.token)
        self.client.account = user_id
        self.client.container = 'c0nt@1n3r_i'

    def tearDown(self):
        FR.headers = dict()
        FR.status_code = 200
        FR.json = dict()
        FR.content = FR.json
        for f in self.files:
            f.close()

    #  Pithos+ methods that extend storage API

    @patch('%s.head' % client_pkg, return_value=FR())
    def test_get_account_info(self, head):
        FR.headers = account_info
        r = self.client.get_account_info()
        self.assert_dicts_are_equal(account_info, r)
        head.assert_called_once_with(
            '/%s' % self.client.account,
            success=(204, 401))
        FR.status_code = 401
        self.assertRaises(ClientError, self.client.get_account_info)

    @patch('%s.post' % storage_pkg, return_value=FR())
    @patch('%s.set_header' % storage_pkg)
    def test_replace_account_meta(self, SH, post):
        metas = dict(k1='v1', k2='v2', k3='v3')
        self.client.replace_account_meta(metas)
        prfx = 'X-Account-Meta-'
        expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
        self.assertEqual(SH.mock_calls, expected)
        post.assert_called_once_with('/%s' % self.client.account, success=202)

    @patch('%s.post' % storage_pkg, return_value=FR())
    @patch('%s.get_account_info' % storage_pkg, return_value=account_info)
    def test_del_account_meta(self, GAI, post):
        prfx = 'x-account-meta-'
        keys = [k[len(prfx):] for k in account_info if k.startswith(prfx)]
        for key in keys:
            self.client.del_account_meta(key)
            self.assertEqual(
                post.mock_calls[-1],
                call('/%s' % self.client.account, success=202))
        self.assertEqual(len(keys), len(post.mock_calls))
        self.assertRaises(ClientError, self.client.del_account_meta, 'k4')

    @patch('%s.put' % storage_pkg, return_value=FR())
    def test_create_container(self, put):
        cont = 's0m3c0n731n3r'
        self.client.create_container(cont)
        args = (user_id, cont)
        put.assert_called_once_with('/%s/%s' % args, success=(201, 202))
        FR.status_code = 202
        self.assertRaises(ClientError, self.client.create_container, cont)

    @patch('%s.head' % storage_pkg, return_value=FR())
    def test_get_container_info(self, head):
        FR.headers = container_info
        cont = self.client.container
        r = self.client.get_container_info(cont)
        self.assert_dicts_are_equal(r, container_info)
        path = '/%s/%s' % (self.client.account, cont)
        head.assert_called_once_with(path, success=(204, 404))
        FR.status_code = 404
        self.assertRaises(ClientError, self.client.get_container_info, cont)

    @patch('%s.delete' % storage_pkg, return_value=FR())
    def test_delete_container(self, delete):
        FR.status_code = 204
        cont = 's0m3c0n731n3r'
        self.client.delete_container(cont)
        for err_code in (404, 409):
            FR.status_code = err_code
            self.assertRaises(ClientError, self.client.delete_container, cont)
        acall = call('/%s/%s' % (user_id, cont), success=(204, 404, 409))
        self.assertEqual(delete.mock_calls, [acall] * 3)

    @patch('%s.get' % storage_pkg, return_value=FR())
    @patch('%s.set_param' % storage_pkg)
    def test_list_containers(self, SP, get):
        FR.json, acc = container_list, self.client.account
        r = self.client.list_containers()
        SP.assert_called_once_with('format', 'json')
        get.assert_called_once_with('/%s' % acc, success=(200, 204))
        for i in range(len(r)):
            self.assert_dicts_are_equal(r[i], container_list[i])

    @patch('%s.put' % storage_pkg, return_value=FR())
    def test_upload_object(self, put):
        (acc, cont) = (self.client.account, self.client.container)
        num_of_blocks = 4
        tmpFile = self._create_temp_file(num_of_blocks)
        tmpFile.seek(0, 2)
        sizes = [None, (tmpFile.tell() / num_of_blocks) / 2]
        for size in sizes:
            tmpFile.seek(0)
            self.client.upload_object(obj, tmpFile, size)
            tmpFile.seek(0)
            self.assertEqual(put.mock_calls[-1], call(
                '/%s/%s/%s' % (acc, cont, obj),
                data=tmpFile.read(size) if size else tmpFile.read(),
                success=201))

    @patch('%s.put' % storage_pkg, return_value=FR())
    @patch('%s.set_header' % storage_pkg)
    def test_create_object(self, SH, put):
        cont = self.client.container
        ctype = 'c0n73n7/typ3'
        exp_shd = [
            call('Content-Type', 'application/octet-stream'),
            call('Content-length', '0'),
            call('Content-Type', ctype), call('Content-length', '42')]
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)] * 2
        self.client.create_object(obj)
        self.client.create_object(obj, content_type=ctype, content_length=42)
        self.assertEqual(SH.mock_calls, exp_shd)
        self.assertEqual(put.mock_calls, exp_put)

    @patch('%s.put' % storage_pkg, return_value=FR())
    @patch('%s.set_header' % client_pkg)
    def test_create_directory(self, SH, put):
        cont = self.client.container
        exp_shd = [
            call('Content-Type', 'application/directory'),
            call('Content-length', '0')]
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)]
        self.client.create_directory(obj)
        self.assertEqual(SH.mock_calls, exp_shd)
        self.assertEqual(put.mock_calls, exp_put)

    @patch('%s.head' % storage_pkg, return_value=FR())
    def test_get_object_info(self, head):
        FR.headers = object_info
        path = '/%s/%s/%s' % (self.client.account, self.client.container, obj)
        r = self.client.get_object_info(obj)
        head.assert_called_once_with(path, success=200)
        self.assertEqual(r, object_info)

    @patch('%s.get_object_info' % storage_pkg, return_value=object_info)
    def test_get_object_meta(self, GOI):
        r = self.client.get_object_meta(obj)
        GOI.assert_called_once_with(obj)
        prfx = 'x-object-meta-'
        for k in [k for k in object_info if k.startswith(prfx)]:
            self.assertEqual(r.pop(k[len(prfx):]), object_info[k])
        self.assertFalse(len(r))

    @patch('%s.post' % storage_pkg, return_value=FR())
    @patch('%s.set_header' % storage_pkg)
    def test_del_object_meta(self, SH, post):
        key = '50m3m3t4k3y'
        self.client.del_object_meta(obj, key)
        prfx = 'X-Object-Meta-'
        SH.assert_called_once_with('%s%s' % (prfx, key), '')
        exp = '/%s/%s/%s' % (self.client.account, self.client.container, obj)
        post.assert_called_once_with(exp, success=202)

    @patch('%s.post' % client_pkg, return_value=FR())
    @patch('%s.set_header' % client_pkg)
    def test_replace_object_meta(self, SH, post):
        metas = dict(k1='new1', k2='new2', k3='new3')
        cont = self.client.container
        self.client.replace_object_meta(metas)
        post.assert_called_once_with('/%s/%s' % (user_id, cont), success=202)
        prfx = 'X-Object-Meta-'
        expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
        self.assertEqual(SH.mock_calls, expected)

    @patch('%s.put' % storage_pkg, return_value=FR())
    @patch('%s.set_header' % storage_pkg)
    def test_copy_object(self, SH, put):
        src_cont = 'src-c0nt41n3r'
        src_obj = 'src-0bj'
        dst_cont = 'dst-c0nt41n3r'
        for dst_obj in (None, 'dst-0bj'):
            dst_path = dst_obj or src_obj
            path = '/%s/%s/%s' % (self.client.account, dst_cont, dst_path)
            self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
            self.assertEqual(put.mock_calls[-1], call(path, success=201))
            kwargs = {
                'X-Copy-From': '/%s/%s' % (src_cont, src_obj),
                'Content-Length': 0}
            self.assertEqual(
                SH.mock_calls[-2:],
                [call(k, v) for k, v in kwargs.items()])

    @patch('%s.put' % storage_pkg, return_value=FR())
    @patch('%s.set_header' % storage_pkg)
    def test_move_object(self, SH, put):
        src_cont = 'src-c0nt41n3r'
        src_obj = 'src-0bj'
        dst_cont = 'dst-c0nt41n3r'
        for dst_obj in (None, 'dst-0bj'):
            dst_path = dst_obj or src_obj
            path = '/%s/%s/%s' % (self.client.account, dst_cont, dst_path)
            self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
            self.assertEqual(put.mock_calls[-1], call(path, success=201))
            kwargs = {
                'X-Move-From': '/%s/%s' % (src_cont, src_obj),
                'Content-Length': 0}
            self.assertEqual(
                SH.mock_calls[-2:],
                [call(k, v) for k, v in kwargs.items()])

    @patch('%s.delete' % storage_pkg, return_value=FR())
    def test_delete_object(self, delete):
        cont = self.client.container
        self.client.delete_object(obj)
        exp = '/%s/%s/%s' % (user_id, cont, obj)
        delete.assert_called_once_with(exp, success=(204, 404))
        FR.status_code = 404
        self.assertRaises(ClientError, self.client.delete_object, obj)

    @patch('%s.get' % client_pkg, return_value=FR())
    @patch('%s.set_param' % client_pkg)
    def test_list_objects(self, SP, get):
        FR.json = object_list
        acc, cont = self.client.account, self.client.container
        r = self.client.list_objects()
        for i in range(len(r)):
            self.assert_dicts_are_equal(r[i], object_list[i])
        exp = '/%s/%s' % (acc, cont)
        get.assert_called_once_with(exp, success=(200, 204, 304, 404))
        self.assertEqual(SP.mock_calls, [
            call('format', 'json'),
            call('limit', None, iff=None),
            call('marker', None, iff=None),
            call('prefix', None, iff=None),
            call('delimiter', None, iff=None)])
        self.client.list_objects(
            format='xml', limit=10, marker='X', path='/lala')
        self.assertEqual(SP.mock_calls[-4:], [
            call('format', 'xml'),
            call('limit', 10, iff=10),
            call('marker', 'X', iff='X'),
            call('path', '/lala')])
        self.client.list_objects(delimiter='X', prefix='/lala')
        self.assertEqual(SP.mock_calls[-5:], [
            call('format', 'json'),
            call('limit', None, iff=None),
            call('marker', None, iff=None),
            call('prefix', '/lala', iff='/lala'),
            call('delimiter', 'X', iff='X'),
            ])
        FR.status_code = 304
        self.assertEqual(self.client.list_objects(), [])
        FR.status_code = 404
        self.assertRaises(ClientError, self.client.list_objects)

    @patch('%s.get' % client_pkg, return_value=FR())
    @patch('%s.set_param' % client_pkg)
    def test_list_objects_in_path(self, SP, get):
        FR.json = object_list
        path = '/some/awsome/path'
        acc, cont = self.client.account, self.client.container
        self.client.list_objects_in_path(path)
        exp = '/%s/%s' % (acc, cont)
        get.assert_called_once_with(exp, success=(200, 204, 404))
        self.assertEqual(
            SP.mock_calls,
            [call('format', 'json'), call('path', path)])
        FR.status_code = 404
        self.assertRaises(ClientError, self.client.list_objects)