Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / pithos / test.py @ ceb84616

History | View | Annotate | Download (44 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.pithos import PithosClient as PC
41
from kamaki.clients.connection.kamakicon import KamakiHTTPConnection as C
42

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

    
141

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

    
150
    def release(self):
151
        pass
152

    
153

    
154
class Pithos(TestCase):
155

    
156
    files = []
157

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

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

    
177
    def setUp(self):
178
        self.url = 'https://www.example.com/pithos'
179
        self.token = 'p17h0570k3n'
180
        self.client = PC(self.url, self.token)
181
        self.client.account = user_id
182
        self.client.container = 'c0nt@1n3r_i'
183

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

    
192
    #  Pithos+ methods that extend storage API
193

    
194
    @patch('%s.account_head' % pithos_pkg, return_value=FR())
195
    def test_get_account_info(self, AH):
196
        FR.headers = account_info
197
        r = self.client.get_account_info()
198
        self.assert_dicts_are_equal(r, account_info)
199
        self.assertEqual(AH.mock_calls[-1], call(until=None))
200
        unt = 'un71L-d473'
201
        r = self.client.get_account_info(until=unt)
202
        self.assert_dicts_are_equal(r, account_info)
203
        self.assertEqual(AH.mock_calls[-1], call(until=unt))
204
        FR.status_code = 401
205
        self.assertRaises(ClientError, self.client.get_account_info)
206

    
207
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
208
    def test_del_account_meta(self, ap):
209
        keys = ['k1', 'k2', 'k3']
210
        expected = []
211
        for key in keys:
212
            self.client.del_account_meta(key)
213
            expected.append(call(update=True, metadata={key: ''}))
214
        self.assertEqual(ap.mock_calls, expected)
215

    
216
    @patch('%s.put' % pithos_pkg, return_value=FR())
217
    def test_create_container(self, put):
218
        FR.status_code = 201
219
        cont = 's0m3c0n731n3r'
220
        self.client.create_container(cont)
221
        expected = [call('/%s/%s' % (user_id, cont), success=(201, 202))]
222
        self.assertEqual(put.mock_calls, expected)
223
        FR.status_code = 202
224
        self.assertRaises(ClientError, self.client.create_container, cont)
225

    
226
    @patch('%s.container_head' % pithos_pkg, return_value=FR())
227
    def test_get_container_info(self, ch):
228
        FR.headers = container_info
229
        r = self.client.get_container_info()
230
        self.assert_dicts_are_equal(r, container_info)
231
        u = 'some date'
232
        r = self.client.get_container_info(until=u)
233
        self.assertEqual(ch.mock_calls, [call(until=None), call(until=u)])
234

    
235
    @patch('%s.delete' % pithos_pkg, return_value=FR())
236
    def test_delete_container(self, delete):
237
        FR.status_code = 204
238
        cont = 's0m3c0n731n3r'
239
        self.client.delete_container(cont)
240
        for err_code in (404, 409):
241
            FR.status_code = err_code
242
            self.assertRaises(ClientError, self.client.delete_container, cont)
243
        acall = call('/%s/%s' % (user_id, cont), success=(204, 404, 409))
244
        self.assertEqual(delete.mock_calls, [acall] * 3)
245

    
246
    @patch('%s.account_get' % pithos_pkg, return_value=FR())
247
    def test_list_containers(self, get):
248
        FR.json = container_list
249
        r = self.client.list_containers()
250
        for i in range(len(r)):
251
            self.assert_dicts_are_equal(r[i], container_list[i])
252

    
253
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
254
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
255
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
256
    def test_upload_object(self, CI, CP, OP):
257
        num_of_blocks = 8
258
        tmpFile = self._create_temp_file(num_of_blocks)
259

    
260
        # Without kwargs
261
        self.client.upload_object(obj, tmpFile)
262
        self.assertEqual(PC.get_container_info.mock_calls, [call()])
263
        [call1, call2] = PC.object_put.mock_calls
264

    
265
        (args1, kwargs1) = call1[1:3]
266
        (args2, kwargs2) = call2[1:3]
267
        self.assertEqual(args1, (obj,))
268
        expected1 = dict(
269
            hashmap=True,
270
            success=(201, 409),
271
            format='json',
272
            json=dict(
273
                hashes=['s0m3h@5h'] * num_of_blocks,
274
                bytes=num_of_blocks * 4 * 1024 * 1024),
275
            etag=None,
276
            content_encoding=None,
277
            content_type='application/octet-stream',
278
            content_disposition=None,
279
            public=None,
280
            permissions=None)
281
        for k, v in expected1.items():
282
            if k == 'json':
283
                self.assertEqual(len(v['hashes']), len(kwargs1[k]['hashes']))
284
                self.assertEqual(v['bytes'], kwargs1[k]['bytes'])
285
            else:
286
                self.assertEqual(v, kwargs1[k])
287

    
288
        (args2, kwargs2) = call2[1:3]
289
        self.assertEqual(args2, (obj,))
290
        expected2 = dict(
291
            json=dict(
292
                hashes=['s0m3h@5h'] * num_of_blocks,
293
                bytes=num_of_blocks * 4 * 1024 * 1024),
294
            content_type='application/octet-stream',
295
            hashmap=True,
296
            success=201,
297
            format='json')
298
        for k, v in expected2.items():
299
            if k == 'json':
300
                self.assertEqual(len(v['hashes']), len(kwargs2[k]['hashes']))
301
                self.assertEqual(v['bytes'], kwargs2[k]['bytes'])
302
            else:
303
                self.assertEqual(v, kwargs2[k])
304

    
305
        OP = PC.object_put
306
        mock_offset = 2
307

    
308
        #  With progress bars
309
        try:
310
            from progress.bar import ShadyBar
311
            blck_bar = ShadyBar('Mock blck calc.')
312
            upld_bar = ShadyBar('Mock uplds')
313
        except ImportError:
314
            blck_bar = None
315
            upld_bar = None
316

    
317
        if blck_bar and upld_bar:
318

    
319
            def blck_gen(n):
320
                for i in blck_bar.iter(range(n)):
321
                    yield
322
                yield
323

    
324
            def upld_gen(n):
325
                for i in upld_bar.iter(range(n)):
326
                    yield
327
                yield
328

    
329
            tmpFile.seek(0)
330
            self.client.upload_object(
331
                obj, tmpFile,
332
                hash_cb=blck_gen, upload_cb=upld_gen)
333

    
334
            for i, c in enumerate(OP.mock_calls[-mock_offset:]):
335
                self.assertEqual(OP.mock_calls[i], c)
336

    
337
        #  With content-type
338
        tmpFile.seek(0)
339
        ctype = 'video/mpeg'
340
        sharing = dict(read=['u1', 'g1', 'u2'], write=['u1'])
341
        self.client.upload_object(obj, tmpFile,
342
            content_type=ctype, sharing=sharing)
343
        self.assertEqual(OP.mock_calls[-1][2]['content_type'], ctype)
344
        self.assert_dicts_are_equal(
345
            OP.mock_calls[-2][2]['permissions'],
346
            sharing)
347

    
348
        # With other args
349
        tmpFile.seek(0)
350
        kwargs = dict(
351
            etag='s0m3E74g',
352
            content_type=ctype,
353
            content_disposition=ctype + 'd15p051710n',
354
            public=True,
355
            content_encoding='802.11')
356
        self.client.upload_object(obj, tmpFile, **kwargs)
357
        for arg, val in kwargs.items():
358
            self.assertEqual(OP.mock_calls[-2][2][arg], val)
359

    
360
    @patch('%s.put' % pithos_pkg, return_value=FR())
361
    @patch('%s.set_header' % client_pkg)
362
    def test_create_object(self, SH, put):
363
        cont = self.client.container
364
        ctype = 'c0n73n7/typ3'
365
        exp_shd = [
366
            call('Content-Type', 'application/octet-stream'),
367
            call('Content-length', '0'),
368
            call('Content-Type', ctype), call('Content-length', '42')]
369
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)] * 2
370
        self.client.create_object(obj)
