Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / storage / test.py @ 96befbfd

History | View | Annotate | Download (21.1 kB)

1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from unittest import TestCase
35
from mock import patch, call
36
from tempfile import NamedTemporaryFile
37
from os import urandom
38

    
39
from kamaki.clients import ClientError
40
from kamaki.clients.storage import StorageClient as SC
41
from kamaki.clients.connection.kamakicon import KamakiHTTPConnection as C
42

    
43
client_pkg = 'kamaki.clients.Client'
44
storage_pkg = 'kamaki.clients.storage.StorageClient'
45

    
46
user_id = 'ac0un7-1d-5tr1ng'
47
obj = 'obj3c7N4m3'
48

    
49
account_info = {
50
    'content-language': 'en-us',
51
    'content-type': 'text/html; charset=utf-8',
52
    'date': 'Wed, 06 Mar 2013 13:25:51 GMT',
53
    'last-modified': 'Mon, 04 Mar 2013 18:22:31 GMT',
54
    'server': 'gunicorn/0.14.5',
55
    'vary': 'Accept-Language',
56
    'x-account-bytes-used': '751615526',
57
    'x-account-container-count': 7,
58
    'x-account-policy-quota': 53687091200,
59
    'x-account-policy-versioning': 'auto',
60
    'x-account-meta-k1': 'val1',
61
    'x-account-meta-k2': 'val2',
62
    'x-account-meta-k3': 'val3'}
63
container_info = {
64
    'content-language': 'en-us',
65
    'content-type': 'text/html; charset=utf-8',
66
    'date': 'Wed, 06 Mar 2013 15:11:05 GMT',
67
    'last-modified': 'Wed, 27 Feb 2013 15:56:13 GMT',
68
    'server': 'gunicorn/0.14.5',
69
    'vary': 'Accept-Language',
70
    'x-container-block-hash': 'sha256',
71
    'x-container-block-size': 4194304,
72
    'x-container-bytes-used': 309528938,
73
    'x-container-object-count': 14,
74
    'x-container-object-meta': '',
75
    'x-container-policy-quota': 53687091200,
76
    'x-container-policy-versioning': 'auto'}
77
object_info = {
78
    'content-language': 'en-us',
79
    'content-length': 254965,
80
    'content-type': 'application/octet-stream',
81
    'date': 'Thu, 07 Mar 2013 13:27:43 GMT',
82
    'etag': '',
83
    'last-modified': 'Mon, 04 Mar 2013 18:22:31 GMT',
84
    'server': 'gunicorn/0.14.5',
85
    'vary': 'Accept-Language',
86
    'x-object-hash': 'obj3c7h45h1s0bj3c7h45h411r34dY',
87
    'x-object-uuid': 'd0c747ca-34bd-49e0-8e98-1d07d8b0cbc7',
88
    'x-object-version': '525996',
89
    'x-object-version-timestamp': 'Mon, 04 Mar 2013 18:22:31 GMT',
90
    'x-object-meta-k1': 'v1',
91
    'x-object-meta-k2': 'v2'}
92
container_list = [
93
    dict(
94
        count=2,
95
        last_modified="2013-02-27T11:56:09.893033+00:00",
96
        bytes=677076979,
97
        name="pithos",
98
        x_container_policy=dict(quota="21474836480", versioning="auto")),
99
    dict(
100
        count=0,
101
        last_modified="2012-10-23T12:25:17.229187+00:00",
102
        bytes=0,
103
        name="trash",
104
        x_container_policy=dict(quota="21474836480", versioning="auto"))]
105
object_list = [
106
    dict(hash="",
107
        name="The_Secret_Garden.zip",
108
        x_object_public="/public/wdp9p",
109
        bytes=203304947,
110
        x_object_version_timestamp="1360237915.7027509",
111
        x_object_uuid="s0m3uu1df0r0bj0n3",
112
        last_modified="2013-02-07T11:51:55.702751+00:00",
113
        content_type="application/octet-stream",
114
        x_object_hash="0afdf29f71cd53126225c3f54ca",
115
        x_object_version=17737,
116
        x_object_modified_by=user_id),
117
    dict(hash="",
118
        name="The_Revealed_Garden.zip",
119
        x_object_public="/public/wpd7p",
120
        bytes=20330947,
121
        x_object_version_timestamp="13602915.7027509",
122
        x_object_uuid="s0m3uu1df0r0bj70w",
123
        last_modified="2013-02-07T11:51:55.702751+00:00",
124
        content_type="application/octet-stream",
125
        x_object_hash="0afdf29f71cd53126225c3f54ca",
126
        x_object_version=17737,
127
        x_object_modified_by=user_id)]
