Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / storage / test.py @ d15e5249

History | View | Annotate | Download (21.2 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
    @patch('%s.head' % storage_pkg, return_value=FR())
243
    def test_get_container_info(self, head):
244
        FR.headers = container_info
245
        cont = self.client.container
246
        r = self.client.get_container_info(cont)
247
        self.assert_dicts_are_equal(r, container_info)
248
        path = '/%s/%s' % (self.client.account, cont)
249
        self.assertEqual(head.mock_calls[-1], call(path, success=(204, 404)))
250
        FR.status_code = 404
251
        self.assertRaises(ClientError, self.client.get_container_info, cont)
252

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

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

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

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

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

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

324
        OP = PC.object_put
325
        mock_offset = 2
326

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

336
        if blck_bar and upld_bar:
337

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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