371
        self.client.create_object(obj, content_type=ctype, content_length=42)
372
        self.assertEqual(PC.set_header.mock_calls, exp_shd)
373
        self.assertEqual(put.mock_calls, exp_put)
374

    
375
    @patch('%s.put' % pithos_pkg, return_value=FR())
376
    @patch('%s.set_header' % client_pkg)
377
    def test_create_directory(self, SH, put):
378
        cont = self.client.container
379
        exp_shd = [
380
            call('Content-Type', 'application/directory'),
381
            call('Content-length', '0')]
382
        exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)]
383
        self.client.create_directory(obj)
384
        self.assertEqual(PC.set_header.mock_calls, exp_shd)
385
        self.assertEqual(put.mock_calls, exp_put)
386

    
387
    def test_get_object_info(self):
388
        FR.headers = object_info
389
        version = 'v3r510n'
390
        with patch.object(PC, 'object_head', return_value=FR()) as head:
391
            r = self.client.get_object_info(obj)
392
            self.assertEqual(r, object_info)
393
            r = self.client.get_object_info(obj, version=version)
394
            self.assertEqual(head.mock_calls, [
395
                call(obj, version=None),
396
                call(obj, version=version)])
397
        with patch.object(
398
                PC,
399
                'object_head',
400
                side_effect=ClientError('Obj not found', 404)):
401
            self.assertRaises(
402
                ClientError,
403
                self.client.get_object_info,
404
                obj, version=version)
405

    
406
    @patch('%s.get_object_info' % pithos_pkg, return_value=object_info)
407
    def test_get_object_meta(self, GOI):
408
        expected = dict()
409
        for k, v in object_info.items():
410
            expected[k] = v
411
        r = self.client.get_object_meta(obj)
412
        self.assert_dicts_are_equal(r, expected)
413

    
414
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
415
    def test_del_object_meta(self, post):
416
        metakey = '50m3m3t4k3y'
