Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / pithos / test.py @ 1792ed1d

History | View | Annotate | Download (64.8 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.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
            content_encoding=None,
839
            content_type='application/octet-stream',
840
            content_disposition=None,
841
            public=None,
842
            permissions=None)
843
        for k, v in expected1.items():
844
            if k == 'json':
845
                self.assertEqual(len(v['hashes']), len(kwargs1[k]['hashes']))
846
                self.assertEqual(v['bytes'], kwargs1[k]['bytes'])
847
            else:
848
                self.assertEqual(v, kwargs1[k])
849

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

    
867
        mock_offset = 2
868

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

    
878
        if blck_bar and upld_bar:
879

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

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

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

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

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

    
909
        # With other args
910
        tmpFile.seek(0)
911
        kwargs = dict(
912
            etag='s0m3E74g',
913
            if_etag_match='if etag match',
914
            if_not_exist=True,
915
            content_type=ctype,
916
            content_disposition=ctype + 'd15p051710n',
917
            public=True,
918
            content_encoding='802.11')
919
        self.client.upload_object(obj, tmpFile, **kwargs)
920
        kwargs.pop('if_not_exist')
921
        ematch = kwargs.pop('if_etag_match')
922
        etag = kwargs.pop('etag')
923
        for arg, val in kwargs.items():
924
            self.assertEqual(OP.mock_calls[-2][2][arg], val)
925
        self.assertEqual(OP.mock_calls[-1][2]['if_etag_match'], ematch)
926
        self.assertEqual(OP.mock_calls[-1][2]['if_etag_not_match'], '*')
927
        self.assertEqual(OP.mock_calls[-1][2]['etag'], etag)
928

    
929
    def test_get_object_info(self):
930
        FR.headers = object_info
931
        version = 'v3r510n'
932
        with patch.object(
933
                pithos.PithosClient, 'object_head',
934
                return_value=FR()) as head:
935
            r = self.client.get_object_info(obj)
936
            self.assertEqual(r, object_info)
937
            r = self.client.get_object_info(obj, version=version)
938
            self.assertEqual(head.mock_calls, [
939
                call(obj, version=None),
940
                call(obj, version=version)])
941
        with patch.object(
942
                pithos.PithosClient, 'object_head',
943
                side_effect=ClientError('Obj not found', 404)):
944
            self.assertRaises(
945
                ClientError,
946
                self.client.get_object_info,
947
                obj, version=version)
948

    
949
    @patch('%s.get_object_info' % pithos_pkg, return_value=object_info)
950
    def test_get_object_meta(self, GOI):
951
        for version in (None, 'v3r510n'):
952
            r = self.client.get_object_meta(obj, version)
953
            for k in [k for k in object_info if k.startswith('x-object-meta')]:
954
                self.assertEqual(r.pop(k), object_info[k])
955
            self.assertFalse(len(r))
956
            self.assertEqual(GOI.mock_calls[-1], call(obj, version=version))
957

    
958
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
959
    def test_del_object_meta(self, post):
960
        metakey = '50m3m3t4k3y'
961
        self.client.del_object_meta(obj, metakey)
962
        post.assert_called_once_with(obj, update=True, metadata={metakey: ''})
963

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

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

    
1024
    #  Pithos+ only methods
1025

    
1026
    @patch('%s.container_delete' % pithos_pkg, return_value=FR())
1027
    def test_purge_container(self, CD):
1028
        self.client.purge_container()
1029
        self.assertTrue('until' in CD.mock_calls[-1][2])
1030
        cont = self.client.container
1031
        self.client.purge_container('another-container')
1032
        self.assertEqual(self.client.container, cont)
1033

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

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

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

    
1107
        self.client.download_object(obj, tmpFile)
1108
        self.assertEqual(len(GET.mock_calls), num_of_blocks)
1109
        self.assertEqual(GET.mock_calls[-1][1], (obj,))
1110
        for k, v in kwargs.items():
1111
            if k == 'async_headers':
1112
                self.assertTrue('Range' in GET.mock_calls[-1][2][k])
1113
            elif k in ('resume', 'range_str'):
1114
                continue
1115
            else:
1116
                self.assertEqual(GET.mock_calls[-1][2][k], None)
1117

    
1118
        #  Check ranges are consecutive
1119
        starts = []
1120
        ends = []
1121
        for c in GET.mock_calls:
1122
            rng_str = c[2]['async_headers']['Range']
1123
            (start, rng_str) = rng_str.split('=')
1124
            (start, end) = rng_str.split('-')
1125
            starts.append(start)
1126
            ends.append(end)
1127
        ends = sorted(ends)
1128
        for i, start in enumerate(sorted(starts)):
1129
            if i:
1130
                int(ends[i - 1]) == int(start) - 1