128
object_hashmap = dict(
129
    block_hash="sha256", block_size=4194304, bytes=33554432,
130
    hashes=[
131
        "4988438cc1c0292c085d289649b28cf547ba3db71c6efaac9f2df7e193d4d0af",
132
        "b214244aa56df7d1df7c6cac066e7cef268d9c2beb4dcf7ce68af667b0626f91",
133
        "17f365f25e0682565ded30576066bb13377a3d306967e4d74e06bb6bbc20f75f",
134
        "2524ae208932669fff89adf8a2fc0df3b67736ca0d3aadce7a2ce640f142af37",
135
        "5d807a2129d2fcd3c221c3da418ed52af3fc48d0817b62e0bb437acffccd3514",
136
        "609de22ce842d997f645fc49d5f14e0e3766dd51a6cbe66383b2bab82c8dfcd0",
137
        "3102851ac168c78be70e35ff5178c7b1ebebd589e5106d565ca1094d1ca8ff59",
138
        "bfe306dd24e92a8d85caf7055643f250fd319e8c4cdd4755ddabbf3ff97e83c7"])
139
sharers = [
140
    dict(last_modified="2013-01-29T16:50:06.084674+00:00", name="0b1a-82d5"),
141
    dict(last_modified="2013-01-29T16:50:06.084674+00:00", name="0b2a-f2d5"),
142
    dict(last_modified="2013-01-29T16:50:06.084674+00:00", name="2b1a-82d6")]
143

    
144

    
145
class FR(object):
146
    """FR stands for Fake Response"""
147
    json = dict()
148
    headers = dict()
149
    content = json
150
    status = None
151
    status_code = 200
152

    
153
    def release(self):
154
        pass
155

    
156

    
157
class Storage(TestCase):
158

    
159
    files = []
160

    
161
    def _create_temp_file(self, num_of_blocks):
162
        self.files.append(NamedTemporaryFile())
163
        tmpFile = self.files[-1]
164
        file_size = num_of_blocks * 4 * 1024 * 1024
165
        print('\n\tCreate tmp file')
166
        tmpFile.write(urandom(file_size))
167
        tmpFile.flush()
168
        tmpFile.seek(0)
169
        print('\t\tDone')
170
        return tmpFile
171

    
172
    def assert_dicts_are_equal(self, d1, d2):
173
        for k, v in d1.items():
174
            self.assertTrue(k in d2)
175
            if isinstance(v, dict):
176
                self.assert_dicts_are_equal(v, d2[k])
177
            else:
178
                self.assertEqual(unicode(v), unicode(d2[k]))
179

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

    
187
    def tearDown(self):
188
        FR.headers = dict()
189
        FR.status_code = 200
190
        FR.json = dict()
191
        FR.content = FR.json
192
        for f in self.files:
193
            f.close()
194

    
195
    #  Pithos+ methods that extend storage API
196

    
197
    @patch('%s.head' % client_pkg, return_value=FR())
198
    def test_get_account_info(self, head):
199
        FR.headers = account_info
200
        r = self.client.get_account_info()
201
        self.assert_dicts_are_equal(account_info, r)
202
        self.assertEqual(
203
            head.mock_calls[-1],
204
            call('/%s' % self.client.account, success=(204, 401)))
205
        FR.status_code = 401
206
        self.assertRaises(ClientError, self.client.get_account_info)
207

    
208
    @patch('%s.post' % storage_pkg, return_value=FR())
209
    @patch('%s.set_header' % storage_pkg)
210
    def test_replace_account_meta(self, SH, post):
211
        metas = dict(k1='v1', k2='v2', k3='v3')
212
        self.client.replace_account_meta(metas)
213
        prfx = 'X-Account-Meta-'
214
        expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
215
        self.assertEqual(SH.mock_calls, expected)
216
        self.assertEqual(
217
            post.mock_calls[-1],
218
            call('/%s' % self.client.account, success=202))
219

    
220
    @patch('%s.post' % storage_pkg, return_value=FR())
221
    @patch('%s.get_account_info' % storage_pkg, return_value=account_info)
222
    def test_del_account_meta(self, GAI, post):
223
        prfx = 'x-account-meta-'
224
        keys = [k[len(prfx):] for k in account_info if k.startswith(prfx)]
225
        for key in keys:
226
            self.client.del_account_meta(key)
227
            self.assertEqual(
228
                post.mock_calls[-1],
229
                call('/%s' % self.client.account, success=202))