417
        self.client.del_object_meta(obj, metakey)
418
        expected = call(obj, update=True, metadata={metakey: ''})
419
        self.assertEqual(post.mock_calls[-1], expected)
420

    
421
    @patch('%s.post' % client_pkg, return_value=FR())
422
    @patch('%s.set_header' % client_pkg)
423
    def test_replace_object_meta(self, SH, post):
424
        metas = dict(k1='new1', k2='new2', k3='new3')
425
        cont = self.client.container
426
        self.client.replace_object_meta(metas)
427
        expected = call('/%s/%s' % (user_id, cont), success=202)
428
        self.assertEqual(post.mock_calls[-1], expected)
429
        prfx = 'X-Object-Meta-'
430
        expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
431
        self.assertEqual(PC.set_header.mock_calls, expected)
432

    
433
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
434
    def test_copy_object(self, put):
435
        src_cont = 'src-c0nt41n3r'
436
        src_obj = 'src-0bj'
437
        dst_cont = 'dst-c0nt41n3r'
438
        dst_obj = 'dst-0bj'
439
        expected = call(
440
            src_obj,
441
            content_length=0,
442
            source_account=None,
443
            success=201,
444
            copy_from='/%s/%s' % (src_cont, src_obj),
445
            delimiter=None,
446
            content_type=None,
447
            source_version=None,
448
            public=False)
449
        self.client.copy_object(src_cont, src_obj, dst_cont)
450
        self.assertEqual(put.mock_calls[-1], expected)
451
        self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
452
        self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
453
        kwargs = dict(
454
            source_version='src-v3r510n',
455
            source_account='src-4cc0un7',
456
            public=True,
457
            content_type='c0n73n7Typ3',
458
            delimiter='5')
459
        self.client.copy_object(src_cont, src_obj, dst_cont, **kwargs)
460
        for k, v in kwargs.items():
461
            self.assertEqual(v, put.mock_calls[-1][2][k])
462

    
463
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
464
    def test_move_object(self, put):
465
        src_cont = 'src-c0nt41n3r'
466
        src_obj = 'src-0bj'
467
        dst_cont = 'dst-c0nt41n3r'
468
        dst_obj = 'dst-0bj'
469
        expected = call(
470
            src_obj,
471
            content_length=0,
472
            source_account=None,
473
            success=201,
474
            move_from='/%s/%s' % (src_cont, src_obj),
475
            delimiter=None,
476
            content_type=None,
477
            source_version=None,
478
            public=False)
479
        self.client.move_object(src_cont, src_obj, dst_cont)
480
        self.assertEqual(put.mock_calls[-1], expected)
481
        self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
482
        self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
483
        kwargs = dict(
484
            source_version='src-v3r510n',
485
            source_account='src-4cc0un7',
486
            public=True,
487
            content_type='c0n73n7Typ3',
488
            delimiter='5')
489
        self.client.move_object(src_cont, src_obj, dst_cont, **kwargs)
490
        for k, v in kwargs.items():
491
            self.assertEqual(v, put.mock_calls[-1][2][k])
492

    
493
    @patch('%s.delete' % client_pkg, return_value=FR())
494
    def test_delete_object(self, delete):
495
        cont = self.client.container
496
        self.client.delete_object(obj)
497
        self.assertEqual(
498
            delete.mock_calls[-1],
499
            call('/%s/%s/%s' % (user_id, cont, obj), success=(204, 404)))
500
        FR.status_code = 404
501
        self.assertRaises(ClientError, self.client.delete_object, obj)
502

    
503
    @patch('%s.get' % client_pkg, return_value=FR())
504
    @patch('%s.set_param' % client_pkg)
505
    def test_list_objects(self, SP, get):
506
        FR.json = object_list
507
        acc = self.client.account
508
        cont = self.client.container
509
        SP = PC.set_param
510
        r = self.client.list_objects()
511
        for i in range(len(r)):
512
            self.assert_dicts_are_equal(r[i], object_list[i])
513
        self.assertEqual(get.mock_calls, [
514
            call('/%s/%s' % (acc, cont), success=(200, 204, 304, 404))])
515
        self.assertEqual(SP.mock_calls, [call('format', 'json')])
516
        FR.status_code = 304
517
        self.assertEqual(self.client.list_objects(), [])
518
        FR.status_code = 404
519
        self.assertRaises(ClientError, self.client.list_objects)
520

    
521
    @patch('%s.get' % client_pkg, return_value=FR())
522
    @patch('%s.set_param' % client_pkg)
523
    def test_list_objects_in_path(self, SP, get):
524
        FR.json = object_list
525
        path = '/some/awsome/path'
526
        acc = self.client.account
527
        cont = self.client.container
528
        SP = PC.set_param
529
        self.client.list_objects_in_path(path)
530
        self.assertEqual(get.mock_calls, [
531
            call('/%s/%s' % (acc, cont), success=(200, 204, 404))])
532
        self.assertEqual(SP.mock_calls, [
533
            call('format', 'json'), call('path', path)])
534
        FR.status_code = 404