1131

    
1132
        #  With progress bars
1133
        try:
1134
            from progress.bar import ShadyBar
1135
            dl_bar = ShadyBar('Mock dl')
1136
        except ImportError:
1137
            dl_bar = None
1138

    
1139
        if dl_bar:
1140

    
1141
            def blck_gen(n):
1142
                for i in dl_bar.iter(range(n)):
1143
                    yield
1144
                yield
1145

    
1146
            tmpFile.seek(0)
1147
            self.client.download_object(obj, tmpFile, download_cb=blck_gen)
1148
            self.assertEqual(len(GET.mock_calls), 2 * num_of_blocks)
1149

    
1150
        tmpFile.seek(0)
1151
        kwargs.pop('async_headers')
1152
        kwargs.pop('resume')
1153
        self.client.download_object(obj, tmpFile, **kwargs)
1154
        for k, v in kwargs.items():
1155
            if k == 'range_str':
1156
                self.assertEqual(
1157
                    GET.mock_calls[-1][2]['data_range'],
1158
                    'bytes=%s' % v)
1159
            else:
1160
                self.assertEqual(GET.mock_calls[-1][2][k], v)
1161

    
1162
        #  ALl options on no tty
1163
        def foo():
1164
            return True
1165

    
1166
        tmpFile.seek(0)
1167
        tmpFile.isatty = foo
1168
        self.client.download_object(obj, tmpFile, **kwargs)
1169
        for k, v in kwargs.items():
1170
            if k == 'range_str':
1171
                self.assertTrue('data_range' in GET.mock_calls[-1][2])
1172
            else:
1173
                self.assertEqual(GET.mock_calls[-1][2][k], v)
1174

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

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

    
1216
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
1217
    def test_del_account_group(self, post):
1218
        group = 'aU53rGr0up'
1219
        self.client.del_account_group(group)
1220
        post.assert_called_once_with(update=True, groups={group: []})
1221

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

    
1229
    @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
1230
    def test_get_account_versioning(self, GAI):
1231
        key = 'x-account-policy-versioning'
1232
        r = self.client.get_account_versioning()
1233
        GAI.assert_called_once_with()
1234
        self.assertEqual(r[key], account_info[key])
1235

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

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

    
1274
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
1275
    def test_set_account_meta(self, post):
1276
        metas = dict(k1='v1', k2='v2', k3='v3')
1277
        self.client.set_account_meta(metas)
1278
        post.assert_called_once_with(update=True, metadata=metas)
1279

    
1280
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
1281
    def test_set_account_quota(self, post):
1282
        qu = 1024
1283
        self.client.set_account_quota(qu)
1284
        post.assert_called_once_with(update=True, quota=qu)
1285

    
1286
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
1287
    def test_set_account_versioning(self, post):
1288
        vrs = 'n3wV3r51on1ngTyp3'
1289
        self.client.set_account_versioning(vrs)
1290
        post.assert_called_once_with(update=True, versioning=vrs)
1291

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

    
1305
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1306
    def test_get_container_versioning(self, GCI):
1307
        key = 'x-container-policy-versioning'
1308
        cont = 'c0n7-417'
1309
        bu_cnt = self.client.container
1310
        for container in (None, cont):
1311
            r = self.client.get_container_versioning(container=container)
1312
            self.assertEqual(r[key], container_info[key])
1313
            self.assertEqual(GCI.mock_calls[-1], call())
1314
            self.assertEqual(bu_cnt, self.client.container)
1315

    
1316
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1317
    def test_get_container_quota(self, GCI):
1318
        key = 'x-container-policy-quota'
1319
        cont = 'c0n7-417'
1320
        bu_cnt = self.client.container
1321
        for container in (None, cont):
1322
            r = self.client.get_container_quota(container=container)
1323
            self.assertEqual(r[key], container_info[key])
1324
            self.assertEqual(GCI.mock_calls[-1], call())
1325
            self.assertEqual(bu_cnt, self.client.container)
1326

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

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

    
1361
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
1362
    def test_set_container_meta(self, post):
1363
        metas = dict(k1='v1', k2='v2', k3='v3')
1364
        self.client.set_container_meta(metas)
1365
        post.assert_called_once_with(update=True, metadata=metas)
1366

    
1367
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
1368
    def test_del_container_meta(self, AP):
1369
        self.client.del_container_meta('somekey')
1370
        AP.assert_called_once_with(update=True, metadata={'somekey': ''})
1371

    
1372
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
1373
    def test_set_container_quota(self, post):
1374
        qu = 1024
1375
        self.client.set_container_quota(qu)
1376
        post.assert_called_once_with(update=True, quota=qu)
1377

    
1378
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
1379
    def test_set_container_versioning(self, post):
1380
        vrs = 'n3wV3r51on1ngTyp3'
