Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (64.5 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
from itertools import product
39
from random import randint
40

    
41
try:
42
    from collections import OrderedDict
43
except ImportError:
44
    from kamaki.clients.commisioning.utils.ordereddict import OrderedDict
45

    
46
from kamaki.clients import pithos, ClientError
47

    
48

    
49
rest_pkg = 'kamaki.clients.pithos.rest_api.PithosRestClient'
50
pithos_pkg = 'kamaki.clients.pithos.PithosClient'
51

    
52
user_id = 'ac0un7-1d-5tr1ng'
53
obj = 'obj3c7N4m3'
54

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

    
147

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

    
156

    
157
class PithosRestClient(TestCase):
158

    
159
    def setUp(self):
160
        self.url = 'https://www.example.com/pithos'
161
        self.token = 'p17h0570k3n'
162
        self.client = pithos.PithosRestClient(self.url, self.token)
163
        self.client.account = user_id
164
        self.client.container = 'c0nt@1n3r_i'
165

    
166
    def tearDown(self):
167
        FR.headers = dict()
168
        FR.json = dict()
169
        FR.content = FR.json
170

    
171
    @patch('%s.set_param' % rest_pkg)
172
    @patch('%s.set_header' % rest_pkg)
173
    @patch('%s.head' % rest_pkg, return_value=FR())
174
    def test_account_head(self, head, SH, SP):
175
        for params in product(
176
                (None, '50m3-d473'),
177
                (None, '50m3-07h3r-d473'),
178
                (None, 'y37-4n7h3r-d473'),
179
                ((), ('someval',), ('v1', 'v2',)),
180
                (dict(), dict(success=200), dict(k='v', v='k'))):
181
            args, kwargs = params[-2], params[-1]
182
            params = params[:-2]
183
            self.client.account_head(*(params + args), **kwargs)
184
            unt = params[0]
185
            self.assertEqual(SP.mock_calls[-1], call('until', unt, iff=unt))
186
            IMS, IUS = params[1], params[2]
187
            self.assertEqual(SH.mock_calls[-2:], [
188
                call('If-Modified-Since', IMS),
189
                call('If-Unmodified-Since', IUS)])
190
            self.assertEqual(head.mock_calls[-1], call(
191
                '/%s' % self.client.account,
192
                *args,
193
                success=kwargs.pop('success', 204),
194
                **kwargs))
195

    
196
    @patch('%s.set_param' % rest_pkg)
197
    @patch('%s.set_header' % rest_pkg)
198
    @patch('%s.get' % rest_pkg, return_value=FR())
199
    def test_account_get(self, get, SH, SP):
200
        keys = ('limit', 'marker', 'format', 'shared', 'until')
201
        for params in product(
202
                (None, 42),
203
                (None, 'X'),
204
                ('json', 'xml'),
205
                (False, True),
206
                (None, '50m3-d473'),
207
                (None, '50m3-07h3r-d473'),
208
                (None, 'y37-4n7h3r-d473'),
209
                ((), ('someval',), ('v1', 'v2',)),
210
                (dict(), dict(success=200), dict(k='v', v='k'))):
211
            args, kwargs = params[-2], params[-1]
212
            params = params[:-2]
213
            self.client.account_get(*(params + args), **kwargs)
214
            self.assertEqual(SP.mock_calls[-5:],
215
                [call(keys[i], iff=X) if (
216
                    i == 3) else call(
217
                        keys[i], X, iff=X) for i, X in enumerate(params[:5])])
218
            IMS, IUS = params[5], params[6]
219
            self.assertEqual(SH.mock_calls[-2:], [
220
                call('If-Modified-Since', IMS),
221
                call('If-Unmodified-Since', IUS)])
222
            self.assertEqual(get.mock_calls[-1], call(
223
                '/%s' % self.client.account,
224
                *args,
225
                success=kwargs.pop('success', (200, 204)),
226
                **kwargs))
227

    
228
    @patch('%s.set_param' % rest_pkg)
229
    @patch('%s.set_header' % rest_pkg)
230
    @patch('%s.post' % rest_pkg, return_value=FR())
231
    def test_account_post(self, post, SH, SP):
232
        #keys = ('update', 'groups', 'metadata', 'quota', 'versioning')
233
        for pm in product(
234
                (True, False),
235
                ({}, dict(g=['u1', 'u2']), dict(g1=[], g2=['u1', 'u2'])),
236
                (None, dict(k1='v1', k2='v2', k3='v2'), dict(k='v')),
237
                (None, 42),
238
                (None, 'v3r510n1ng'),
239
                ((), ('someval',), ('v1', 'v2',)),
240
                (dict(), dict(success=200), dict(k='v', v='k'))):
241
            args, kwargs = pm[-2:]
242
            pm = pm[:-2]
243
            self.client.account_post(*(pm + args), **kwargs)
244
            upd = pm[0]
245
            self.assertEqual(SP.mock_calls[-1], call('update', iff=upd))
246
            expected = []
247
            if pm[1]:
248
                expected += [
249
                call('X-Account-Group-%s' % k, v) for k, v in pm[1].items()]
250
            if pm[2]:
251
                expected = [
252
                call('X-Account-Meta-%s' % k, v) for k, v in pm[2].items()]
253
            expected = [
254
                call('X-Account-Policy-Quota', pm[3]),
255
                call('X-Account-Policy-Versioning', pm[4])]
256
            self.assertEqual(SH.mock_calls[- len(expected):], expected)
257
            self.assertEqual(post.mock_calls[-1], call(
258
                '/%s' % self.client.account,
259
                *args,
260
                success=kwargs.pop('success', 202),
261
                **kwargs))
262

    
263
    @patch('%s.set_param' % rest_pkg)
264
    @patch('%s.set_header' % rest_pkg)
265
    @patch('%s.head' % rest_pkg, return_value=FR())
266
    def test_container_head(self, head, SH, SP):
267
        for pm in product(
268
                (None, '4-d473'),
269
                (None, '47h3r-d473'),
270
                (None, 'y37-4n47h3r'),
271
                ((), ('someval',)),
272
                (dict(), dict(success=200), dict(k='v', v='k'))):
273
            args, kwargs = pm[-2:]
274
            pm = pm[:-2]
275
            self.client.container_head(*(pm + args), **kwargs)
276
            unt, ims, ius = pm[0:3]
277
            self.assertEqual(SP.mock_calls[-1], call('until', unt, iff=unt))
278
            self.assertEqual(SH.mock_calls[-2:], [
279
                call('If-Modified-Since', ims),
280
                call('If-Unmodified-Since', ius)])
281
            self.assertEqual(head.mock_calls[-1], call(
282
                '/%s/%s' % (self.client.account, self.client.container),
283
                *args,
284
                success=kwargs.pop('success', 204),
285
                **kwargs))
286

    
287
    @patch('%s.set_param' % rest_pkg)
288
    @patch('%s.set_header' % rest_pkg)
289
    @patch('%s.get' % rest_pkg, return_value=FR())
290
    def test_container_get(self, get, SH, SP):
291
        for pm in product(
292
                (None, 42),
293
                (None, 'X'),
294
                (None, 'some/prefix'),
295
                (None, 'delimiter'),
296
                (None, '/some/path'),
297
                ('json', 'some-format'),
298
                ([], ['k1', 'k2', 'k3']),
299
                (False, True),
300
                (None, 'unt1l-d473'),
301
                (None, 'y37-4n47h3r'),
302
                (None, '4n47h3r-d473'),
303
                ((), ('someval',)),
304
                (dict(), dict(success=400), dict(k='v', v='k'))):
305
            args, kwargs = pm[-2:]
306
            pm = pm[:-2]
307
            self.client.container_get(*(pm + args), **kwargs)
308
            lmt, mrk, prfx, dlm, path, frmt, meta, shr, unt = pm[:-2]
309
            exp = [call('limit', lmt, iff=lmt), call('marker', mrk, iff=mrk)]
310
            exp += [call('path', path)] if path else [
311
                call('prefix', prfx, iff=prfx),
312
                call('delimiter', dlm, iff=dlm)]
313
            exp += [call('format', frmt, iff=frmt), call('shared', iff=shr)]
314
            if meta:
315
                exp += [call('meta', ','.join(meta))]
316
            exp += [call('until', unt, iff=unt)]
317
            self.assertEqual(SP.mock_calls[- len(exp):], exp)
318
            ims, ius = pm[-2:]
319
            self.assertEqual(SH.mock_calls[-2:], [
320
                call('If-Modified-Since', ims),
321
                call('If-Unmodified-Since', ius)])
322
            self.assertEqual(get.mock_calls[-1], call(
323
                '/%s/%s' % (self.client.account, self.client.container),
324
                *args,
325
                success=kwargs.pop('success', 200),
326
                **kwargs))
327

    
328
    @patch('%s.set_header' % rest_pkg)
329
    @patch('%s.put' % rest_pkg, return_value=FR())
330
    def test_container_put(self, put, SH):
331
        for pm in product(
332
                (None, 42),
333
                (None, 'v3r51on1ng'),
334
                (dict(), dict(k1='v2'), dict(k2='v2', k3='v3')),
335
                ((), ('someval',)),
336
                (dict(), dict(success=400), dict(k='v', v='k'))):
337
            args, kwargs = pm[-2:]
338
            pm = pm[:-2]
339
            self.client.container_put(*(pm + args), **kwargs)
340
            quota, versioning, metas = pm[-3:]
341
            exp = [
342
                call('X-Container-Policy-Quota', quota),
343
                call('X-Container-Policy-Versioning', versioning)] + [
344
                call('X-Container-Meta-%s' % k, v) for k, v in metas.items()]
345
            self.assertEqual(SH.mock_calls[- len(exp):], exp)
346
            self.assertEqual(put.mock_calls[-1], call(
347
                '/%s/%s' % (self.client.account, self.client.container),
348
                *args,
349
                success=kwargs.pop('success', (201, 202)),
350
                **kwargs))
351

    
352
    @patch('%s.set_param' % rest_pkg)
353
    @patch('%s.set_header' % rest_pkg)
354
    @patch('%s.post' % rest_pkg, return_value=FR())
355
    def test_container_post(self, post, SH, SP):
356
        for pm in product(
357
                (True, False),
358
                ('json', 'some-format'),
359
                (None, 'quota'),
360
                (None, 'v3r51on1ng'),
361
                (dict(), dict(k1='v2'), dict(k2='v2', k3='v3')),
362
                (None, 'content-type'),
363
                (None, 42),
364
                (None, 'transfer-encoding'),
365
                ((), ('someval',)),
366
                (dict(), dict(success=400), dict(k='v', v='k'))):
367
            args, kwargs = pm[-2:]
368
            pm = pm[:-2]
369
            self.client.container_post(*(pm + args), **kwargs)
370
            upd, frmt = pm[:2]
371
            self.assertEqual(SP.mock_calls[-2:], [
372
                call('update', iff=upd),
373
                call('format', frmt, iff=frmt)])
374
            qta, vrs, metas, ctype, clen, trenc = pm[2:]
375
            prfx = 'X-Container-Meta-'
376
            exp = [
377
                call('X-Container-Policy-Quota', qta),
378
                call('X-Container-Policy-Versioning', vrs)] + [
379
                call('%s%s' % (prfx, k), v) for k, v in metas.items()] + [
380
                call('Content-Type', ctype),
381
                call('Content-Length', clen),
382
                call('Transfer-Encoding', trenc)]
383
            self.assertEqual(SH.mock_calls[- len(exp):], exp)
384
            ims, ius = pm[-2:]
385
            self.assertEqual(post.mock_calls[-1], call(
386
                '/%s/%s' % (self.client.account, self.client.container),
387
                *args,
388
                success=kwargs.pop('success', 202),
389
                **kwargs))
390

    
391
    @patch('%s.set_param' % rest_pkg)
392
    @patch('%s.delete' % rest_pkg, return_value=FR())
393
    def test_container_delete(self, delete, SP):
394
        for pm in product(
395
                (None, 'd473'),
396
                (None, 'd3l1m'),
397
                ((), ('someval',)),
398
                (dict(), dict(success=400), dict(k='v', v='k'))):
399
            args, kwargs = pm[-2:]
400
            pm = pm[:-2]
401
            self.client.container_delete(*(pm + args), **kwargs)
402
            unt, dlm = pm[-2:]
403
            self.assertEqual(SP.mock_calls[-2:], [
404
                call('until', unt, iff=unt),
405
                call('delimiter', dlm, iff=dlm)])
406
            self.assertEqual(delete.mock_calls[-1], call(
407
                '/%s/%s' % (self.client.account, self.client.container),
408
                *args,
409
                success=kwargs.pop('success', 204),
410
                **kwargs))
411

    
412
    @patch('%s.set_param' % rest_pkg)
413
    @patch('%s.set_header' % rest_pkg)
414
    @patch('%s.head' % rest_pkg, return_value=FR())
415
    def test_object_head(self, head, SH, SP):
416
        for pm in product(
417
                (None, 'v3r510n'),
418
                (None, '1f-374g'),
419
                (None, '1f-n0-74g'),
420
                (None, '1f-m0d-51nc3'),
421
                (None, '1f-unm0d-51nc3'),
422
                ((), ('someval',)),
423
                (dict(), dict(success=400), dict(k='v', v='k'))):
424
            args, kwargs = pm[-2:]
425
            pm = pm[:-2]
426
            self.client.object_head(obj, *(pm + args), **kwargs)
427
            vrs, etag, netag, ims, ius = pm[:5]
428
            self.assertEqual(
429
                SP.mock_calls[-1],
430
                call('version', vrs, iff=vrs))
431
            self.assertEqual(SH.mock_calls[-4:], [
432
                call('If-Match', etag),
433
                call('If-None-Match', netag),
434
                call('If-Modified-Since', ims),
435
                call('If-Unmodified-Since', ius)])
436
            acc, cont = self.client.account, self.client.container
437
            self.assertEqual(head.mock_calls[-1], call(
438
                '/%s/%s/%s' % (acc, cont, obj),
439
                *args,
440
                success=kwargs.pop('success', 200),
441
                **kwargs))
442

    
443
    @patch('%s.set_param' % rest_pkg)
444
    @patch('%s.set_header' % rest_pkg)
445
    @patch('%s.get' % rest_pkg, return_value=FR())
446
    def test_object_get(self, get, SH, SP):
447
        for pm in product(
448
                ('json', 'f0rm47'),
449
                (False, True),
450
                (None, 'v3r510n'),
451
                (None, 'range=74-63'),
452
                (False, True),
453
                (None, '3746'),
454
                (None, 'non-3746'),
455
                (None, '1f-m0d'),
456
                (None, '1f-unm0d'),
457
                ((), ('someval',)),
458
                (dict(), dict(success=400), dict(k='v', v='k'))):
459
            args, kwargs = pm[-2:]
460
            pm = pm[:-2]
461
            self.client.object_get(obj, *(pm + args), **kwargs)
462
            format, hashmap, version = pm[:3]
463
            self.assertEqual(SP.mock_calls[-3:], [
464
                call('format', format, iff=format),
465
                call('hashmap', hashmap, iff=hashmap),
466
                call('version', version, iff=version)])
467
            rng, ifrng, im, inm, ims, ius = pm[-6:]
468
            self.assertEqual(SH.mock_calls[-6:], [
469
                call('Range', rng),
470
                call('If-Range', '', ifrng and rng),
471
                call('If-Match', im),
472
                call('If-None-Match', inm),
473
                call('If-Modified-Since', ims),
474
                call('If-Unmodified-Since', ius)])
475
            acc, cont = self.client.account, self.client.container
476
            self.assertEqual(get.mock_calls[-1], call(
477
                '/%s/%s/%s' % (acc, cont, obj),
478
                *args,
479
                success=kwargs.pop('success', 200),
480
                **kwargs))
481

    
482
    @patch('%s.set_param' % rest_pkg)
483
    @patch('%s.set_header' % rest_pkg)
484
    @patch('%s.put' % rest_pkg, return_value=FR())
485
    def test_object_put(self, put, SH, SP):
486
        for pm in product(
487
                ('json', 'f0rm47'),
488
                (False, True),
489
                (None, 'delim',),
490
                (dict(), dict(read=['u1', 'g2'], write=['u1'])),
491
                (False, True),
492
                (dict(), dict(k2='v2', k3='v3')),
493
                ((), ('someval',)),
494
                (dict(), dict(success=400), dict(k='v', v='k'))):
495
            args, kwargs = pm[-2:]
496
            pm = pm[:-2]
497
            terms = [None] * 13
498
            for i in range(len(terms)):
499
                if randint(0, 2):
500
                    terms[i] = 'val_%s' % randint(13, 1024)
501
            self.client.object_put(
502
                obj,
503
                *(pm[:3] + tuple(terms) + pm[3:] + args),
504
                **kwargs)
505
            format, hashmap, delimiter = pm[:3]
506
            self.assertEqual(SP.mock_calls[-3:], [
507
                call('format', format, iff=format),
508
                call('hashmap', hashmap, iff=hashmap),
509
                call('delimiter', delimiter, iff=delimiter)])
510
            (
511
                im, inm, etag, clen, ctype, trenc,
512
                cp, mv, srcacc, srcvrs, conenc, condis, mnf) = terms
513
            perms, public, metas = pm[3:]
514
            exp = [
515
                call('If-Match', im),
516
                call('If-None-Match', inm),
517
                call('ETag', etag),
518
                call('Content-Length', clen),
519
                call('Content-Type', ctype),
520
                call('Transfer-Encoding', trenc),
521
                call('X-Copy-From', cp),
522
                call('X-Move-From', mv),
523
                call('X-Source-Account', srcacc),
524
                call('X-Source-Version', srcvrs),
525
                call('Content-Encoding', conenc),
526
                call('Content-Disposition', condis),
527
                call('X-Object-Manifest', mnf)]
528
            if perms:
529
                perm_str = ''
530
                for ptype, pval in perms.items():
531
                    if pval:
532
                        perm_str += ';' if perm_str else ''
533
                        perm_str += '%s=%s' % (ptype, ','.join(pval))
534
                exp += [call('X-Object-Sharing', perm_str)]
535
            exp += [call('X-Object-Public', public)]
536
            for k, v in metas.items():
537
                exp += [call('X-Object-Meta-%s' % k, v)]
538
            self.assertEqual(SH.mock_calls[- len(exp):], exp)
539
            acc, cont = self.client.account, self.client.container
540
            self.assertEqual(put.mock_calls[-1], call(
541
                '/%s/%s/%s' % (acc, cont, obj),
542
                *args,
543
                success=kwargs.pop('success', 201),
544
                **kwargs))
545

    
546
    @patch('%s.set_param' % rest_pkg)
547
    @patch('%s.set_header' % rest_pkg)
548
    @patch('%s.copy' % rest_pkg, return_value=FR())
549
    def test_object_copy(self, copy, SH, SP):
550
        dest = 'dest1n4710n'
551
        for pm in product(
552
                ('json', 'f0rm47'),
553
                (False, True),
554
                (None, 'ifmatch'),
555
                (None, 'ifnonematch'),
556
                (None, 'destinationaccount'),
557
                (None, 'content-type'),
558
                (None, 'content-encoding'),
559
                (None, 'content-disp'),
560
                (None, 'source-version'),
561
                (dict(), dict(read=['u1', 'g2'], write=['u1'])),
562
                (False, True),
563
                (dict(), dict(k2='v2', k3='v3')),
564
                ((), ('someval',)),
565
                (dict(), dict(success=400), dict(k='v', v='k'))):
566
            args, kwargs = pm[-2:]
567
            pm = pm[:-2]
568
            self.client.object_copy(obj, dest, *(pm + args), **kwargs)
569
            format, ict = pm[:2]
570
            self.assertEqual(SP.mock_calls[-2:], [
571
                call('format', format, iff=format),
572
                call('ignore_content_type', iff=ict)])
573
            im, inm, da, ct, ce, cd, sv, perms, public, metas = pm[2:]
574
            exp = [call('If-Match', im),
575
                call('If-None-Match', inm),
576
                call('Destination', dest),
577
                call('Destination-Account', da),
578
                call('Content-Type', ct),
579
                call('Content-Encoding', ce),
580
                call('Content-Disposition', cd),
581
                call('X-Source-Version', sv)]
582
            if perms:
583
                perm_str = ''
584
                for ptype, pval in perms.items():
585
                    if pval:
586
                        perm_str += ';' if perm_str else ''
587
                        perm_str += '%s=%s' % (ptype, ','.join(pval))
588
                exp += [call('X-Object-Sharing', perm_str)]
589
            exp += [call('X-Object-Public', public)]
590
            for k, v in metas.items():
591
                exp += [call('X-Object-Meta-%s' % k, v)]
592
            self.assertEqual(SH.mock_calls[- len(exp):], exp)
593
            acc, cont = self.client.account, self.client.container
594
            self.assertEqual(copy.mock_calls[-1], call(
595
                '/%s/%s/%s' % (acc, cont, obj),
596
                *args,
597
                success=kwargs.pop('success', 201),
598
                **kwargs))
599

    
600
    @patch('%s.set_param' % rest_pkg)
601
    @patch('%s.set_header' % rest_pkg)
602
    @patch('%s.move' % rest_pkg, return_value=FR())
603
    def test_object_move(self, move, SH, SP):
604
        for pm in product(
605
                ('json', 'f0rm47'),
606
                (False, True),
607
                (None, 'ifmatch'),
608
                (None, 'ifnonematch'),
609
                (None, 'destination'),
610
                (None, 'destinationaccount'),
611
                (None, 'content-type'),
612
                (None, 'content-encoding'),
613
                (None, 'content-disp'),
614
                (dict(), dict(read=['u1', 'g2'], write=['u1'])),
615
                (False, True),
616
                (dict(), dict(k2='v2', k3='v3')),
617
                ((), ('someval',)),
618
                (dict(), dict(success=400), dict(k='v', v='k'))):
619
            args, kwargs = pm[-2:]
620
            pm = pm[:-2]
621
            self.client.object_move(obj, *(pm + args), **kwargs)
622
            format, ict = pm[:2]
623
            self.assertEqual(SP.mock_calls[-2:], [
624
                call('format', format, iff=format),
625
                call('ignore_content_type', iff=ict)])
626
            im, inm, d, da, ct, ce, cd, perms, public, metas = pm[2:]
627
            exp = [call('If-Match', im),
628
                call('If-None-Match', inm),
629
                call('Destination', d),
630
                call('Destination-Account', da),
631
                call('Content-Type', ct),
632
                call('Content-Encoding', ce),
633
                call('Content-Disposition', cd)]
634
            if perms:
635
                perm_str = ''
636
                for ptype, pval in perms.items():
637
                    if pval:
638
                        perm_str += ';' if perm_str else ''
639
                        perm_str += '%s=%s' % (ptype, ','.join(pval))
640
                exp += [call('X-Object-Sharing', perm_str)]
641
            exp += [call('X-Object-Public', public)]
642
            for k, v in metas.items():
643
                exp += [call('X-Object-Meta-%s' % k, v)]
644
            self.assertEqual(SH.mock_calls[- len(exp):], exp)
645
            acc, cont = self.client.account, self.client.container
646
            self.assertEqual(move.mock_calls[-1], call(
647
                '/%s/%s/%s' % (acc, cont, obj),
648
                *args,
649
                success=kwargs.pop('success', 201),
650
                **kwargs))
651

    
652
    @patch('%s.set_param' % rest_pkg)
653
    @patch('%s.set_header' % rest_pkg)
654
    @patch('%s.post' % rest_pkg, return_value=FR())
655
    def test_object_post(self, post, SH, SP):
656
        for pm in product(
657
                ('json', 'f0rm47'),
658
                (False, True),
659
                (dict(), dict(read=['u1', 'g2'], write=['u1'])),
660
                (False, True),
661
                (dict(), dict(k2='v2', k3='v3')),
662
                ((), ('someval',)),
663
                (dict(), dict(success=400), dict(k='v', v='k'))):
664
            args, kwargs = pm[-2:]
665
            pm = pm[:-2]
666
            terms = [None] * 13
667
            for i in range(len(terms)):
668
                if randint(0, 2):
669
                    terms[i] = 'val_%s' % randint(13, 1024)
670
            self.client.object_post(
671
                obj,
672
                *(pm[:2] + tuple(terms) + pm[2:] + args),
673
                **kwargs)
674
            format, update = pm[:2]
675
            self.assertEqual(SP.mock_calls[-2:], [
676
                call('format', format, iff=format),
677
                call('update', iff=update)])
678
            (
679
                im, inm, clen, ctype, crng, trenc, cenc,
680
                condis, srcobj, srcacc, srcvrs, obytes, mnfs) = terms
681
            exp = [
682
                call('If-Match', im),
683
                call('If-None-Match', inm),
684
                call('Content-Length', clen, iff=not trenc),
685
                call('Content-Type', ctype),
686
                call('Content-Range', crng),
687
                call('Transfer-Encoding', trenc),
688
                call('Content-Encoding', cenc),
689
                call('Content-Disposition', condis),
690
                call('X-Source-Object', srcobj),
691
                call('X-Source-Account', srcacc),
692
                call('X-Source-Version', srcvrs),
693
                call('X-Object-Bytes', obytes),
694
                call('X-Object-Manifest', mnfs)]
695
            perms, public, metas = pm[2:]
696
            if perms:
697
                perm_str = ''
698
                for ptype, pval in perms.items():
699
                    if pval:
700
                        perm_str += ';' if perm_str else ''
701
                        perm_str += '%s=%s' % (ptype, ','.join(pval))
702
                exp += [call('X-Object-Sharing', perm_str)]
703
            exp += [call('X-Object-Public', public)]
704
            for k, v in metas.items():
705
                exp += [call('X-Object-Meta-%s' % k, v)]
706
            self.assertEqual(SH.mock_calls[- len(exp):], exp)
707
            acc, cont = self.client.account, self.client.container
708
            self.assertEqual(post.mock_calls[-1], call(
709
                '/%s/%s/%s' % (acc, cont, obj),
710
                *args,
711
                success=kwargs.pop('success', (202, 204)),
712
                **kwargs))
713

    
714
    @patch('%s.set_param' % rest_pkg)
715
    @patch('%s.delete' % rest_pkg, return_value=FR())
716
    def test_object_delete(self, delete, SP):
717
        for pm in product(
718
                (None, 'until'),
719
                (None, 'delim'),
720
                ((), ('someval',)),
721
                (dict(), dict(success=400), dict(k='v', v='k'))):
722
            args, kwargs = pm[-2:]
723
            pm = pm[:-2]
724
            self.client.object_delete(
725
                obj,
726
                *(pm + args),
727
                **kwargs)
728
            until, dlm = pm[-2:]
729
            self.assertEqual(SP.mock_calls[-2:], [
730
                call('until', until, iff=until),
731
                call('delimiter', dlm, iff=dlm)])
732
            acc, cont = self.client.account, self.client.container
733
            self.assertEqual(delete.mock_calls[-1], call(
734
                '/%s/%s/%s' % (acc, cont, obj),
735
                *args,
736
                success=kwargs.pop('success', 204),
737
                **kwargs))
738

    
739

    
740
class PithosClient(TestCase):
741

    
742
    files = []
743

    
744
    def _create_temp_file(self, num_of_blocks):
745
        self.files.append(NamedTemporaryFile())
746
        tmpFile = self.files[-1]
747
        file_size = num_of_blocks * 4 * 1024 * 1024
748
        print('\n\tCreate tmp file')
749
        tmpFile.write(urandom(file_size))
750
        tmpFile.flush()
751
        tmpFile.seek(0)
752
        print('\t\tDone')
753
        return tmpFile
754

    
755
    def assert_dicts_are_equal(self, d1, d2):
756
        for k, v in d1.items():
757
            self.assertTrue(k in d2)
758
            if isinstance(v, dict):
759
                self.assert_dicts_are_equal(v, d2[k])
760
            else:
761
                self.assertEqual(unicode(v), unicode(d2[k]))
762

    
763
    def setUp(self):
764
        self.url = 'https://www.example.com/pithos'
765
        self.token = 'p17h0570k3n'
766
        self.client = pithos.PithosClient(self.url, self.token)
767
        self.client.account = user_id
768
        self.client.container = 'c0nt@1n3r_i'
769

    
770
    def tearDown(self):
771
        FR.headers = dict()
772
        FR.status_code = 200
773
        FR.json = dict()
774
        FR.content = FR.json
775
        for f in self.files:
776
            f.close()
777

    
778
    #  Pithos+ methods that extend storage API
779

    
780
    @patch('%s.account_head' % pithos_pkg, return_value=FR())
781
    def test_get_account_info(self, AH):
782
        FR.headers = account_info
783
        for until in (None, 'un71L-d473'):
784
            r = self.client.get_account_info(until=until)
785
            self.assert_dicts_are_equal(r, account_info)
786
            self.assertEqual(AH.mock_calls[-1], call(until=until))
787
        FR.status_code = 401
788
        self.assertRaises(ClientError, self.client.get_account_info)
789

    
790
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
791
    def test_del_account_meta(self, AP):
792
        keys = ['k1', 'k2', 'k3']
793
        for key in keys:
794
            self.client.del_account_meta(key)
795
            self.assertEqual(
796
                AP.mock_calls[-1],
797
                call(update=True, metadata={key: ''}))
798

    
799
    @patch('%s.container_head' % pithos_pkg, return_value=FR())
800
    def test_get_container_info(self, CH):
801
        FR.headers = container_info
802
        r = self.client.get_container_info()
803
        self.assert_dicts_are_equal(r, container_info)
804
        u = 'some date'
805
        r = self.client.get_container_info(until=u)
806
        self.assertEqual(CH.mock_calls, [call(until=None), call(until=u)])
807

    
808
    @patch('%s.account_get' % pithos_pkg, return_value=FR())
809
    def test_list_containers(self, get):
810
        FR.json = container_list
811
        r = self.client.list_containers()
812
        get.assert_called_once_with()
813
        for i in range(len(r)):
814
            self.assert_dicts_are_equal(r[i], container_list[i])
815

    
816
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
817
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
818
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
819
    def test_upload_object(self, OP, CP, GCI):
820
        num_of_blocks = 8
821
        tmpFile = self._create_temp_file(num_of_blocks)
822

    
823
        # Without kwargs
824
        self.client.upload_object(obj, tmpFile)
825
        self.assertEqual(GCI.mock_calls[-1], call())
826
        [call1, call2] = OP.mock_calls
827

    
828
        (args1, kwargs1) = call1[1:3]
829
        (args2, kwargs2) = call2[1:3]
830
        self.assertEqual(args1, (obj,))
831
        expected1 = dict(
832
            hashmap=True,
833
            success=(201, 409),
834
            format='json',
835
            json=dict(
836
                hashes=['s0m3h@5h'] * num_of_blocks,
837
                bytes=num_of_blocks * 4 * 1024 * 1024),
838
            etag=None,
839
            content_encoding=None,
840
            content_type='application/octet-stream',
841
            content_disposition=None,
842
            public=None,
843
            permissions=None)
844
        for k, v in expected1.items():
845
            if k == 'json':
846
                self.assertEqual(len(v['hashes']), len(kwargs1[k]['hashes']))
847
                self.assertEqual(v['bytes'], kwargs1[k]['bytes'])
848
            else:
849
                self.assertEqual(v, kwargs1[k])
850

    
851
        (args2, kwargs2) = call2[1:3]
852
        self.assertEqual(args2, (obj,))
853
        expected2 = dict(
854
            json=dict(
855
                hashes=['s0m3h@5h'] * num_of_blocks,
856
                bytes=num_of_blocks * 4 * 1024 * 1024),
857
            content_type='application/octet-stream',
858
            hashmap=True,
859
            success=201,
860
            format='json')
861
        for k, v in expected2.items():
862
            if k == 'json':
863
                self.assertEqual(len(v['hashes']), len(kwargs2[k]['hashes']))
864
                self.assertEqual(v['bytes'], kwargs2[k]['bytes'])
865
            else:
866
                self.assertEqual(v, kwargs2[k])
867

    
868
        mock_offset = 2
869

    
870
        #  With progress bars
871
        try:
872
            from progress.bar import ShadyBar
873
            blck_bar = ShadyBar('Mock blck calc.')
874
            upld_bar = ShadyBar('Mock uplds')
875
        except ImportError:
876
            blck_bar = None
877
            upld_bar = None
878

    
879
        if blck_bar and upld_bar:
880

    
881
            def blck_gen(n):
882
                for i in blck_bar.iter(range(n)):
883
                    yield
884
                yield
885

    
886
            def upld_gen(n):
887
                for i in upld_bar.iter(range(n)):
888
                    yield
889
                yield
890

    
891
            tmpFile.seek(0)
892
            self.client.upload_object(
893
                obj, tmpFile,
894
                hash_cb=blck_gen, upload_cb=upld_gen)
895

    
896
            for i, c in enumerate(OP.mock_calls[-mock_offset:]):
897
                self.assertEqual(OP.mock_calls[i], c)
898

    
899
        #  With content-type
900
        tmpFile.seek(0)
901
        ctype = 'video/mpeg'
902
        sharing = dict(read=['u1', 'g1', 'u2'], write=['u1'])
903
        self.client.upload_object(obj, tmpFile,
904
            content_type=ctype, sharing=sharing)
905
        self.assertEqual(OP.mock_calls[-1][2]['content_type'], ctype)
906
        self.assert_dicts_are_equal(
907
            OP.mock_calls[-2][2]['permissions'],
908
            sharing)
909

    
910
        # With other args
911
        tmpFile.seek(0)
912
        kwargs = dict(
913
            etag='s0m3E74g',
914
            content_type=ctype,
915
            content_disposition=ctype + 'd15p051710n',
916
            public=True,
917
            content_encoding='802.11')
918
        self.client.upload_object(obj, tmpFile, **kwargs)
919
        for arg, val in kwargs.items():
920
            self.assertEqual(OP.mock_calls[-2][2][arg], val)
921

    
922
    def test_get_object_info(self):
923
        FR.headers = object_info
924
        version = 'v3r510n'
925
        with patch.object(
926
                pithos.PithosClient, 'object_head',
927
                return_value=FR()) as head:
928
            r = self.client.get_object_info(obj)
929
            self.assertEqual(r, object_info)
930
            r = self.client.get_object_info(obj, version=version)
931
            self.assertEqual(head.mock_calls, [
932
                call(obj, version=None),
933
                call(obj, version=version)])
934
        with patch.object(
935
                pithos.PithosClient, 'object_head',
936
                side_effect=ClientError('Obj not found', 404)):
937
            self.assertRaises(
938
                ClientError,
939
                self.client.get_object_info,
940
                obj, version=version)
941

    
942
    @patch('%s.get_object_info' % pithos_pkg, return_value=object_info)
943
    def test_get_object_meta(self, GOI):
944
        for version in (None, 'v3r510n'):
945
            r = self.client.get_object_meta(obj, version)
946
            for k in [k for k in object_info if k.startswith('x-object-meta')]:
947
                self.assertEqual(r.pop(k), object_info[k])
948
            self.assertFalse(len(r))
949
            self.assertEqual(GOI.mock_calls[-1], call(obj, version=version))
950

    
951
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
952
    def test_del_object_meta(self, post):
953
        metakey = '50m3m3t4k3y'
954
        self.client.del_object_meta(obj, metakey)
955
        post.assert_called_once_with(obj, update=True, metadata={metakey: ''})
956

    
957
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
958
    def test_copy_object(self, put):
959
        src_cont = 'src-c0nt41n3r'
960
        src_obj = 'src-0bj'
961
        dst_cont = 'dst-c0nt41n3r'
962
        dst_obj = 'dst-0bj'
963
        expected = call(
964
            src_obj,
965
            content_length=0,
966
            source_account=None,
967
            success=201,
968
            copy_from='/%s/%s' % (src_cont, src_obj),
969
            delimiter=None,
970
            content_type=None,
971
            source_version=None,
972
            public=False)
973
        self.client.copy_object(src_cont, src_obj, dst_cont)
974
        self.assertEqual(put.mock_calls[-1], expected)
975
        self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
976
        self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
977
        kwargs = dict(
978
            source_version='src-v3r510n',
979
            source_account='src-4cc0un7',
980
            public=True,
981
            content_type='c0n73n7Typ3',
982
            delimiter='5')
983
        self.client.copy_object(src_cont, src_obj, dst_cont, **kwargs)
984
        for k, v in kwargs.items():
985
            self.assertEqual(v, put.mock_calls[-1][2][k])
986

    
987
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
988
    def test_move_object(self, put):
989
        src_cont = 'src-c0nt41n3r'
990
        src_obj = 'src-0bj'
991
        dst_cont = 'dst-c0nt41n3r'
992
        dst_obj = 'dst-0bj'
993
        expected = call(
994
            src_obj,
995
            content_length=0,
996
            source_account=None,
997
            success=201,
998
            move_from='/%s/%s' % (src_cont, src_obj),
999
            delimiter=None,
1000
            content_type=None,
1001
            source_version=None,
1002
            public=False)
1003
        self.client.move_object(src_cont, src_obj, dst_cont)
1004
        self.assertEqual(put.mock_calls[-1], expected)
1005
        self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
1006
        self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
1007
        kwargs = dict(
1008
            source_version='src-v3r510n',
1009
            source_account='src-4cc0un7',
1010
            public=True,
1011
            content_type='c0n73n7Typ3',
1012
            delimiter='5')
1013
        self.client.move_object(src_cont, src_obj, dst_cont, **kwargs)
1014
        for k, v in kwargs.items():
1015
            self.assertEqual(v, put.mock_calls[-1][2][k])
1016

    
1017
    #  Pithos+ only methods
1018

    
1019
    @patch('%s.container_delete' % pithos_pkg, return_value=FR())
1020
    def test_purge_container(self, CD):
1021
        self.client.purge_container()
1022
        self.assertTrue('until' in CD.mock_calls[-1][2])
1023
        cont = self.client.container
1024
        self.client.purge_container('another-container')
1025
        self.assertEqual(self.client.container, cont)
1026

    
1027
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
1028
    def test_upload_object_unchunked(self, put):
1029
        num_of_blocks = 8
1030
        tmpFile = self._create_temp_file(num_of_blocks)
1031
        expected = dict(
1032
                success=201,
1033
                data=num_of_blocks * 4 * 1024 * 1024,
1034
                etag='some-etag',
1035
                content_encoding='some content_encoding',
1036
                content_type='some content-type',
1037
                content_disposition='some content_disposition',
1038
                public=True,
1039
                permissions=dict(read=['u1', 'g1', 'u2'], write=['u1']))
1040
        self.client.upload_object_unchunked(obj, tmpFile)
1041
        self.assertEqual(put.mock_calls[-1][1], (obj,))
1042
        self.assertEqual(
1043
            sorted(put.mock_calls[-1][2].keys()),
1044
            sorted(expected.keys()))
1045
        kwargs = dict(expected)
1046
        kwargs.pop('success')
1047
        kwargs['size'] = kwargs.pop('data')
1048
        kwargs['sharing'] = kwargs.pop('permissions')
1049
        tmpFile.seek(0)
1050
        self.client.upload_object_unchunked(obj, tmpFile, **kwargs)
1051
        pmc = put.mock_calls[-1][2]
1052
        for k, v in expected.items():
1053
            if k == 'data':
1054
                self.assertEqual(len(pmc[k]), v)
1055
            else:
1056
                self.assertEqual(pmc[k], v)
1057
        self.assertRaises(
1058
            ClientError,
1059
            self.client.upload_object_unchunked,
1060
            obj, tmpFile, withHashFile=True)
1061

    
1062
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
1063
    def test_create_object_by_manifestation(self, put):
1064
        manifest = '%s/%s' % (self.client.container, obj)
1065
        kwargs = dict(
1066
                etag='some-etag',
1067
                content_encoding='some content_encoding',
1068
                content_type='some content-type',
1069
                content_disposition='some content_disposition',
1070
                public=True,
1071
                sharing=dict(read=['u1', 'g1', 'u2'], write=['u1']))
1072
        self.client.create_object_by_manifestation(obj)
1073
        expected = dict(content_length=0, manifest=manifest)
1074
        for k in kwargs:
1075
            expected['permissions' if k == 'sharing' else k] = None
1076
        self.assertEqual(put.mock_calls[-1], call(obj, **expected))
1077
        self.client.create_object_by_manifestation(obj, **kwargs)
1078
        expected.update(kwargs)
1079
        expected['permissions'] = expected.pop('sharing')
1080
        self.assertEqual(put.mock_calls[-1], call(obj, **expected))
1081

    
1082
    @patch('%s.get_object_hashmap' % pithos_pkg, return_value=object_hashmap)
1083
    @patch('%s.object_get' % pithos_pkg, return_value=FR())
1084
    def test_download_object(self, GET, GOH):
1085
        num_of_blocks = 8
1086
        tmpFile = self._create_temp_file(num_of_blocks)
1087
        FR.content = tmpFile.read(4 * 1024 * 1024)
1088
        tmpFile = self._create_temp_file(num_of_blocks)
1089
        num_of_blocks = len(object_hashmap['hashes'])
1090
        kwargs = dict(
1091
            resume=True,
1092
            version='version',
1093
            range_str='10-20',
1094
            if_match='if and only if',
1095
            if_none_match='if and only not',
1096
            if_modified_since='what if not?',
1097
            if_unmodified_since='this happens if not!',
1098
            async_headers=dict(Range='bytes=0-88888888'))
1099

    
1100
        self.client.download_object(obj, tmpFile)
1101
        self.assertEqual(len(GET.mock_calls), num_of_blocks)
1102
        self.assertEqual(GET.mock_calls[-1][1], (obj,))
1103
        for k, v in kwargs.items():
1104
            if k == 'async_headers':
1105
                self.assertTrue('Range' in GET.mock_calls[-1][2][k])
1106
            elif k in ('resume', 'range_str'):
1107
                continue
1108
            else:
1109
                self.assertEqual(GET.mock_calls[-1][2][k], None)
1110

    
1111
        #  Check ranges are consecutive
1112
        starts = []
1113
        ends = []
1114
        for c in GET.mock_calls:
1115
            rng_str = c[2]['async_headers']['Range']
1116
            (start, rng_str) = rng_str.split('=')
1117
            (start, end) = rng_str.split('-')
1118
            starts.append(start)
1119
            ends.append(end)
1120
        ends = sorted(ends)
1121
        for i, start in enumerate(sorted(starts)):
1122
            if i:
1123
                int(ends[i - 1]) == int(start) - 1
1124

    
1125
        #  With progress bars
1126
        try:
1127
            from progress.bar import ShadyBar
1128
            dl_bar = ShadyBar('Mock dl')
1129
        except ImportError:
1130
            dl_bar = None
1131

    
1132
        if dl_bar:
1133

    
1134
            def blck_gen(n):
1135
                for i in dl_bar.iter(range(n)):
1136
                    yield
1137
                yield
1138

    
1139
            tmpFile.seek(0)
1140
            self.client.download_object(obj, tmpFile, download_cb=blck_gen)
1141
            self.assertEqual(len(GET.mock_calls), 2 * num_of_blocks)
1142

    
1143
        tmpFile.seek(0)
1144
        kwargs.pop('async_headers')
1145
        kwargs.pop('resume')
1146
        self.client.download_object(obj, tmpFile, **kwargs)
1147
        for k, v in kwargs.items():
1148
            if k == 'range_str':
1149
                self.assertEqual(
1150
                    GET.mock_calls[-1][2]['data_range'],
1151
                    'bytes=%s' % v)
1152
            else:
1153
                self.assertEqual(GET.mock_calls[-1][2][k], v)
1154

    
1155
        #  ALl options on no tty
1156

    
1157
        def foo():
1158
            return True
1159

    
1160
        tmpFile.seek(0)
1161
        tmpFile.isatty = foo
1162
        self.client.download_object(obj, tmpFile, **kwargs)
1163
        for k, v in kwargs.items():
1164
            if k == 'range_str':
1165
                self.assertTrue('data_range' in GET.mock_calls[-1][2])
1166
            else:
1167
                self.assertEqual(GET.mock_calls[-1][2][k], v)
1168

    
1169
    def test_get_object_hashmap(self):
1170
        FR.json = object_hashmap
1171
        for empty in (304, 412):
1172
            with patch.object(
1173
                    pithos.PithosClient, 'object_get',
1174
                    side_effect=ClientError('Empty', status=empty)):
1175
                r = self.client.get_object_hashmap(obj)
1176
                self.assertEqual(r, {})
1177
        exp_args = dict(
1178
            hashmap=True,
1179
            data_range=None,
1180
            version=None,
1181
            if_etag_match=None,
1182
            if_etag_not_match=None,
1183
            if_modified_since=None,
1184
            if_unmodified_since=None)
1185
        kwargs = dict(
1186
            version='s0m3v3r51on',
1187
            if_match='if match',
1188
            if_none_match='if non match',
1189
            if_modified_since='some date here',
1190
            if_unmodified_since='some date here',
1191
            data_range='10-20')
1192
        with patch.object(
1193
                pithos.PithosClient, 'object_get',
1194
                return_value=FR()) as get:
1195
            r = self.client.get_object_hashmap(obj)
1196
            self.assertEqual(r, object_hashmap)
1197
            self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
1198
            r = self.client.get_object_hashmap(obj, **kwargs)
1199
            exp_args['if_etag_match'] = kwargs.pop('if_match')
1200
            exp_args['if_etag_not_match'] = kwargs.pop('if_none_match')
1201
            exp_args.update(kwargs)
1202
            self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
1203

    
1204
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
1205
    def test_set_account_group(self, post):
1206
        (group, usernames) = ('aU53rGr0up', ['u1', 'u2', 'u3'])
1207
        self.client.set_account_group(group, usernames)
1208
        post.assert_called_once_with(update=True, groups={group: usernames})
1209

    
1210
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
1211
    def test_del_account_group(self, post):
1212
        group = 'aU53rGr0up'
1213
        self.client.del_account_group(group)
1214
        post.assert_called_once_with(update=True, groups={group: []})
1215

    
1216
    @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
1217
    def test_get_account_quota(self, GAI):
1218
        key = 'x-account-policy-quota'
1219
        r = self.client.get_account_quota()
1220
        GAI.assert_called_once_with()
1221
        self.assertEqual(r[key], account_info[key])
1222

    
1223
    @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
1224
    def test_get_account_versioning(self, GAI):
1225
        key = 'x-account-policy-versioning'
1226
        r = self.client.get_account_versioning()
1227
        GAI.assert_called_once_with()
1228
        self.assertEqual(r[key], account_info[key])
1229

    
1230
    def test_get_account_meta(self):
1231
        key = 'x-account-meta-'
1232
        with patch.object(
1233
                pithos.PithosClient, 'get_account_info',
1234
                return_value=account_info):
1235
            r = self.client.get_account_meta()
1236
            keys = [k for k in r if k.startswith(key)]
1237
            self.assertFalse(keys)
1238
        acc_info = dict(account_info)
1239
        acc_info['%sk1' % key] = 'v1'
1240
        acc_info['%sk2' % key] = 'v2'
1241
        acc_info['%sk3' % key] = 'v3'
1242
        with patch.object(
1243
                pithos.PithosClient, 'get_account_info',
1244
                return_value=acc_info):
1245
            r = self.client.get_account_meta()
1246
            for k in [k for k in acc_info if k.startswith(key)]:
1247
                self.assertEqual(r[k], acc_info[k])
1248

    
1249
    def test_get_account_group(self):
1250
        key = 'x-account-group-'
1251
        with patch.object(
1252
                pithos.PithosClient, 'get_account_info',
1253
                return_value=account_info):
1254
            r = self.client.get_account_group()
1255
            keys = [k for k in r if k.startswith(key)]
1256
            self.assertFalse(keys)
1257
        acc_info = dict(account_info)
1258
        acc_info['%sk1' % key] = 'g1'
1259
        acc_info['%sk2' % key] = 'g2'
1260
        acc_info['%sk3' % key] = 'g3'
1261
        with patch.object(
1262
                pithos.PithosClient, 'get_account_info',
1263
                return_value=acc_info):
1264
            r = self.client.get_account_group()
1265
            for k in [k for k in acc_info if k.startswith(key)]:
1266
                self.assertEqual(r[k], acc_info[k])
1267

    
1268
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
1269
    def test_set_account_meta(self, post):
1270
        metas = dict(k1='v1', k2='v2', k3='v3')
1271
        self.client.set_account_meta(metas)
1272
        post.assert_called_once_with(update=True, metadata=metas)
1273

    
1274
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
1275
    def test_set_account_quota(self, post):
1276
        qu = 1024
1277
        self.client.set_account_quota(qu)
1278
        post.assert_called_once_with(update=True, quota=qu)
1279

    
1280
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
1281
    def test_set_account_versioning(self, post):
1282
        vrs = 'n3wV3r51on1ngTyp3'
1283
        self.client.set_account_versioning(vrs)
1284
        post.assert_called_once_with(update=True, versioning=vrs)
1285

    
1286
    @patch('%s.container_delete' % pithos_pkg, return_value=FR())
1287
    def test_del_container(self, delete):
1288
        for kwarg in (
1289
                dict(delimiter=None, until=None),
1290
                dict(delimiter='X', until='50m3d473')):
1291
            self.client.del_container(**kwarg)
1292
            expected = dict(kwarg)
1293
            expected['success'] = (204, 404, 409)
1294
            self.assertEqual(delete.mock_calls[-1], call(**expected))
1295
        for status_code in (404, 409):
1296
            FR.status_code = status_code
1297
            self.assertRaises(ClientError, self.client.del_container)
1298

    
1299
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1300
    def test_get_container_versioning(self, GCI):
1301
        key = 'x-container-policy-versioning'
1302
        cont = 'c0n7-417'
1303
        bu_cnt = self.client.container
1304
        for container in (None, cont):
1305
            r = self.client.get_container_versioning(container=container)
1306
            self.assertEqual(r[key], container_info[key])
1307
            self.assertEqual(GCI.mock_calls[-1], call())
1308
            self.assertEqual(bu_cnt, self.client.container)
1309

    
1310
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1311
    def test_get_container_quota(self, GCI):
1312
        key = 'x-container-policy-quota'
1313
        cont = 'c0n7-417'
1314
        bu_cnt = self.client.container
1315
        for container in (None, cont):
1316
            r = self.client.get_container_quota(container=container)
1317
            self.assertEqual(r[key], container_info[key])
1318
            self.assertEqual(GCI.mock_calls[-1], call())
1319
            self.assertEqual(bu_cnt, self.client.container)
1320

    
1321
    def test_get_container_meta(self):
1322
        somedate = '50m3d473'
1323
        key = 'x-container-meta'
1324
        metaval = '50m3m374v41'
1325
        container_plus = dict(container_info)
1326
        container_plus[key] = metaval
1327
        for ret in ((container_info, {}), (container_plus, {key: metaval})):
1328
            with patch.object(
1329
                    pithos.PithosClient,
1330
                    'get_container_info',
1331
                    return_value=ret[0]) as GCI:
1332
                for until in (None, somedate):
1333
                    r = self.client.get_container_meta(until=until)
1334
                    self.assertEqual(r, ret[1])
1335
                    self.assertEqual(GCI.mock_calls[-1], call(until=until))
1336

    
1337
    def test_get_container_object_meta(self):
1338
        somedate = '50m3d473'
1339
        key = 'x-container-object-meta'
1340
        metaval = '50m3m374v41'
1341
        container_plus = dict(container_info)
1342
        container_plus[key] = metaval
1343
        for ret in (
1344
                (container_info, {key: ''}),
1345
                (container_plus, {key: metaval})):
1346
            with patch.object(
1347
                    pithos.PithosClient,
1348
                    'get_container_info',
1349
                    return_value=ret[0]) as GCI:
1350
                for until in (None, somedate):
1351
                    r = self.client.get_container_object_meta(until=until)
1352
                    self.assertEqual(r, ret[1])
1353
                    self.assertEqual(GCI.mock_calls[-1], call(until=until))
1354

    
1355
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
1356
    def test_set_container_meta(self, post):
1357
        metas = dict(k1='v1', k2='v2', k3='v3')
1358
        self.client.set_container_meta(metas)
1359
        post.assert_called_once_with(update=True, metadata=metas)
1360

    
1361
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
1362
    def test_del_container_meta(self, AP):
1363
        self.client.del_container_meta('somekey')
1364
        AP.assert_called_once_with(update=True, metadata={'somekey': ''})
1365

    
1366
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
1367
    def test_set_container_quota(self, post):
1368
        qu = 1024
1369
        self.client.set_container_quota(qu)
1370
        post.assert_called_once_with(update=True, quota=qu)
1371

    
1372
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
1373
    def test_set_container_versioning(self, post):
1374
        vrs = 'n3wV3r51on1ngTyp3'
1375
        self.client.set_container_versioning(vrs)
1376
        post.assert_called_once_with(update=True, versioning=vrs)
1377

    
1378
    @patch('%s.object_delete' % pithos_pkg, return_value=FR())
1379
    def test_del_object(self, delete):
1380
        for kwarg in (
1381
                dict(delimiter=None, until=None),
1382
                dict(delimiter='X', until='50m3d473')):
1383
            self.client.del_object(obj, **kwarg)
1384
            self.assertEqual(delete.mock_calls[-1], call(obj, **kwarg))
1385

    
1386
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
1387
    def test_set_object_meta(self, post):
1388
        metas = dict(k1='v1', k2='v2', k3='v3')
1389
        self.assertRaises(
1390
            AssertionError,
1391
            self.client.set_object_meta,
1392
            obj, 'Non dict arg')
1393
        self.client.set_object_meta(obj, metas)
1394
        post.assert_called_once_with(obj, update=True, metadata=metas)
1395

    
1396
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
1397
    def test_publish_object(self, post):
1398
        oinfo = dict(object_info)
1399
        val = 'pubL1c'
1400
        oinfo['x-object-public'] = val
1401
        with patch.object(
1402
                pithos.PithosClient, 'get_object_info',
1403
                return_value=oinfo) as GOF:
1404
            r = self.client.publish_object(obj)
1405
            self.assertEqual(
1406
                post.mock_calls[-1],
1407
                call(obj, public=True, update=True))
1408
            self.assertEqual(GOF.mock_calls[-1], call(obj))
1409
            self.assertEqual(r, '%s%s' % (self.url[:-6], val))
1410

    
1411
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
1412
    def test_unpublish_object(self, post):
1413
        self.client.unpublish_object(obj)
1414
        post.assert_called_once_with(obj, public=False, update=True)
1415

    
1416
    def test_get_object_sharing(self):
1417
        info = dict(object_info)
1418
        expected = dict(read='u1,g1,u2', write='u1')
1419
        info['x-object-sharing'] = '; '.join(
1420
            ['%s=%s' % (k, v) for k, v in expected.items()])
1421
        with patch.object(
1422
                pithos.PithosClient, 'get_object_info',
1423
                return_value=info) as GOF:
1424
            r = self.client.get_object_sharing(obj)
1425
            self.assertEqual(GOF.mock_calls[-1], call(obj))
1426
            self.assert_dicts_are_equal(r, expected)
1427
            info['x-object-sharing'] = '//'.join(
1428
                ['%s=%s' % (k, v) for k, v in expected.items()])
1429
            self.assertRaises(
1430
                ValueError,
1431
                self.client.get_object_sharing,
1432
                obj)
1433
            info['x-object-sharing'] = '; '.join(
1434
                ['%s:%s' % (k, v) for k, v in expected.items()])
1435
            self.assertRaises(
1436
                ClientError,
1437
                self.client.get_object_sharing,
1438
                obj)
1439
            info['x-object-sharing'] = 'read=%s' % expected['read']
1440
            r = self.client.get_object_sharing(obj)
1441
            expected.pop('write')
1442
            self.assert_dicts_are_equal(r, expected)
1443

    
1444
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
1445
    def test_set_object_sharing(self, OP):
1446
        read_perms = ['u1', 'g1', 'u2', 'g2']
1447
        write_perms = ['u1', 'g1']
1448
        for kwargs in (
1449
                dict(read_permition=read_perms, write_permition=write_perms),
1450
                dict(read_permition=read_perms),
1451
                dict(write_permition=write_perms),
1452
                dict()):
1453
            self.client.set_object_sharing(obj, **kwargs)
1454
            kwargs['read'] = kwargs.pop('read_permition', '')
1455
            kwargs['write'] = kwargs.pop('write_permition', '')
1456
            self.assertEqual(
1457
                OP.mock_calls[-1],
1458
                call(obj, update=True, permissions=kwargs))
1459

    
1460
    @patch('%s.set_object_sharing' % pithos_pkg)
1461
    def test_del_object_sharing(self, SOS):
1462
        self.client.del_object_sharing(obj)
1463
        SOS.assert_called_once_with(obj)
1464

    
1465
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1466
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
1467
    def test_append_object(self, post, GCI):
1468
        num_of_blocks = 4
1469
        tmpFile = self._create_temp_file(num_of_blocks)
1470
        tmpFile.seek(0, 2)
1471
        file_size = tmpFile.tell()
1472
        for turn in range(2):
1473
            tmpFile.seek(0, 0)
1474

    
1475
            try:
1476
                from progress.bar import ShadyBar
1477
                apn_bar = ShadyBar('Mock append')
1478
            except ImportError:
1479
                apn_bar = None
1480

    
1481
            if apn_bar:
1482

    
1483
                def append_gen(n):
1484
                    for i in apn_bar.iter(range(n)):
1485
                        yield
1486
                    yield
1487

    
1488
            else:
1489
                append_gen = None
1490

    
1491
            self.client.append_object(
1492
                obj, tmpFile,
1493
                upload_cb=append_gen if turn else None)
1494
            self.assertEqual((turn + 1) * num_of_blocks, len(post.mock_calls))
1495
            (args, kwargs) = post.mock_calls[-1][1:3]
1496
            self.assertEqual(args, (obj,))
1497
            self.assertEqual(kwargs['content_length'], len(kwargs['data']))
1498
            fsize = num_of_blocks * int(kwargs['content_length'])
1499
            self.assertEqual(fsize, file_size)
1500
            self.assertEqual(kwargs['content_range'], 'bytes */*')
1501
            exp = 'application/octet-stream'
1502
            self.assertEqual(kwargs['content_type'], exp)
1503
            self.assertEqual(kwargs['update'], True)
1504

    
1505
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
1506
    def test_truncate_object(self, post):
1507
        upto_bytes = 377
1508
        self.client.truncate_object(obj, upto_bytes)
1509
        post.assert_called_once_with(
1510
            obj,
1511
            update=True,
1512
            object_bytes=upto_bytes,
1513
            content_range='bytes 0-%s/*' % upto_bytes,
1514
            content_type='application/octet-stream',
1515
            source_object='/%s/%s' % (self.client.container, obj))
1516

    
1517
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1518
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
1519
    def test_overwrite_object(self, post, GCI):
1520
        num_of_blocks = 4
1521
        tmpFile = self._create_temp_file(num_of_blocks)
1522
        tmpFile.seek(0, 2)
1523
        file_size = tmpFile.tell()
1524
        info = dict(object_info)
1525
        info['content-length'] = file_size
1526
        block_size = container_info['x-container-block-size']
1527
        with patch.object(
1528
                pithos.PithosClient, 'get_object_info',
1529
                return_value=info) as GOI:
1530
            for start, end in (
1531
                    (0, file_size + 1),
1532
                    (file_size + 1, file_size + 2)):
1533
                tmpFile.seek(0, 0)
1534
                self.assertRaises(
1535
                    ClientError,
1536
                    self.client.overwrite_object,
1537
                    obj, start, end, tmpFile)
1538
            for start, end in ((0, 144), (144, 233), (233, file_size)):
1539
                tmpFile.seek(0, 0)
1540
                owr_gen = None
1541
                exp_size = end - start + 1
1542
                if not start or exp_size > block_size:
1543
                    try:
1544
                        from progress.bar import ShadyBar
1545
                        owr_bar = ShadyBar('Mock append')
1546
                    except ImportError:
1547
                        owr_bar = None
1548

    
1549
                    if owr_bar:
1550

    
1551
                        def owr_gen(n):
1552
                            for i in owr_bar.iter(range(n)):
1553
                                yield
1554
                            yield
1555

    
1556
                    if exp_size > block_size:
1557
                        exp_size = exp_size % block_size or block_size
1558

    
1559
                self.client.overwrite_object(obj, start, end, tmpFile, owr_gen)
1560
                self.assertEqual(GOI.mock_calls[-1], call(obj))
1561
                self.assertEqual(GCI.mock_calls[-1], call())
1562
                (args, kwargs) = post.mock_calls[-1][1:3]
1563
                self.assertEqual(args, (obj,))
1564
                self.assertEqual(len(kwargs['data']), exp_size)
1565
                self.assertEqual(kwargs['content_length'], exp_size)
1566
                self.assertEqual(kwargs['update'], True)
1567
                exp = 'application/octet-stream'
1568
                self.assertEqual(kwargs['content_type'], exp)
1569

    
1570
    @patch('%s.set_param' % pithos_pkg)
1571
    @patch('%s.get' % pithos_pkg, return_value=FR())
1572
    def test_get_sharing_accounts(self, get, SP):
1573
        FR.json = sharers
1574
        for kws in (
1575
                dict(),
1576
                dict(limit='50m3-11m17'),
1577
                dict(marker='X'),
1578
                dict(limit='50m3-11m17', marker='X')):
1579
            r = self.client.get_sharing_accounts(**kws)
1580
            self.assertEqual(get.mock_calls[-1], call('', success=(200, 204)))
1581
            self.assertEqual(SP.mock_calls[-3], call('format', 'json'))
1582
            limit, marker = kws.get('limit', None), kws.get('marker', None)
1583
            self.assertEqual(SP.mock_calls[-2], call(
1584
                'limit', limit,
1585
                iff=limit is not None))
1586
            self.assertEqual(SP.mock_calls[-1], call(
1587
                'marker', marker,
1588
                iff=marker is not None))
1589
            for i in range(len(r)):
1590
                self.assert_dicts_are_equal(r[i], sharers[i])
1591

    
1592
    @patch('%s.object_get' % pithos_pkg, return_value=FR())
1593
    def test_get_object_versionlist(self, get):
1594
        info = dict(object_info)
1595
        info['versions'] = ['v1', 'v2']
1596
        FR.json = info
1597
        r = self.client.get_object_versionlist(obj)
1598
        get.assert_called_once_with(obj, format='json', version='list')
1599
        self.assertEqual(r, info['versions'])
1600

    
1601
if __name__ == '__main__':
1602
    from sys import argv
1603
    from kamaki.clients.test import runTestCase
1604
    not_found = True
1605
    if not argv[1:] or argv[1] == 'PithosClient':
1606
        not_found = False
1607
        runTestCase(PithosClient, 'Pithos Client', argv[2:])
1608
    if not argv[1:] or argv[1] == 'PithosRestClient':
1609
        not_found = False
1610
        runTestCase(PithosRestClient, 'PithosRest Client', argv[2:])
1611
    if not_found:
1612
        print('TestCase %s not found' % argv[1])