535
        self.assertRaises(ClientError, self.client.list_objects)
536

    
537
    #  Pithos+ only methods
538

    
539
    @patch('%s.container_delete' % pithos_pkg, return_value=FR())
540
    def test_purge_container(self, cd):
541
        self.client.purge_container()
542
        self.assertTrue('until' in cd.mock_calls[-1][2])
543
        cont = self.client.container
544
        self.client.purge_container('another-container')
545
        self.assertEqual(self.client.container, cont)
546

    
547
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
548
    def test_upload_object_unchunked(self, put):
549
        num_of_blocks = 8
550
        tmpFile = self._create_temp_file(num_of_blocks)
551
        expected = dict(
552
                success=201,
553
                data=num_of_blocks * 4 * 1024 * 1024,
554
                etag='some-etag',
555
                content_encoding='some content_encoding',
556
                content_type='some content-type',
557
                content_disposition='some content_disposition',
558
                public=True,
559
                permissions=dict(read=['u1', 'g1', 'u2'], write=['u1']))
560
        self.client.upload_object_unchunked(obj, tmpFile)
561
        self.assertEqual(put.mock_calls[-1][1], (obj,))
562
        self.assertEqual(
563
            sorted(put.mock_calls[-1][2].keys()),
564
            sorted(expected.keys()))
565
        kwargs = dict(expected)
566
        kwargs.pop('success')
567
        kwargs['size'] = kwargs.pop('data')
568
        kwargs['sharing'] = kwargs.pop('permissions')
569
        tmpFile.seek(0)
570
        self.client.upload_object_unchunked(obj, tmpFile, **kwargs)
571
        pmc = put.mock_calls[-1][2]
572
        for k, v in expected.items():
573
            if k == 'data':
574
                self.assertEqual(len(pmc[k]), v)
575
            else:
576
                self.assertEqual(pmc[k], v)
577
        self.assertRaises(
578
            ClientError,
579
            self.client.upload_object_unchunked,
580
            obj, tmpFile, withHashFile=True)
581

    
582
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
583
    def test_create_object_by_manifestation(self, put):
584
        manifest = '%s/%s' % (self.client.container, obj)
585
        kwargs = dict(
586
                etag='some-etag',
587
                content_encoding='some content_encoding',
588
                content_type='some content-type',
589
                content_disposition='some content_disposition',
590
                public=True,
591
                sharing=dict(read=['u1', 'g1', 'u2'], write=['u1']))
592
        self.client.create_object_by_manifestation(obj)
593
        expected = dict(content_length=0, manifest=manifest)
594
        for k in kwargs:
595
            expected['permissions' if k == 'sharing' else k] = None
596
        self.assertEqual(put.mock_calls[-1], call(obj, **expected))
597
        self.client.create_object_by_manifestation(obj, **kwargs)
598
        expected.update(kwargs)
599
        expected['permissions'] = expected.pop('sharing')
600
        self.assertEqual(put.mock_calls[-1], call(obj, **expected))
601

    
602
    @patch('%s.get_object_hashmap' % pithos_pkg, return_value=object_hashmap)
603
    @patch('%s.object_get' % pithos_pkg, return_value=FR())
604
    def test_download_object(self, GOH, GET):
605
        num_of_blocks = 8
606
        tmpFile = self._create_temp_file(num_of_blocks)
607
        FR.content = tmpFile.read(4 * 1024 * 1024)
608
        tmpFile = self._create_temp_file(num_of_blocks)
609
        GET = PC.object_get
610
        num_of_blocks = len(object_hashmap['hashes'])
611
        kwargs = dict(
612
            resume=True,
613
            version='version',
614
            range_str='10-20',
615
            if_match='if and only if',
616
            if_none_match='if and only not',
617
            if_modified_since='what if not?',
618
            if_unmodified_since='this happens if not!',
619
            async_headers=dict(Range='bytes=0-88888888'))
620

    
621
        self.client.download_object(obj, tmpFile)
622
        self.assertEqual(len(GET.mock_calls), num_of_blocks)
623
        self.assertEqual(GET.mock_calls[-1][1], (obj,))
624
        for k, v in kwargs.items():
625
            if k == 'async_headers':
626
                self.assertTrue('Range' in GET.mock_calls[-1][2][k])
627
            elif k in ('resume', 'range_str'):
628
                continue
629
            else:
630
                self.assertEqual(GET.mock_calls[-1][2][k], None)
631

    
632
        #  Check ranges are consecutive
633
        starts = []
634
        ends = []
635
        for c in GET.mock_calls:
636
            rng_str = c[2]['async_headers']['Range']
637
            (start, rng_str) = rng_str.split('=')
638
            (start, end) = rng_str.split('-')
639
            starts.append(start)
640
            ends.append(end)
641
        ends = sorted(ends)
642
        for i, start in enumerate(sorted(starts)):
643
            if i:
644
                int(ends[i - 1]) == int(start) - 1
645

    
646
        #  With progress bars
647
        try:
648
            from progress.bar import ShadyBar