1381
        self.client.set_container_versioning(vrs)
1382
        post.assert_called_once_with(update=True, versioning=vrs)
1383

    
1384
    @patch('%s.object_delete' % pithos_pkg, return_value=FR())
1385
    def test_del_object(self, delete):
1386
        for kwarg in (
1387
                dict(delimiter=None, until=None),
1388
                dict(delimiter='X', until='50m3d473')):
1389
            self.client.del_object(obj, **kwarg)
1390
            self.assertEqual(delete.mock_calls[-1], call(obj, **kwarg))
1391

    
1392
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
1393
    def test_set_object_meta(self, post):
1394
        metas = dict(k1='v1', k2='v2', k3='v3')
1395
        self.assertRaises(
1396
            AssertionError,
1397
            self.client.set_object_meta,
1398
            obj, 'Non dict arg')
1399
        self.client.set_object_meta(obj, metas)
1400
        post.assert_called_once_with(obj, update=True, metadata=metas)
1401

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

    
1417
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
1418
    def test_unpublish_object(self, post):
1419
        self.client.unpublish_object(obj)
1420
        post.assert_called_once_with(obj, public=False, update=True)
1421

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

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

    
1466
    @patch('%s.set_object_sharing' % pithos_pkg)
1467
    def test_del_object_sharing(self, SOS):
1468
        self.client.del_object_sharing(obj)
1469
        SOS.assert_called_once_with(obj)
1470

    
1471
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1472
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
1473
    def test_append_object(self, post, GCI):
1474
        num_of_blocks = 4
1475
        tmpFile = self._create_temp_file(num_of_blocks)
1476
        tmpFile.seek(0, 2)
1477
        file_size = tmpFile.tell()
1478
        for turn in range(2):
1479
            tmpFile.seek(0, 0)
1480

    
1481
            try:
1482
                from progress.bar import ShadyBar
1483
                apn_bar = ShadyBar('Mock append')
1484
            except ImportError:
1485
                apn_bar = None
1486

    
1487
            if apn_bar:
1488

    
1489
                def append_gen(n):
1490
                    for i in apn_bar.iter(range(n)):
1491
                        yield
1492
                    yield
1493

    
1494
            else:
1495
                append_gen = None
1496

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

    
1511
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
1512
    def test_truncate_object(self, post):
1513
        upto_bytes = 377
1514
        self.client.truncate_object(obj, upto_bytes)
1515
        post.assert_called_once_with(
1516
            obj,
1517
            update=True,
1518
            object_bytes=upto_bytes,
1519
            content_range='bytes 0-%s/*' % upto_bytes,
1520
            content_type='application/octet-stream',
1521
            source_object='/%s/%s' % (self.client.container, obj))
1522

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

    
1555
                    if owr_bar:
1556

    
1557
                        def owr_gen(n):
1558
                            for i in owr_bar.iter(range(n)):
1559
                                yield
1560
                            yield
1561

    
1562
                    if exp_size > block_size:
1563
                        exp_size = exp_size % block_size or block_size
1564

    
1565
                self.client.overwrite_object(obj, start, end, tmpFile, owr_gen)
1566
                self.assertEqual(GOI.mock_calls[-1], call(obj))
1567
                self.assertEqual(GCI.mock_calls[-1], call())
1568
                (args, kwargs) = post.mock_calls[-1][1:3]
1569
                self.assertEqual(args, (obj,))
1570
                self.assertEqual(len(kwargs['data']), exp_size)
1571
                self.assertEqual(kwargs['content_length'], exp_size)
1572
                self.assertEqual(kwargs['update'], True)
1573
                exp = 'application/octet-stream'
1574
                self.assertEqual(kwargs['content_type'], exp)
1575

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

    
1598
    @patch('%s.object_get' % pithos_pkg, return_value=FR())
1599
    def test_get_object_versionlist(self, get):
1600
        info = dict(object_info)
1601
        info['versions'] = ['v1', 'v2']
1602
        FR.json = info
1603
        r = self.client.get_object_versionlist(obj)
1604
        get.assert_called_once_with(obj, format='json', version='list')
1605
        self.assertEqual(r, info['versions'])
1606

    
1607
if __name__ == '__main__':
1608
    from sys import argv
1609
    from kamaki.clients.test import runTestCase
1610
    not_found = True
1611
    if not argv[1:] or argv[1] == 'PithosClient':
1612
        not_found = False
1613
        runTestCase(PithosClient, 'Pithos Client', argv[2:])
1614
    if not argv[1:] or argv[1] == 'PithosRestClient':
1615
        not_found = False
1616
        runTestCase(PithosRestClient, 'PithosRest Client', argv[2:])
1617
    if not_found:
1618
        print('TestCase %s not found' % argv[1])