230
        self.assertEqual(len(keys), len(post.mock_calls))
231
        self.assertRaises(ClientError, self.client.del_account_meta, 'k4')
232

    
233
    @patch('%s.put' % storage_pkg, return_value=FR())
234
    def test_create_container(self, put):
235
        cont = 's0m3c0n731n3r'
236
        self.client.create_container(cont)
237
        expected = call('/%s/%s' % (user_id, cont), success=(201, 202))
238
        self.assertEqual(put.mock_calls[-1], expected)
239
        FR.status_code = 202
240
        self.assertRaises(ClientError, self.client.create_container, cont)
241

    
242
    """
243
    @patch('%s.container_head' % pithos_pkg, return_value=FR())
244
    def test_get_container_info(self, ch):
245
        FR.headers = container_info
246
        r = self.client.get_container_info()
247
        self.assert_dicts_are_equal(r, container_info)
248
        u = 'some date'
249
        r = self.client.get_container_info(until=u)
250
        self.assertEqual(ch.mock_calls, [call(until=None), call(until=u)])
251

252
    @patch('%s.delete' % pithos_pkg, return_value=FR())
253
    def test_delete_container(self, delete):
254
        FR.status_code = 204
255
        cont = 's0m3c0n731n3r'
256
        self.client.delete_container(cont)
257
        for err_code in (404, 409):
258
            FR.status_code = err_code
259
            self.assertRaises(ClientError, self.client.delete_container, cont)
260
        acall = call('/%s/%s' % (user_id, cont), success=(204, 404, 409))
261
        self.assertEqual(delete.mock_calls, [acall] * 3)
262

263
    @patch('%s.account_get' % pithos_pkg, return_value=FR())
264
    def test_list_containers(self, get):
265
        FR.json = container_list
266
        r = self.client.list_containers()
267
        for i in range(len(r)):
268
            self.assert_dicts_are_equal(r[i], container_list[i])
269

270
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
271
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
272
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
273
    def test_upload_object(self, CI, CP, OP):
274
        num_of_blocks = 8
275
        tmpFile = self._create_temp_file(num_of_blocks)
276

277
        # Without kwargs
278
        self.client.upload_object(obj, tmpFile)
279
        self.assertEqual(PC.get_container_info.mock_calls, [call()])
280
        [call1, call2] = PC.object_put.mock_calls
281

282
        (args1, kwargs1) = call1[1:3]
283
        (args2, kwargs2) = call2[1:3]
284
        self.assertEqual(args1, (obj,))
285
        expected1 = dict(
286
            hashmap=True,
287
            success=(201, 409),
288
            format='json',
289
            json=dict(
290
                hashes=['s0m3h@5h'] * num_of_blocks,
291
                bytes=num_of_blocks * 4 * 1024 * 1024),
292
            etag=None,
293
            content_encoding=None,
294
            content_type='application/octet-stream',
295
            content_disposition=None,
296
            public=None,
297
            permissions=None)
298
        for k, v in expected1.items():
299
            if k == 'json':
300
                self.assertEqual(len(v['hashes']), len(kwargs1[k]['hashes']))
301
                self.assertEqual(v['bytes'], kwargs1[k]['bytes'])
302
            else:
303
                self.assertEqual(v, kwargs1[k])
304

305
        (args2, kwargs2) = call2[1:3]
306
        self.assertEqual(args2, (obj,))
307
        expected2 = dict(
308
            json=dict(
309
                hashes=['s0m3h@5h'] * num_of_blocks,
310
                bytes=num_of_blocks * 4 * 1024 * 1024),
311
            content_type='application/octet-stream',
312
            hashmap=True,
313
            success=201,
314
            format='json')
315
        for k, v in expected2.items():
316
            if k == 'json':
317
                self.assertEqual(len(v['hashes']), len(kwargs2[k]['hashes']))
318
                self.assertEqual(v['bytes'], kwargs2[k]['bytes'])
319
            else:
320
                self.assertEqual(v, kwargs2[k])
321

322
        OP = PC.object_put
323
        mock_offset = 2
324

325
        #  With progress bars
326
        try:
327
            from progress.bar import ShadyBar
328
            blck_bar = ShadyBar('Mock blck calc.')
329
            upld_bar = ShadyBar('Mock uplds')
330
        except ImportError:
331
            blck_bar = None
332
            upld_bar = None
333

334
        if blck_bar and upld_bar:
335

336
            def blck_gen(n):
337
                for i in blck_bar.iter(range(n)):
338
                    yield
339
                yield
340

341
            def upld_gen(n):
342
                for i in upld_bar.iter(range(n)):
343
                    yield
344
                yield
345

346
            tmpFile.seek(0)
347
            self.client.upload_object(
348
                obj, tmpFile,
349
                hash_cb=blck_gen, upload_cb=upld_gen)
350

351
            for i, c in enumerate(OP.mock_calls[-mock_offset:]):
352
                self.assertEqual(OP.mock_calls[i], c)
353

354
        #  With content-type
355
        tmpFile.seek(0)
356
        ctype = 'video/mpeg'
357
        sharing = dict(read=['u1', 'g1', 'u2'], write=['u1'])
358
        self.client.upload_object(obj, tmpFile,
359
            content_type=ctype, sharing=sharing)
360
        self.assertEqual(OP.mock_calls[-1][2]['content_type'], ctype)
361
        self.assert_dicts_are_equal(
362
            OP.mock_calls[-2][2]['permissions'],
363
            sharing)
364

365
        # With other args
366
        tmpFile.seek(0)
367
        kwargs = dict(
368
            etag='s0m3E74g',
369
            content_type=ctype,
370
            content_disposition=ctype + 'd15p051710n',
371
            public=True,
372
            content_encoding='802.11')
373
        self.client.upload_object(obj, tmpFile, **kwargs)
374
        for arg, val in kwargs.items():
375
            self.assertEqual(OP.mock_calls[-2][2][arg], val)
376

377
    @patch('%s.put' % pithos_pkg, return_value=FR())
378
    @patch('%s.set_header' % client_pkg)
379
    def test_create_object(self, SH, put):
380
        cont = self.client.container
381
        ctype = 'c0n73n7/typ3'
382
        exp_shd = [
383
            call('Content-Type', 'application/octet-stream'),
384
            call('Content-length', '0'),
385
            call('Content-Type', ctype), call('Content-length', '42')]
386
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)] * 2
387
        self.client.create_object(obj)
388
        self.client.create_object(obj, content_type=ctype, content_length=42)
389
        self.assertEqual(PC.set_header.mock_calls, exp_shd)
390
        self.assertEqual(put.mock_calls, exp_put)
391

392
    @patch('%s.put' % pithos_pkg, return_value=FR())
393
    @patch('%s.set_header' % client_pkg)
394
    def test_create_directory(self, SH, put):
395
        cont = self.client.container
396
        exp_shd = [
397
            call('Content-Type', 'application/directory'),
398
            call('Content-length', '0')]
399
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)]
400
        self.client.create_directory(obj)
401
        self.assertEqual(PC.set_header.mock_calls, exp_shd)
402
        self.assertEqual(put.mock_calls, exp_put)
403

404
    def test_get_object_info(self):
405
        FR.headers = object_info
406
        version = 'v3r510n'
407
        with patch.object(PC, 'object_head', return_value=FR()) as head:
408
            r = self.client.get_object_info(obj)
409
            self.assertEqual(r, object_info)
410
            r = self.client.get_object_info(obj, version=version)
411
            self.assertEqual(head.mock_calls, [
412
                call(obj, version=None),
413
                call(obj, version=version)])
414
        with patch.object(
415
                PC,
416
                'object_head',
417
                side_effect=ClientError('Obj not found', 404)):
418
            self.assertRaises(
419
                ClientError,
420
                self.client.get_object_info,
421
                obj, version=version)
422

423
    @patch('%s.get_object_info' % pithos_pkg, return_value=object_info)
424
    def test_get_object_meta(self, GOI):
425
        expected = dict()
426
        for k, v in object_info.items():
427
            expected[k] = v
428
        r = self.client.get_object_meta(obj)
429
        self.assert_dicts_are_equal(r, expected)
430

431
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
432
    def test_del_object_meta(self, post):
433
        metakey = '50m3m3t4k3y'
434
        self.client.del_object_meta(obj, metakey)
435
        expected = call(obj, update=True, metadata={metakey: ''})
436
        self.assertEqual(post.mock_calls[-1], expected)
437

438
    @patch('%s.post' % client_pkg, return_value=FR())
439
    @patch('%s.set_header' % client_pkg)
440
    def test_replace_object_meta(self, SH, post):
441
        metas = dict(k1='new1', k2='new2', k3='new3')
442
        cont = self.client.container
443
        self.client.replace_object_meta(metas)
444
        expected = call('/%s/%s' % (user_id, cont), success=202)
445
        self.assertEqual(post.mock_calls[-1], expected)
446
        prfx = 'X-Object-Meta-'
447
        expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
448
        self.assertEqual(PC.set_header.mock_calls, expected)
449

450
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
451
    def test_copy_object(self, put):
452
        src_cont = 'src-c0nt41n3r'
453
        src_obj = 'src-0bj'
454
        dst_cont = 'dst-c0nt41n3r'
455
        dst_obj = 'dst-0bj'
456
        expected = call(
457
            src_obj,
458
            content_length=0,
459
            source_account=None,
460
            success=201,
461
            copy_from='/%s/%s' % (src_cont, src_obj),
462
            delimiter=None,
463
            content_type=None,
464
            source_version=None,
465
            public=False)
466
        self.client.copy_object(src_cont, src_obj, dst_cont)
467
        self.assertEqual(put.mock_calls[-1], expected)
468
        self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
469
        self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
470
        kwargs = dict(
471
            source_version='src-v3r510n',
472
            source_account='src-4cc0un7',
473
            public=True,
474
            content_type='c0n73n7Typ3',
475
            delimiter='5')
476
        self.client.copy_object(src_cont, src_obj, dst_cont, **kwargs)
477
        for k, v in kwargs.items():
478
            self.assertEqual(v, put.mock_calls[-1][2][k])
479

480
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
481
    def test_move_object(self, put):
482
        src_cont = 'src-c0nt41n3r'
483
        src_obj = 'src-0bj'
484
        dst_cont = 'dst-c0nt41n3r'
485
        dst_obj = 'dst-0bj'
486
        expected = call(
487
            src_obj,
488
            content_length=0,
489
            source_account=None,
490
            success=201,
491
            move_from='/%s/%s' % (src_cont, src_obj),
492
            delimiter=None,
493
            content_type=None,
494
            source_version=None,
495
            public=False)
496
        self.client.move_object(src_cont, src_obj, dst_cont)
497
        self.assertEqual(put.mock_calls[-1], expected)
498
        self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
499
        self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
500
        kwargs = dict(
501
            source_version='src-v3r510n',
502
            source_account='src-4cc0un7',
503
            public=True,
504
            content_type='c0n73n7Typ3',
505
            delimiter='5')
506
        self.client.move_object(src_cont, src_obj, dst_cont, **kwargs)
507
        for k, v in kwargs.items():
508
            self.assertEqual(v, put.mock_calls[-1][2][k])
509

510
    @patch('%s.delete' % client_pkg, return_value=FR())
511
    def test_delete_object(self, delete):
512
        cont = self.client.container
513
        self.client.delete_object(obj)
514
        self.assertEqual(
515
            delete.mock_calls[-1],
516
            call('/%s/%s/%s' % (user_id, cont, obj), success=(204, 404)))
517
        FR.status_code = 404
518
        self.assertRaises(ClientError, self.client.delete_object, obj)
519

520
    @patch('%s.get' % client_pkg, return_value=FR())
521
    @patch('%s.set_param' % client_pkg)
522
    def test_list_objects(self, SP, get):
523
        FR.json = object_list
524
        acc = self.client.account
525
        cont = self.client.container
526
        SP = PC.set_param
527
        r = self.client.list_objects()
528
        for i in range(len(r)):
529
            self.assert_dicts_are_equal(r[i], object_list[i])
530
        self.assertEqual(get.mock_calls, [
531
            call('/%s/%s' % (acc, cont), success=(200, 204, 304, 404))])
532
        self.assertEqual(SP.mock_calls, [call('format', 'json')])
533
        FR.status_code = 304
534
        self.assertEqual(self.client.list_objects(), [])
535
        FR.status_code = 404
536
        self.assertRaises(ClientError, self.client.list_objects)
537

538
    @patch('%s.get' % client_pkg, return_value=FR())
539
    @patch('%s.set_param' % client_pkg)
540
    def test_list_objects_in_path(self, SP, get):
541
        FR.json = object_list
542
        path = '/some/awsome/path'
543
        acc = self.client.account
544
        cont = self.client.container
545
        SP = PC.set_param
546
        self.client.list_objects_in_path(path)
547
        self.assertEqual(get.mock_calls, [
548
            call('/%s/%s' % (acc, cont), success=(200, 204, 404))])
549
        self.assertEqual(SP.mock_calls, [
550
            call('format', 'json'), call('path', path)])
551
        FR.status_code = 404
552
        self.assertRaises(ClientError, self.client.list_objects)
553
    """
554

    
555
if __name__ == '__main__':
556
    from sys import argv
557
    from kamaki.clients.test import runTestCase
558
    runTestCase(Storage, 'Storage Client', argv[1:])