649
            dl_bar = ShadyBar('Mock dl')
650
        except ImportError:
651
            dl_bar = None
652

    
653
        if dl_bar:
654

    
655
            def blck_gen(n):
656
                for i in dl_bar.iter(range(n)):
657
                    yield
658
                yield
659

    
660
            tmpFile.seek(0)
661
            self.client.download_object(obj, tmpFile, download_cb=blck_gen)
662
            self.assertEqual(len(GET.mock_calls), 2 * num_of_blocks)
663

    
664
        tmpFile.seek(0)
665
        kwargs.pop('async_headers')
666
        kwargs.pop('resume')
667
        self.client.download_object(obj, tmpFile, **kwargs)
668
        for k, v in kwargs.items():
669
            if k == 'range_str':
670
                self.assertEqual(
671
                    GET.mock_calls[-1][2]['data_range'],
672
                    'bytes=%s' % v)
673
            else:
674
                self.assertEqual(GET.mock_calls[-1][2][k], v)
675

    
676
        #  ALl options on no tty
677

    
678
        def foo():
679
            return True
680

    
681
        tmpFile.seek(0)
682
        tmpFile.isatty = foo
683
        self.client.download_object(obj, tmpFile, **kwargs)
684
        for k, v in kwargs.items():
685
            if k == 'range_str':
686
                self.assertTrue('data_range' in GET.mock_calls[-1][2])
687
            else:
688
                self.assertEqual(GET.mock_calls[-1][2][k], v)
689

    
690
    def test_get_object_hashmap(self):
691
        FR.json = object_hashmap
692
        for empty in (304, 412):
693
            with patch.object(
694
                    PC, 'object_get',
695
                    side_effect=ClientError('Empty', status=empty)):
696
                r = self.client.get_object_hashmap(obj)
697
                self.assertEqual(r, {})
698
        exp_args = dict(
699
            hashmap=True,
700
            data_range=None,
701
            version=None,
702
            if_etag_match=None,
703
            if_etag_not_match=None,
704
            if_modified_since=None,
705
            if_unmodified_since=None)
706
        kwargs = dict(
707
            version='s0m3v3r51on',
708
            if_match='if match',
709
            if_none_match='if non match',
710
            if_modified_since='some date here',
711
            if_unmodified_since='some date here',
712
            data_range='10-20')
713
        with patch.object(PC, 'object_get', return_value=FR()) as get:
714
            r = self.client.get_object_hashmap(obj)
715
            self.assertEqual(r, object_hashmap)
716
            self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
717
            r = self.client.get_object_hashmap(obj, **kwargs)
718
            exp_args['if_etag_match'] = kwargs.pop('if_match')
719
            exp_args['if_etag_not_match'] = kwargs.pop('if_none_match')
720
            exp_args.update(kwargs)
721
            self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
722

    
723
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
724
    def test_set_account_group(self, post):
725
        (group, usernames) = ('aU53rGr0up', ['u1', 'u2', 'u3'])
726
        self.client.set_account_group(group, usernames)
727
        self.assertEqual(
728
            post.mock_calls[-1],
729
            call(update=True, groups={group: usernames}))
730

    
731
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
732
    def test_del_account_group(self, post):
733
        group = 'aU53rGr0up'
734
        self.client.del_account_group(group)
735
        self.assertEqual(
736
            post.mock_calls[-1],
737
            call(update=True, groups={group: []}))
738

    
739
    @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
740
    def test_get_account_quota(self, GAI):
741
        key = 'x-account-policy-quota'
742
        r = self.client.get_account_quota()
743
        self.assertEqual(r[key], account_info[key])
744

    
745
    @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
746
    def test_get_account_versioning(self, GAI):
747
        key = 'x-account-policy-versioning'
748
        r = self.client.get_account_versioning()
749
        self.assertEqual(r[key], account_info[key])
750

    
751
    def test_get_account_meta(self):
752
        key = 'x-account-meta-'
753
        with patch.object(PC, 'get_account_info', return_value=account_info):
754
            r = self.client.get_account_meta()
755
            keys = [k for k in r if k.startswith(key)]
756
            self.assertFalse(keys)
757
        acc_info = dict(account_info)
758
        acc_info['%sk1' % key] = 'v1'
759
        acc_info['%sk2' % key] = 'v2'
760
        acc_info['%sk3' % key] = 'v3'
761
        with patch.object(PC, 'get_account_info', return_value=acc_info):
762
            r = self.client.get_account_meta()
763
            for k in [k for k in acc_info if k.startswith(key)]:
764
                self.assertEqual(r[k], acc_info[k])
765

    
766
    def test_get_account_group(self):
767
        key = 'x-account-group-'
768
        with patch.object(PC, 'get_account_info', return_value=account_info):
769
            r = self.client.get_account_group()
770
            keys = [k for k in r if k.startswith(key)]
771
            self.assertFalse(keys)
772
        acc_info = dict(account_info)
773
        acc_info['%sk1' % key] = 'g1'
774
        acc_info['%sk2' % key] = 'g2'
