Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (27.6 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, Mock
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.astakos import AstakosClient
42
from kamaki.clients.connection.kamakicon import KamakiHTTPConnection as C
43

    
44
user_id = 'ac0un7-1d-5tr1ng'
45

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

    
134

    
135
class Pithos(TestCase):
136

    
137
    class FR(object):
138
        """FR stands for Fake Response"""
139
        json = dict()
140
        headers = dict()
141
        content = json
142
        status = None
143
        status_code = 200
144

    
145
        def release(self):
146
            pass
147

    
148
    files = []
149

    
150
    def _create_temp_file(self):
151
        self.files.append(NamedTemporaryFile())
152
        tmpFile = self.files[-1]
153
        num_of_blocks = 8
154
        file_size = num_of_blocks * 4 * 1024 * 1024
155
        print('\n\tCreate tmp file')
156
        tmpFile.write(urandom(file_size))
157
        tmpFile.flush()
158
        tmpFile.seek(0)
159
        print('\t\tDone')
160
        return tmpFile
161

    
162
    def assert_dicts_are_equal(self, d1, d2):
163
        for k, v in d1.items():
164
            self.assertTrue(k in d2)
165
            if isinstance(v, dict):
166
                self.assert_dicts_are_equal(v, d2[k])
167
            else:
168
                self.assertEqual(unicode(v), unicode(d2[k]))
169

    
170
    def setUp(self):
171
        self.url = 'https://www.example.com/pithos'
172
        self.token = 'p17h0570k3n'
173
        self.client = PC(self.url, self.token)
174
        self.client.account = user_id
175
        self.client.container = 'c0nt@1n3r_i'
176

    
177
    def tearDown(self):
178
        self.FR.headers = dict()
179
        self.FR.status_code = 200
180
        self.FR.json = dict()
181
        self.FR.content = self.FR.json
182
        for f in self.files:
183
            f.close()
184

    
185
    #  Pithos+ methods that extend storage API
186

    
187
    def test_get_account_info(self):
188
        self.FR.headers = account_info
189
        self.FR.status_code = 204
190
        with patch.object(C, 'perform_request', return_value=self.FR()):
191
            r = self.client.get_account_info()
192
            self.assertEqual(self.client.http_client.url, self.url)
193
            self.assertEqual(self.client.http_client.path, '/%s' % user_id)
194
            self.assert_dicts_are_equal(r, account_info)
195
            PC.set_param = Mock()
196
            untils = ['date 1', 'date 2', 'date 3']
197
            for unt in untils:
198
                r = self.client.get_account_info(until=unt)
199
                self.assert_dicts_are_equal(r, account_info)
200
            for i in range(len(untils)):
201
                self.assertEqual(
202
                    PC.set_param.mock_calls[i],
203
                    call('until', untils[i], iff=untils[i]))
204
            self.FR.status_code = 401
205
            self.assertRaises(ClientError, self.client.get_account_info)
206

    
207
    def test_replace_account_meta(self):
208
        self.FR.status_code = 202
209
        metas = dict(k1='v1', k2='v2', k3='v3')
210
        PC.set_header = Mock()
211
        with patch.object(C, 'perform_request', return_value=self.FR()):
212
            self.client.replace_account_meta(metas)
213
            self.assertEqual(self.client.http_client.url, self.url)
214
            self.assertEqual(self.client.http_client.path, '/%s' % user_id)
215
            prfx = 'X-Account-Meta-'
216
            expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
217
            self.assertEqual(PC.set_header.mock_calls, expected)
218

    
219
    def test_del_account_meta(self):
220
        keys = ['k1', 'k2', 'k3']
221
        with patch.object(PC, 'account_post', return_value=self.FR()) as ap:
222
            expected = []
223
            for key in keys:
224
                self.client.del_account_meta(key)
225
                expected.append(call(update=True, metadata={key: ''}))
226
            self.assertEqual(ap.mock_calls, expected)
227

    
228
    def test_create_container(self):
229
        self.FR.status_code = 201
230
        with patch.object(PC, 'put', return_value=self.FR()) as put:
231
            cont = 's0m3c0n731n3r'
232
            self.client.create_container(cont)
233
            expected = [call('/%s/%s' % (user_id, cont), success=(201, 202))]
234
            self.assertEqual(put.mock_calls, expected)
235
            self.FR.status_code = 202
236
            self.assertRaises(ClientError, self.client.create_container, cont)
237

    
238
    def test_get_container_info(self):
239
        self.FR.headers = container_info
240
        with patch.object(PC, 'container_head', return_value=self.FR()) as ch:
241
            r = self.client.get_container_info()
242
            self.assert_dicts_are_equal(r, container_info)
243
            u = 'some date'
244
            r = self.client.get_container_info(until=u)
245
            self.assertEqual(ch.mock_calls, [call(until=None), call(until=u)])
246

    
247
    def test_delete_container(self):
248
        self.FR.status_code = 204
249
        with patch.object(PC, 'delete', return_value=self.FR()) as delete:
250
            cont = 's0m3c0n731n3r'
251
            self.client.delete_container(cont)
252
            self.FR.status_code = 404
253
            self.assertRaises(ClientError, self.client.delete_container, cont)
254
            self.FR.status_code = 409
255
            self.assertRaises(ClientError, self.client.delete_container, cont)
256
            acall = call('/%s/%s' % (user_id, cont), success=(204, 404, 409))
257
            self.assertEqual(delete.mock_calls, [acall] * 3)
258

    
259
    def test_list_containers(self):
260
        self.FR.json = container_list
261
        with patch.object(PC, 'account_get', return_value=self.FR()):
262
            r = self.client.list_containers()
263
            for i in range(len(r)):
264
                self.assert_dicts_are_equal(r[i], container_list[i])
265

    
266
    def test_upload_object(self):
267
        PC.get_container_info = Mock(return_value=container_info)
268
        PC.container_post = Mock(return_value=self.FR())
269
        PC.object_put = Mock(return_value=self.FR())
270
        tmpFile = self._create_temp_file()
271
        obj = 'objectName'
272

    
273
        # Without kwargs
274
        self.client.upload_object(obj, tmpFile)
275
        self.assertEqual(PC.get_container_info.mock_calls, [call()])
276
        [call1, call2] = PC.object_put.mock_calls
277

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

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

    
318
        OP = PC.object_put
319
        mock_offset = 2
320

    
321
        #  With progress bars
322
        try:
323
            from progress.bar import ShadyBar
324
            blck_bar = ShadyBar('Mock blck calc.')
325
            upld_bar = ShadyBar('Mock uplds')
326
        except ImportError:
327
            blck_bar = None
328
            upld_bar = None
329

    
330
        if blck_bar and upld_bar:
331

    
332
            def blck_gen(n):
333
                for i in blck_bar.iter(range(n)):
334
                    yield
335
                yield
336

    
337
            def upld_gen(n):
338
                for i in upld_bar.iter(range(n)):
339
                    yield
340
                yield
341

    
342
            tmpFile.seek(0)
343
            self.client.upload_object(
344
                obj, tmpFile,
345
                hash_cb=blck_gen, upload_cb=upld_gen)
346

    
347
            for i, c in enumerate(OP.mock_calls[-mock_offset:]):
348
                self.assertEqual(OP.mock_calls[i], c)
349

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

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

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

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

    
403
    def test_get_object_info(self):
404
        self.FR.headers = object_info
405
        obj = 'r4nd0m0bj3c7'
406
        version = 'v3r510n'
407
        with patch.object(PC, 'object_head', return_value=self.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
    def test_get_object_meta(self):
424
        obj = 'r4nd0m0bj3c7'
425
        expected = dict()
426
        for k, v in object_info.items():
427
            expected[k] = v
428
        with patch.object(
429
                PC,
430
                'get_object_info',
431
                return_value=object_info):
432
            r = self.client.get_object_meta(obj)
433
            self.assert_dicts_are_equal(r, expected)
434

    
435
    def test_del_object_meta(self):
436
        obj = 'r4nd0m0bj3c7'
437
        metakey = '50m3m3t4k3y'
438
        with patch.object(PC, 'object_post', return_value=self.FR()) as post:
439
            self.client.del_object_meta(obj, metakey)
440
            self.assertEqual(
441
                post.mock_calls,
442
                [call(obj, update=True, metadata={metakey: ''})])
443

    
444
    def test_replace_object_meta(self):
445
        PC.set_header = Mock()
446
        metas = dict(k1='new1', k2='new2', k3='new3')
447
        cont = self.client.container
448
        with patch.object(PC, 'post', return_value=self.FR()) as post:
449
            self.client.replace_object_meta(metas)
450
            self.assertEqual(post.mock_calls, [
451
                call('/%s/%s' % (user_id, cont),
452
                success=202)])
453
            prfx = 'X-Object-Meta-'
454
            expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
455
            self.assertEqual(PC.set_header.mock_calls, expected)
456

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

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

    
517
    def test_delete_object(self):
518
        obj = 's0m30bj3c7'
519
        cont = self.client.container
520
        with patch.object(PC, 'delete', return_value=self.FR()) as delete:
521
            self.client.delete_object(obj)
522
            self.assertEqual(delete.mock_calls, [
523
                call('/%s/%s/%s' % (user_id, cont, obj), success=(204, 404))])
524
            self.FR.status_code = 404
525
            self.assertRaises(ClientError, self.client.delete_object, obj)
526

    
527
    def test_list_objects(self):
528
        self.FR.json = object_list
529
        acc = self.client.account
530
        cont = self.client.container
531
        PC.set_param = Mock()
532
        SP = PC.set_param
533
        with patch.object(PC, 'get', return_value=self.FR()) as get:
534
            r = self.client.list_objects()
535
            for i in range(len(r)):
536
                self.assert_dicts_are_equal(r[i], object_list[i])
537
            self.assertEqual(get.mock_calls, [
538
                call('/%s/%s' % (acc, cont), success=(200, 204, 304, 404))])
539
            self.assertEqual(SP.mock_calls, [call('format', 'json')])
540
            self.FR.status_code = 304
541
            self.assertEqual(self.client.list_objects(), [])
542
            self.FR.status_code = 404
543
            self.assertRaises(ClientError, self.client.list_objects)
544

    
545
    def test_list_objects_in_path(self):
546
        self.FR.json = object_list
547
        path = '/some/awsome/path'
548
        acc = self.client.account
549
        cont = self.client.container
550
        PC.set_param = Mock()
551
        SP = PC.set_param
552
        with patch.object(PC, 'get', return_value=self.FR()) as get:
553
            self.client.list_objects_in_path(path)
554
            self.assertEqual(get.mock_calls, [
555
                call('/%s/%s' % (acc, cont), success=(200, 204, 404))])
556
            self.assertEqual(SP.mock_calls, [
557
                call('format', 'json'), call('path', path)])
558
            self.FR.status_code = 404
559
            self.assertRaises(ClientError, self.client.list_objects)
560

    
561
    #  Pithos+ only methods
562

    
563
    def test_purge_container(self):
564
        with patch.object(
565
                PC,
566
                'container_delete',
567
                return_value=self.FR()) as cd:
568
            self.client.purge_container()
569
            self.assertTrue('until' in cd.mock_calls[-1][2])
570
            cont = self.client.container
571
            self.client.purge_container('another-container')
572
            self.assertEqual(self.client.container, cont)
573

    
574
    def test_upload_object_unchunked(self):
575
        tmpFile = self._create_temp_file()
576
        obj = 'obj3c7N4m3'
577
        expected = dict(
578
                success=201,
579
                data=8 * 4 * 1024 * 1024,
580
                etag='some-etag',
581
                content_encoding='some content_encoding',
582
                content_type='some content-type',
583
                content_disposition='some content_disposition',
584
                public=True,
585
                permissions=dict(read=['u1', 'g1', 'u2'], write=['u1']))
586
        with patch.object(PC, 'object_put', return_value=self.FR()) as put:
587
            self.client.upload_object_unchunked(obj, tmpFile)
588
            self.assertEqual(put.mock_calls[-1][1], (obj,))
589
            self.assertEqual(
590
                sorted(put.mock_calls[-1][2].keys()),
591
                sorted(expected.keys()))
592
            kwargs = dict(expected)
593
            kwargs.pop('success')
594
            kwargs['size'] = kwargs.pop('data')
595
            kwargs['sharing'] = kwargs.pop('permissions')
596
            tmpFile.seek(0)
597
            self.client.upload_object_unchunked(obj, tmpFile, **kwargs)
598
            pmc = put.mock_calls[-1][2]
599
            for k, v in expected.items():
600
                if k == 'data':
601
                    self.assertEqual(len(pmc[k]), v)
602
                else:
603
                    self.assertEqual(pmc[k], v)
604
            self.assertRaises(
605
                ClientError,
606
                self.client.upload_object_unchunked,
607
                obj, tmpFile, withHashFile=True)
608

    
609
    def test_create_object_by_manifestation(self):
610
        obj = 'obj3c7N4m3'
611
        manifest = '%s/%s' % (self.client.container, obj)
612
        kwargs = dict(
613
                etag='some-etag',
614
                content_encoding='some content_encoding',
615
                content_type='some content-type',
616
                content_disposition='some content_disposition',
617
                public=True,
618
                sharing=dict(read=['u1', 'g1', 'u2'], write=['u1']))
619
        with patch.object(PC, 'object_put', return_value=self.FR()) as put:
620
            self.client.create_object_by_manifestation(obj)
621
            expected = dict(content_length=0, manifest=manifest)
622
            for k in kwargs:
623
                expected['permissions' if k == 'sharing' else k] = None
624
            self.assertEqual(put.mock_calls[-1], call(obj, **expected))
625
            self.client.create_object_by_manifestation(obj, **kwargs)
626
            expected.update(kwargs)
627
            expected['permissions'] = expected.pop('sharing')
628
            self.assertEqual(put.mock_calls[-1], call(obj, **expected))
629

    
630
    def test_download_object(self):
631
        PC.get_object_hashmap = Mock(return_value=object_hashmap)
632
        tmpFile = self._create_temp_file()
633
        self.FR.content = tmpFile.read(4 * 1024 * 1024)
634
        tmpFile = self._create_temp_file()
635
        PC.object_get = Mock(return_value=self.FR())
636
        GET = PC.object_get
637
        obj = 'obj3c7N4m3'
638
        num_of_blocks = len(object_hashmap['hashes'])
639

    
640
        kwargs = dict(
641
            version='version',
642
            resume=True,
643
            range_str='10-20',
644
            if_match='if and only if',
645
            if_none_match='if and only not',
646
            if_modified_since='what if not?',
647
            if_unmodified_since='this happens if not!',
648
            async_headers=dict(Range='bytes=0-88888888'))
649

    
650
        self.client.download_object(obj, tmpFile)
651
        self.assertEqual(len(GET.mock_calls), num_of_blocks)
652
        self.assertEqual(GET.mock_calls[-1][1], (obj,))
653
        for k, v in kwargs.items():
654
            if k == 'async_headers':
655
                self.assertTrue('Range' in GET.mock_calls[-1][2][k])
656
            elif k in ('resume', 'range_str'):
657
                continue
658
            else:
659
                self.assertEqual(GET.mock_calls[-1][2][k], None)
660

    
661
        #  Check ranges are consecutive
662
        starts = []
663
        ends = []
664
        for c in GET.mock_calls:
665
            rng_str = c[2]['async_headers']['Range']
666
            (start, rng_str) = rng_str.split('=')
667
            (start, end) = rng_str.split('-')
668
            starts.append(start)
669
            ends.append(end)
670
        ends = sorted(ends)
671
        for i, start in enumerate(sorted(starts)):
672
            if i:
673
                int(ends[i - 1]) == int(start) - 1
674

    
675
        #  With progress bars
676
        try:
677
            from progress.bar import ShadyBar
678
            dl_bar = ShadyBar('Mock dl')
679
        except ImportError:
680
            dl_bar = None
681

    
682
        if dl_bar:
683

    
684
            def blck_gen(n):
685
                for i in dl_bar.iter(range(n)):
686
                    yield
687
                yield
688

    
689
            tmpFile.seek(0)
690
            self.client.download_object(obj, tmpFile, download_cb=blck_gen)
691
            self.assertEqual(len(GET.mock_calls), 2 * num_of_blocks)
692

    
693
        tmpFile.seek(0)
694
        kwargs.pop('async_headers')
695
        kwargs.pop('resume')
696
        self.client.download_object(obj, tmpFile, **kwargs)
697
        for k, v in kwargs.items():
698
            if k == 'range_str':
699
                self.assertEqual(
700
                    GET.mock_calls[-1][2]['data_range'],
701
                    'bytes=%s' % v)
702
            else:
703
                self.assertEqual(GET.mock_calls[-1][2][k], v)
704

    
705
        #  ALl options on no tty
706

    
707
        def foo():
708
            return True
709

    
710
        tmpFile.seek(0)
711
        tmpFile.isatty = foo
712
        self.client.download_object(obj, tmpFile, **kwargs)
713
        for k, v in kwargs.items():
714
            if k == 'range_str':
715
                self.assertTrue('data_range' in GET.mock_calls[-1][2])
716
            else:
717
                self.assertEqual(GET.mock_calls[-1][2][k], v)