775
        acc_info['%sk3' % key] = 'g3'
776
        with patch.object(PC, 'get_account_info', return_value=acc_info):
777
            r = self.client.get_account_group()
778
            for k in [k for k in acc_info if k.startswith(key)]:
779
                self.assertEqual(r[k], acc_info[k])
780

    
781
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
782
    def test_set_account_meta(self, post):
783
        metas = dict(k1='v1', k2='v2', k3='v3')
784
        self.client.set_account_meta(metas)
785
        self.assertEqual(
786
            post.mock_calls[-1],
787
            call(update=True, metadata=metas))
788

    
789
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
790
    def test_set_account_quota(self, post):
791
        qu = 1024
792
        self.client.set_account_quota(qu)
793
        self.assertEqual(post.mock_calls[-1], call(update=True, quota=qu))
794

    
795
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
796
    def test_set_account_versioning(self, post):
797
        vrs = 'n3wV3r51on1ngTyp3'
798
        self.client.set_account_versioning(vrs)
799
        self.assertEqual(
800
            post.mock_calls[-1],
801
            call(update=True, versioning=vrs))
802

    
803
    @patch('%s.container_delete' % pithos_pkg, return_value=FR())
804
    def test_del_container(self, delete):
805
        for kwarg in (
806
                dict(delimiter=None, until=None),
807
                dict(delimiter='X', until='50m3d473')):
808
            self.client.del_container(**kwarg)
809
            expected = dict(kwarg)
810
            expected['success'] = (204, 404, 409)
811
            self.assertEqual(delete.mock_calls[-1], call(**expected))
812
        for status_code in (404, 409):
813
            FR.status_code = status_code
814
            self.assertRaises(ClientError, self.client.del_container)
815

    
816
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
817
    def test_get_container_versioning(self, GCI):
818
        key = 'x-container-policy-versioning'
819
        cont = 'c0n7-417'
820
        bu_cnt = self.client.container
821
        for container in (None, cont):
822
            r = self.client.get_container_versioning(container=container)
823
            self.assertEqual(r[key], container_info[key])
824
            self.assertEqual(GCI.mock_calls[-1], call())
825
            self.assertEqual(bu_cnt, self.client.container)
826

    
827
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
828
    def test_get_container_quota(self, GCI):
829
        key = 'x-container-policy-quota'
830
        cont = 'c0n7-417'
831
        bu_cnt = self.client.container
832
        for container in (None, cont):
833
            r = self.client.get_container_quota(container=container)
834
            self.assertEqual(r[key], container_info[key])
835
            self.assertEqual(GCI.mock_calls[-1], call())
836
            self.assertEqual(bu_cnt, self.client.container)
837

    
838
    def test_get_container_meta(self):
839
        somedate = '50m3d473'
840
        key = 'x-container-meta'
841
        metaval = '50m3m374v41'
842
        container_plus = dict(container_info)
843
        container_plus[key] = metaval
844
        for ret in ((container_info, {}), (container_plus, {key: metaval})):
845
            with patch.object(
846
                    PC,
847
                    'get_container_info',
848
                    return_value=ret[0]) as gci:
849
                for until in (None, somedate):
850
                    r = self.client.get_container_meta(until=until)
851
                    self.assertEqual(r, ret[1])
852
                    self.assertEqual(gci.mock_calls[-1], call(until=until))
853

    
854
    def test_get_container_object_meta(self):
855
        somedate = '50m3d473'
856
        key = 'x-container-object-meta'
857
        metaval = '50m3m374v41'
858
        container_plus = dict(container_info)
859
        container_plus[key] = metaval
860
        for ret in (
861
                (container_info, {key: ''}),
862
                (container_plus, {key: metaval})):
863
            with patch.object(
864
                    PC,
865
                    'get_container_info',
866
                    return_value=ret[0]) as gci:
867
                for until in (None, somedate):
868
                    r = self.client.get_container_object_meta(until=until)
869
                    self.assertEqual(r, ret[1])
870
                    self.assertEqual(gci.mock_calls[-1], call(until=until))
871

    
872
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
873
    def test_set_container_meta(self, post):
874
        metas = dict(k1='v1', k2='v2', k3='v3')
875
        self.client.set_container_meta(metas)
876
        self.assertEqual(
877
            post.mock_calls[-1],
878
            call(update=True, metadata=metas))
879

    
880
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
881
    def test_del_container_meta(self, ap):
882
        self.client.del_container_meta('somekey')
883
        expected = [call(update=True, metadata={'somekey': ''})]
884
        self.assertEqual(ap.mock_calls, expected)
885

    
886
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
887
    def test_set_container_quota(self, post):
888
        qu = 1024
889
        self.client.set_container_quota(qu)
890
        self.assertEqual(post.mock_calls[-1], call(update=True, quota=qu))
891

    
892
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
893
    def test_set_container_versioning(self, post):
894
        vrs = 'n3wV3r51on1ngTyp3'
895
        self.client.set_container_versioning(vrs)
896
        self.assertEqual(
897
            post.mock_calls[-1],
898
            call(update=True, versioning=vrs))
899

    
900
    @patch('%s.object_delete' % pithos_pkg, return_value=FR())
901
    def test_del_object(self, delete):
902
        for kwarg in (
903
                dict(delimiter=None, until=None),
904
                dict(delimiter='X', until='50m3d473')):
905
            self.client.del_object(obj, **kwarg)
906
            self.assertEqual(delete.mock_calls[-1], call(obj, **kwarg))
907

    
908
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
909
    def test_set_object_meta(self, post):
910
        metas = dict(k1='v1', k2='v2', k3='v3')
911
        self.assertRaises(
912
            AssertionError,
913
            self.client.set_object_meta,
914
            obj, 'Non dict arg')
915
        self.client.set_object_meta(obj, metas)
916
        self.assertEqual(
917
            post.mock_calls[-1],
918
            call(obj, update=True, metadata=metas))
919

    
920
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
921
    def test_publish_object(self, post):
922
        oinfo = dict(object_info)
923
        val = 'pubL1c'
924
        oinfo['x-object-public'] = val
925
        with patch.object(PC, 'get_object_info', return_value=oinfo) as gof:
926
            r = self.client.publish_object(obj)
927
            self.assertEqual(
928
                post.mock_calls[-1],
929
                call(obj, public=True, update=True))
930
            self.assertEqual(gof.mock_calls[-1], call(obj))
931
            self.assertEqual(r, '%s%s' % (self.url[:-6], val))
932

    
933
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
934
    def test_unpublish_object(self, post):
935
        self.client.unpublish_object(obj)
936
        self.assertEqual(
937
            post.mock_calls[-1],
938
            call(obj, public=False, update=True))
939

    
940
    def test_get_object_sharing(self):
941
        info = dict(object_info)
942
        expected = dict(read='u1,g1,u2', write='u1')
943
        info['x-object-sharing'] = '; '.join(
944
            ['%s=%s' % (k, v) for k, v in expected.items()])
945
        with patch.object(PC, 'get_object_info', return_value=info) as GOF:
946
            r = self.client.get_object_sharing(obj)
947
            self.assertEqual(GOF.mock_calls[-1], call(obj))
948
            self.assert_dicts_are_equal(r, expected)
949
            info['x-object-sharing'] = '//'.join(
950
                ['%s=%s' % (k, v) for k, v in expected.items()])
951
            self.assertRaises(
952
                ValueError,
953
                self.client.get_object_sharing,
954
                obj)
955
            info['x-object-sharing'] = '; '.join(
956
                ['%s:%s' % (k, v) for k, v in expected.items()])
957
            self.assertRaises(
958
                ClientError,
959
                self.client.get_object_sharing,
960
                obj)
961
            info['x-object-sharing'] = 'read=%s' % expected['read']
962
            r = self.client.get_object_sharing(obj)
963
            expected.pop('write')
964
            self.assert_dicts_are_equal(r, expected)
965

    
966
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
967
    def test_set_object_sharing(self, POST):
968
        read_perms = ['u1', 'g1', 'u2', 'g2']
969
        write_perms = ['u1', 'g1']
970
        for kwargs in (
971
                dict(read_permition=read_perms, write_permition=write_perms),
972
                dict(read_permition=read_perms),
973
                dict(write_permition=write_perms),
974
                dict()):
975
            self.client.set_object_sharing(obj, **kwargs)
976
            kwargs['read'] = kwargs.pop('read_permition', '')
977
            kwargs['write'] = kwargs.pop('write_permition', '')
978
            self.assertEqual(
979
                POST.mock_calls[-1],
980
                call(obj, update=True, permissions=kwargs))
981

    
982
    @patch('%s.set_object_sharing' % pithos_pkg)
983
    def test_del_object_sharing(self, SOS):
984
        self.client.del_object_sharing(obj)
985
        self.assertEqual(SOS.mock_calls[-1], call(obj))
986

    
987
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
988
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
989
    def test_append_object(self, post, GCI):
990
        num_of_blocks = 4
991
        tmpFile = self._create_temp_file(num_of_blocks)
992
        tmpFile.seek(0, 2)
993
        file_size = tmpFile.tell()
994
        for turn in range(2):
995
            tmpFile.seek(0, 0)
996

    
997
            try:
998
                from progress.bar import ShadyBar
999
                apn_bar = ShadyBar('Mock append')
1000
            except ImportError:
1001
                apn_bar = None
1002

    
1003
            if apn_bar:
1004

    
1005
                def append_gen(n):
1006
                    for i in apn_bar.iter(range(n)):
1007
                        yield
1008
                    yield
1009

    
1010
            else:
1011
                append_gen = None
1012

    
1013
            self.client.append_object(
1014
                obj, tmpFile,
1015
                upload_cb=append_gen if turn else None)
1016
            self.assertEqual((turn + 1) * num_of_blocks, len(post.mock_calls))
1017
            (args, kwargs) = post.mock_calls[-1][1:3]
1018
            self.assertEqual(args, (obj,))
1019
            self.assertEqual(kwargs['content_length'], len(kwargs['data']))
1020
            fsize = num_of_blocks * int(kwargs['content_length'])
1021
            self.assertEqual(fsize, file_size)
1022
            self.assertEqual(kwargs['content_range'], 'bytes */*')
1023
            exp = 'application/octet-stream'
1024
            self.assertEqual(kwargs['content_type'], exp)
1025
            self.assertEqual(kwargs['update'], True)
1026

    
1027
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
1028
    def test_truncate_object(self, post):
1029
        upto_bytes = 377
1030
        self.client.truncate_object(obj, upto_bytes)
1031
        self.assertEqual(post.mock_calls[-1], call(
1032
            obj,
1033
            update=True,
1034
            object_bytes=upto_bytes,
1035
            content_range='bytes 0-%s/*' % upto_bytes,
1036
            content_type='application/octet-stream',
1037
            source_object='/%s/%s' % (self.client.container, obj)))
1038

    
1039
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1040
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
1041
    def test_overwrite_object(self, post, GCI):
1042
        num_of_blocks = 4
1043
        tmpFile = self._create_temp_file(num_of_blocks)
1044
        tmpFile.seek(0, 2)
1045
        file_size = tmpFile.tell()
1046
        info = dict(object_info)
1047
        info['content-length'] = file_size
1048
        block_size = container_info['x-container-block-size']
1049
        with patch.object(PC, 'get_object_info', return_value=info) as GOI:
1050
            for start, end in (
1051
                    (0, file_size + 1),
1052
                    (file_size + 1, file_size + 2)):
1053
                tmpFile.seek(0, 0)
1054
                self.assertRaises(
1055
                    ClientError,
1056
                    self.client.overwrite_object,
1057
                    obj, start, end, tmpFile)
1058
            for start, end in ((0, 144), (144, 233), (233, file_size)):
1059
                tmpFile.seek(0, 0)
1060
                owr_gen = None
1061
                exp_size = end - start + 1
1062
                if not start or exp_size > block_size:
1063
                    try:
1064
                        from progress.bar import ShadyBar
1065
                        owr_bar = ShadyBar('Mock append')
1066
                    except ImportError:
1067
                        owr_bar = None
1068

    
1069
                    if owr_bar:
1070

    
1071
                        def owr_gen(n):
1072
                            for i in owr_bar.iter(range(n)):
1073
                                yield
1074
                            yield
1075

    
1076
                    if exp_size > block_size:
1077
                        exp_size = exp_size % block_size or block_size
1078

    
1079
                self.client.overwrite_object(obj, start, end, tmpFile, owr_gen)
1080
                self.assertEqual(GOI.mock_calls[-1], call(obj))
1081
                self.assertEqual(GCI.mock_calls[-1], call())
1082
                (args, kwargs) = post.mock_calls[-1][1:3]
1083
                self.assertEqual(args, (obj,))
1084
                self.assertEqual(len(kwargs['data']), exp_size)
1085
                self.assertEqual(kwargs['content_length'], exp_size)
1086
                self.assertEqual(kwargs['update'], True)
1087
                exp = 'application/octet-stream'
1088
                self.assertEqual(kwargs['content_type'], exp)
1089

    
1090
    @patch('%s.set_param' % client_pkg)
1091
    @patch('%s.get' % pithos_pkg, return_value=FR())
1092
    def test_get_sharing_accounts(self, get, SP):
1093
        FR.json = sharers
1094
        for kws in (
1095
                dict(),
1096
                dict(limit='50m3-11m17'),
1097
                dict(marker='X'),
1098
                dict(limit='50m3-11m17', marker='X')):
1099
            r = self.client.get_sharing_accounts(**kws)
1100
            self.assertEqual(SP.mock_calls[-3], call('format', 'json'))
1101
            limit, marker = kws.get('limit', None), kws.get('marker', None)
1102
            self.assertEqual(SP.mock_calls[-2], call(
1103
                'limit', limit,
1104
                iff=limit is not None))
1105
            self.assertEqual(SP.mock_calls[-1], call(
1106
                'marker', marker,
1107
                iff=marker is not None))
1108
            self.assertEqual(get.mock_calls[-1], call('', success=(200, 204)))
1109
            for i in range(len(r)):
1110
                self.assert_dicts_are_equal(r[i], sharers[i])
1111

    
1112
    @patch('%s.object_get' % pithos_pkg, return_value=FR())
1113
    def test_get_object_versionlist(self, get):
1114
        info = dict(object_info)
1115
        info['versions'] = ['v1', 'v2']
1116
        FR.json = info
1117
        r = self.client.get_object_versionlist(obj)
1118
        self.assertEqual(
1119
            get.mock_calls[-1],
1120
            call(obj, format='json', version='list'))
1121
        self.assertEqual(r, info['versions'])
1122

    
1123
if __name__ == '__main__':
1124
    from sys import argv
1125
    from kamaki.clients.test import runTestCase
1126
    runTestCase(Pithos, 'Pithos+ Client', argv[1:])