Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / pithos / test.py @ 98a7195e

History | View | Annotate | Download (39 kB)

1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from unittest import TestCase
35
from mock import patch, call
36
from tempfile import NamedTemporaryFile
37
from os import urandom
38

    
39
from kamaki.clients import ClientError
40
from kamaki.clients.pithos import PithosClient as PC
41

    
42
pithos_pkg = 'kamaki.clients.pithos.PithosClient'
43

    
44
user_id = 'ac0un7-1d-5tr1ng'
45
obj = 'obj3c7N4m3'
46

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

    
139

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

    
148
    def release(self):
149
        pass
150

    
151

    
152
class Pithos(TestCase):
153

    
154
    files = []
155

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

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

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

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

    
190
    #  Pithos+ methods that extend storage API
191

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

    
202
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
203
    def test_del_account_meta(self, AP):
204
        keys = ['k1', 'k2', 'k3']
205
        for key in keys:
206
            self.client.del_account_meta(key)
207
            self.assertEqual(
208
                AP.mock_calls[-1],
209
                call(update=True, metadata={key: ''}))
210

    
211
    @patch('%s.container_head' % pithos_pkg, return_value=FR())
212
    def test_get_container_info(self, CH):
213
        FR.headers = container_info
214
        r = self.client.get_container_info()
215
        self.assert_dicts_are_equal(r, container_info)
216
        u = 'some date'
217
        r = self.client.get_container_info(until=u)
218
        self.assertEqual(CH.mock_calls, [call(until=None), call(until=u)])
219

    
220
    @patch('%s.account_get' % pithos_pkg, return_value=FR())
221
    def test_list_containers(self, get):
222
        FR.json = container_list
223
        r = self.client.list_containers()
224
        get.assert_called_once_with()
225
        for i in range(len(r)):
226
            self.assert_dicts_are_equal(r[i], container_list[i])
227

    
228
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
229
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
230
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
231
    def test_upload_object(self, OP, CP, GCI):
232
        num_of_blocks = 8
233
        tmpFile = self._create_temp_file(num_of_blocks)
234

    
235
        # Without kwargs
236
        self.client.upload_object(obj, tmpFile)
237
        self.assertEqual(GCI.mock_calls[-1], call())
238
        [call1, call2] = OP.mock_calls
239

    
240
        (args1, kwargs1) = call1[1:3]
241
        (args2, kwargs2) = call2[1:3]
242
        self.assertEqual(args1, (obj,))
243
        expected1 = dict(
244
            hashmap=True,
245
            success=(201, 409),
246
            format='json',
247
            json=dict(
248
                hashes=['s0m3h@5h'] * num_of_blocks,
249
                bytes=num_of_blocks * 4 * 1024 * 1024),
250
            etag=None,
251
            content_encoding=None,
252
            content_type='application/octet-stream',
253
            content_disposition=None,
254
            public=None,
255
            permissions=None)
256
        for k, v in expected1.items():
257
            if k == 'json':
258
                self.assertEqual(len(v['hashes']), len(kwargs1[k]['hashes']))
259
                self.assertEqual(v['bytes'], kwargs1[k]['bytes'])
260
            else:
261
                self.assertEqual(v, kwargs1[k])
262

    
263
        (args2, kwargs2) = call2[1:3]
264
        self.assertEqual(args2, (obj,))
265
        expected2 = dict(
266
            json=dict(
267
                hashes=['s0m3h@5h'] * num_of_blocks,
268
                bytes=num_of_blocks * 4 * 1024 * 1024),
269
            content_type='application/octet-stream',
270
            hashmap=True,
271
            success=201,
272
            format='json')
273
        for k, v in expected2.items():
274
            if k == 'json':
275
                self.assertEqual(len(v['hashes']), len(kwargs2[k]['hashes']))
276
                self.assertEqual(v['bytes'], kwargs2[k]['bytes'])
277
            else:
278
                self.assertEqual(v, kwargs2[k])
279

    
280
        mock_offset = 2
281

    
282
        #  With progress bars
283
        try:
284
            from progress.bar import ShadyBar
285
            blck_bar = ShadyBar('Mock blck calc.')
286
            upld_bar = ShadyBar('Mock uplds')
287
        except ImportError:
288
            blck_bar = None
289
            upld_bar = None
290

    
291
        if blck_bar and upld_bar:
292

    
293
            def blck_gen(n):
294
                for i in blck_bar.iter(range(n)):
295
                    yield
296
                yield
297

    
298
            def upld_gen(n):
299
                for i in upld_bar.iter(range(n)):
300
                    yield
301
                yield
302

    
303
            tmpFile.seek(0)
304
            self.client.upload_object(
305
                obj, tmpFile,
306
                hash_cb=blck_gen, upload_cb=upld_gen)
307

    
308
            for i, c in enumerate(OP.mock_calls[-mock_offset:]):
309
                self.assertEqual(OP.mock_calls[i], c)
310

    
311
        #  With content-type
312
        tmpFile.seek(0)
313
        ctype = 'video/mpeg'
314
        sharing = dict(read=['u1', 'g1', 'u2'], write=['u1'])
315
        self.client.upload_object(obj, tmpFile,
316
            content_type=ctype, sharing=sharing)
317
        self.assertEqual(OP.mock_calls[-1][2]['content_type'], ctype)
318
        self.assert_dicts_are_equal(
319
            OP.mock_calls[-2][2]['permissions'],
320
            sharing)
321

    
322
        # With other args
323
        tmpFile.seek(0)
324
        kwargs = dict(
325
            etag='s0m3E74g',
326
            content_type=ctype,
327
            content_disposition=ctype + 'd15p051710n',
328
            public=True,
329
            content_encoding='802.11')
330
        self.client.upload_object(obj, tmpFile, **kwargs)
331
        for arg, val in kwargs.items():
332
            self.assertEqual(OP.mock_calls[-2][2][arg], val)
333

    
334
    def test_get_object_info(self):
335
        FR.headers = object_info
336
        version = 'v3r510n'
337
        with patch.object(PC, 'object_head', return_value=FR()) as head:
338
            r = self.client.get_object_info(obj)
339
            self.assertEqual(r, object_info)
340
            r = self.client.get_object_info(obj, version=version)
341
            self.assertEqual(head.mock_calls, [
342
                call(obj, version=None),
343
                call(obj, version=version)])
344
        with patch.object(
345
                PC, 'object_head',
346
                side_effect=ClientError('Obj not found', 404)):
347
            self.assertRaises(
348
                ClientError,
349
                self.client.get_object_info,
350
                obj, version=version)
351

    
352
    @patch('%s.get_object_info' % pithos_pkg, return_value=object_info)
353
    def test_get_object_meta(self, GOI):
354
        for version in (None, 'v3r510n'):
355
            r = self.client.get_object_meta(obj, version)
356
            for k in [k for k in object_info if k.startswith('x-object-meta')]:
357
                self.assertEqual(r.pop(k), object_info[k])
358
            self.assertFalse(len(r))
359
            self.assertEqual(GOI.mock_calls[-1], call(obj, version=version))
360

    
361
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
362
    def test_del_object_meta(self, post):
363
        metakey = '50m3m3t4k3y'
364
        self.client.del_object_meta(obj, metakey)
365
        post.assert_called_once_with(obj, update=True, metadata={metakey: ''})
366

    
367
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
368
    def test_copy_object(self, put):
369
        src_cont = 'src-c0nt41n3r'
370
        src_obj = 'src-0bj'
371
        dst_cont = 'dst-c0nt41n3r'
372
        dst_obj = 'dst-0bj'
373
        expected = call(
374
            src_obj,
375
            content_length=0,
376
            source_account=None,
377
            success=201,
378
            copy_from='/%s/%s' % (src_cont, src_obj),
379
            delimiter=None,
380
            content_type=None,
381
            source_version=None,
382
            public=False)
383
        self.client.copy_object(src_cont, src_obj, dst_cont)
384
        self.assertEqual(put.mock_calls[-1], expected)
385
        self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
386
        self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
387
        kwargs = dict(
388
            source_version='src-v3r510n',
389
            source_account='src-4cc0un7',
390
            public=True,
391
            content_type='c0n73n7Typ3',
392
            delimiter='5')
393
        self.client.copy_object(src_cont, src_obj, dst_cont, **kwargs)
394
        for k, v in kwargs.items():
395
            self.assertEqual(v, put.mock_calls[-1][2][k])
396

    
397
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
398
    def test_move_object(self, put):
399
        src_cont = 'src-c0nt41n3r'
400
        src_obj = 'src-0bj'
401
        dst_cont = 'dst-c0nt41n3r'
402
        dst_obj = 'dst-0bj'
403
        expected = call(
404
            src_obj,
405
            content_length=0,
406
            source_account=None,
407
            success=201,
408
            move_from='/%s/%s' % (src_cont, src_obj),
409
            delimiter=None,
410
            content_type=None,
411
            source_version=None,
412
            public=False)
413
        self.client.move_object(src_cont, src_obj, dst_cont)
414
        self.assertEqual(put.mock_calls[-1], expected)
415
        self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
416
        self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
417
        kwargs = dict(
418
            source_version='src-v3r510n',
419
            source_account='src-4cc0un7',
420
            public=True,
421
            content_type='c0n73n7Typ3',
422
            delimiter='5')
423
        self.client.move_object(src_cont, src_obj, dst_cont, **kwargs)
424
        for k, v in kwargs.items():
425
            self.assertEqual(v, put.mock_calls[-1][2][k])
426

    
427
    #  Pithos+ only methods
428

    
429
    @patch('%s.container_delete' % pithos_pkg, return_value=FR())
430
    def test_purge_container(self, CD):
431
        self.client.purge_container()
432
        self.assertTrue('until' in CD.mock_calls[-1][2])
433
        cont = self.client.container
434
        self.client.purge_container('another-container')
435
        self.assertEqual(self.client.container, cont)
436

    
437
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
438
    def test_upload_object_unchunked(self, put):
439
        num_of_blocks = 8
440
        tmpFile = self._create_temp_file(num_of_blocks)
441
        expected = dict(
442
                success=201,
443
                data=num_of_blocks * 4 * 1024 * 1024,
444
                etag='some-etag',
445
                content_encoding='some content_encoding',
446
                content_type='some content-type',
447
                content_disposition='some content_disposition',
448
                public=True,
449
                permissions=dict(read=['u1', 'g1', 'u2'], write=['u1']))
450
        self.client.upload_object_unchunked(obj, tmpFile)
451
        self.assertEqual(put.mock_calls[-1][1], (obj,))
452
        self.assertEqual(
453
            sorted(put.mock_calls[-1][2].keys()),
454
            sorted(expected.keys()))
455
        kwargs = dict(expected)
456
        kwargs.pop('success')
457
        kwargs['size'] = kwargs.pop('data')
458
        kwargs['sharing'] = kwargs.pop('permissions')
459
        tmpFile.seek(0)
460
        self.client.upload_object_unchunked(obj, tmpFile, **kwargs)
461
        pmc = put.mock_calls[-1][2]
462
        for k, v in expected.items():
463
            if k == 'data':
464
                self.assertEqual(len(pmc[k]), v)
465
            else:
466
                self.assertEqual(pmc[k], v)
467
        self.assertRaises(
468
            ClientError,
469
            self.client.upload_object_unchunked,
470
            obj, tmpFile, withHashFile=True)
471

    
472
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
473
    def test_create_object_by_manifestation(self, put):
474
        manifest = '%s/%s' % (self.client.container, obj)
475
        kwargs = dict(
476
                etag='some-etag',
477
                content_encoding='some content_encoding',
478
                content_type='some content-type',
479
                content_disposition='some content_disposition',
480
                public=True,
481
                sharing=dict(read=['u1', 'g1', 'u2'], write=['u1']))
482
        self.client.create_object_by_manifestation(obj)
483
        expected = dict(content_length=0, manifest=manifest)
484
        for k in kwargs:
485
            expected['permissions' if k == 'sharing' else k] = None
486
        self.assertEqual(put.mock_calls[-1], call(obj, **expected))
487
        self.client.create_object_by_manifestation(obj, **kwargs)
488
        expected.update(kwargs)
489
        expected['permissions'] = expected.pop('sharing')
490
        self.assertEqual(put.mock_calls[-1], call(obj, **expected))
491

    
492
    @patch('%s.get_object_hashmap' % pithos_pkg, return_value=object_hashmap)
493
    @patch('%s.object_get' % pithos_pkg, return_value=FR())
494
    def test_download_object(self, GET, GOH):
495
        num_of_blocks = 8
496
        tmpFile = self._create_temp_file(num_of_blocks)
497
        FR.content = tmpFile.read(4 * 1024 * 1024)
498
        tmpFile = self._create_temp_file(num_of_blocks)
499
        num_of_blocks = len(object_hashmap['hashes'])
500
        kwargs = dict(
501
            resume=True,
502
            version='version',
503
            range_str='10-20',
504
            if_match='if and only if',
505
            if_none_match='if and only not',
506
            if_modified_since='what if not?',
507
            if_unmodified_since='this happens if not!',
508
            async_headers=dict(Range='bytes=0-88888888'))
509

    
510
        self.client.download_object(obj, tmpFile)
511
        self.assertEqual(len(GET.mock_calls), num_of_blocks)
512
        self.assertEqual(GET.mock_calls[-1][1], (obj,))
513
        for k, v in kwargs.items():
514
            if k == 'async_headers':
515
                self.assertTrue('Range' in GET.mock_calls[-1][2][k])
516
            elif k in ('resume', 'range_str'):
517
                continue
518
            else:
519
                self.assertEqual(GET.mock_calls[-1][2][k], None)
520

    
521
        #  Check ranges are consecutive
522
        starts = []
523
        ends = []
524
        for c in GET.mock_calls:
525
            rng_str = c[2]['async_headers']['Range']
526
            (start, rng_str) = rng_str.split('=')
527
            (start, end) = rng_str.split('-')
528
            starts.append(start)
529
            ends.append(end)
530
        ends = sorted(ends)
531
        for i, start in enumerate(sorted(starts)):
532
            if i:
533
                int(ends[i - 1]) == int(start) - 1
534

    
535
        #  With progress bars
536
        try:
537
            from progress.bar import ShadyBar
538
            dl_bar = ShadyBar('Mock dl')
539
        except ImportError:
540
            dl_bar = None
541

    
542
        if dl_bar:
543

    
544
            def blck_gen(n):
545
                for i in dl_bar.iter(range(n)):
546
                    yield
547
                yield
548

    
549
            tmpFile.seek(0)
550
            self.client.download_object(obj, tmpFile, download_cb=blck_gen)
551
            self.assertEqual(len(GET.mock_calls), 2 * num_of_blocks)
552

    
553
        tmpFile.seek(0)
554
        kwargs.pop('async_headers')
555
        kwargs.pop('resume')
556
        self.client.download_object(obj, tmpFile, **kwargs)
557
        for k, v in kwargs.items():
558
            if k == 'range_str':
559
                self.assertEqual(
560
                    GET.mock_calls[-1][2]['data_range'],
561
                    'bytes=%s' % v)
562
            else:
563
                self.assertEqual(GET.mock_calls[-1][2][k], v)
564

    
565
        #  ALl options on no tty
566

    
567
        def foo():
568
            return True
569

    
570
        tmpFile.seek(0)
571
        tmpFile.isatty = foo
572
        self.client.download_object(obj, tmpFile, **kwargs)
573
        for k, v in kwargs.items():
574
            if k == 'range_str':
575
                self.assertTrue('data_range' in GET.mock_calls[-1][2])
576
            else:
577
                self.assertEqual(GET.mock_calls[-1][2][k], v)
578

    
579
    def test_get_object_hashmap(self):
580
        FR.json = object_hashmap
581
        for empty in (304, 412):
582
            with patch.object(
583
                    PC, 'object_get',
584
                    side_effect=ClientError('Empty', status=empty)):
585
                r = self.client.get_object_hashmap(obj)
586
                self.assertEqual(r, {})
587
        exp_args = dict(
588
            hashmap=True,
589
            data_range=None,
590
            version=None,
591
            if_etag_match=None,
592
            if_etag_not_match=None,
593
            if_modified_since=None,
594
            if_unmodified_since=None)
595
        kwargs = dict(
596
            version='s0m3v3r51on',
597
            if_match='if match',
598
            if_none_match='if non match',
599
            if_modified_since='some date here',
600
            if_unmodified_since='some date here',
601
            data_range='10-20')
602
        with patch.object(PC, 'object_get', return_value=FR()) as get:
603
            r = self.client.get_object_hashmap(obj)
604
            self.assertEqual(r, object_hashmap)
605
            self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
606
            r = self.client.get_object_hashmap(obj, **kwargs)
607
            exp_args['if_etag_match'] = kwargs.pop('if_match')
608
            exp_args['if_etag_not_match'] = kwargs.pop('if_none_match')
609
            exp_args.update(kwargs)
610
            self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
611

    
612
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
613
    def test_set_account_group(self, post):
614
        (group, usernames) = ('aU53rGr0up', ['u1', 'u2', 'u3'])
615
        self.client.set_account_group(group, usernames)
616
        post.assert_called_once_with(update=True, groups={group: usernames})
617

    
618
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
619
    def test_del_account_group(self, post):
620
        group = 'aU53rGr0up'
621
        self.client.del_account_group(group)
622
        post.assert_called_once_with(update=True, groups={group: []})
623

    
624
    @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
625
    def test_get_account_quota(self, GAI):
626
        key = 'x-account-policy-quota'
627
        r = self.client.get_account_quota()
628
        GAI.assert_called_once_with()
629
        self.assertEqual(r[key], account_info[key])
630

    
631
    @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
632
    def test_get_account_versioning(self, GAI):
633
        key = 'x-account-policy-versioning'
634
        r = self.client.get_account_versioning()
635
        GAI.assert_called_once_with()
636
        self.assertEqual(r[key], account_info[key])
637

    
638
    def test_get_account_meta(self):
639
        key = 'x-account-meta-'
640
        with patch.object(PC, 'get_account_info', return_value=account_info):
641
            r = self.client.get_account_meta()
642
            keys = [k for k in r if k.startswith(key)]
643
            self.assertFalse(keys)
644
        acc_info = dict(account_info)
645
        acc_info['%sk1' % key] = 'v1'
646
        acc_info['%sk2' % key] = 'v2'
647
        acc_info['%sk3' % key] = 'v3'
648
        with patch.object(PC, 'get_account_info', return_value=acc_info):
649
            r = self.client.get_account_meta()
650
            for k in [k for k in acc_info if k.startswith(key)]:
651
                self.assertEqual(r[k], acc_info[k])
652

    
653
    def test_get_account_group(self):
654
        key = 'x-account-group-'
655
        with patch.object(PC, 'get_account_info', return_value=account_info):
656
            r = self.client.get_account_group()
657
            keys = [k for k in r if k.startswith(key)]
658
            self.assertFalse(keys)
659
        acc_info = dict(account_info)
660
        acc_info['%sk1' % key] = 'g1'
661
        acc_info['%sk2' % key] = 'g2'
662
        acc_info['%sk3' % key] = 'g3'
663
        with patch.object(PC, 'get_account_info', return_value=acc_info):
664
            r = self.client.get_account_group()
665
            for k in [k for k in acc_info if k.startswith(key)]:
666
                self.assertEqual(r[k], acc_info[k])
667

    
668
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
669
    def test_set_account_meta(self, post):
670
        metas = dict(k1='v1', k2='v2', k3='v3')
671
        self.client.set_account_meta(metas)
672
        post.assert_called_once_with(update=True, metadata=metas)
673

    
674
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
675
    def test_set_account_quota(self, post):
676
        qu = 1024
677
        self.client.set_account_quota(qu)
678
        post.assert_called_once_with(update=True, quota=qu)
679

    
680
    @patch('%s.account_post' % pithos_pkg, return_value=FR())
681
    def test_set_account_versioning(self, post):
682
        vrs = 'n3wV3r51on1ngTyp3'
683
        self.client.set_account_versioning(vrs)
684
        post.assert_called_once_with(update=True, versioning=vrs)
685

    
686
    @patch('%s.container_delete' % pithos_pkg, return_value=FR())
687
    def test_del_container(self, delete):
688
        for kwarg in (
689
                dict(delimiter=None, until=None),
690
                dict(delimiter='X', until='50m3d473')):
691
            self.client.del_container(**kwarg)
692
            expected = dict(kwarg)
693
            expected['success'] = (204, 404, 409)
694
            self.assertEqual(delete.mock_calls[-1], call(**expected))
695
        for status_code in (404, 409):
696
            FR.status_code = status_code
697
            self.assertRaises(ClientError, self.client.del_container)
698

    
699
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
700
    def test_get_container_versioning(self, GCI):
701
        key = 'x-container-policy-versioning'
702
        cont = 'c0n7-417'
703
        bu_cnt = self.client.container
704
        for container in (None, cont):
705
            r = self.client.get_container_versioning(container=container)
706
            self.assertEqual(r[key], container_info[key])
707
            self.assertEqual(GCI.mock_calls[-1], call())
708
            self.assertEqual(bu_cnt, self.client.container)
709

    
710
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
711
    def test_get_container_quota(self, GCI):
712
        key = 'x-container-policy-quota'
713
        cont = 'c0n7-417'
714
        bu_cnt = self.client.container
715
        for container in (None, cont):
716
            r = self.client.get_container_quota(container=container)
717
            self.assertEqual(r[key], container_info[key])
718
            self.assertEqual(GCI.mock_calls[-1], call())
719
            self.assertEqual(bu_cnt, self.client.container)
720

    
721
    def test_get_container_meta(self):
722
        somedate = '50m3d473'
723
        key = 'x-container-meta'
724
        metaval = '50m3m374v41'
725
        container_plus = dict(container_info)
726
        container_plus[key] = metaval
727
        for ret in ((container_info, {}), (container_plus, {key: metaval})):
728
            with patch.object(
729
                    PC,
730
                    'get_container_info',
731
                    return_value=ret[0]) as GCI:
732
                for until in (None, somedate):
733
                    r = self.client.get_container_meta(until=until)
734
                    self.assertEqual(r, ret[1])
735
                    self.assertEqual(GCI.mock_calls[-1], call(until=until))
736

    
737
    def test_get_container_object_meta(self):
738
        somedate = '50m3d473'
739
        key = 'x-container-object-meta'
740
        metaval = '50m3m374v41'
741
        container_plus = dict(container_info)
742
        container_plus[key] = metaval
743
        for ret in (
744
                (container_info, {key: ''}),
745
                (container_plus, {key: metaval})):
746
            with patch.object(
747
                    PC,
748
                    'get_container_info',
749
                    return_value=ret[0]) as GCI:
750
                for until in (None, somedate):
751
                    r = self.client.get_container_object_meta(until=until)
752
                    self.assertEqual(r, ret[1])
753
                    self.assertEqual(GCI.mock_calls[-1], call(until=until))
754

    
755
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
756
    def test_set_container_meta(self, post):
757
        metas = dict(k1='v1', k2='v2', k3='v3')
758
        self.client.set_container_meta(metas)
759
        post.assert_called_once_with(update=True, metadata=metas)
760

    
761
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
762
    def test_del_container_meta(self, AP):
763
        self.client.del_container_meta('somekey')
764
        AP.assert_called_once_with(update=True, metadata={'somekey': ''})
765

    
766
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
767
    def test_set_container_quota(self, post):
768
        qu = 1024
769
        self.client.set_container_quota(qu)
770
        post.assert_called_once_with(update=True, quota=qu)
771

    
772
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
773
    def test_set_container_versioning(self, post):
774
        vrs = 'n3wV3r51on1ngTyp3'
775
        self.client.set_container_versioning(vrs)
776
        post.assert_called_once_with(update=True, versioning=vrs)
777

    
778
    @patch('%s.object_delete' % pithos_pkg, return_value=FR())
779
    def test_del_object(self, delete):
780
        for kwarg in (
781
                dict(delimiter=None, until=None),
782
                dict(delimiter='X', until='50m3d473')):
783
            self.client.del_object(obj, **kwarg)
784
            self.assertEqual(delete.mock_calls[-1], call(obj, **kwarg))
785

    
786
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
787
    def test_set_object_meta(self, post):
788
        metas = dict(k1='v1', k2='v2', k3='v3')
789
        self.assertRaises(
790
            AssertionError,
791
            self.client.set_object_meta,
792
            obj, 'Non dict arg')
793
        self.client.set_object_meta(obj, metas)
794
        post.assert_called_once_with(obj, update=True, metadata=metas)
795

    
796
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
797
    def test_publish_object(self, post):
798
        oinfo = dict(object_info)
799
        val = 'pubL1c'
800
        oinfo['x-object-public'] = val
801
        with patch.object(PC, 'get_object_info', return_value=oinfo) as GOF:
802
            r = self.client.publish_object(obj)
803
            self.assertEqual(
804
                post.mock_calls[-1],
805
                call(obj, public=True, update=True))
806
            self.assertEqual(GOF.mock_calls[-1], call(obj))
807
            self.assertEqual(r, '%s%s' % (self.url[:-6], val))
808

    
809
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
810
    def test_unpublish_object(self, post):
811
        self.client.unpublish_object(obj)
812
        post.assert_called_once_with(obj, public=False, update=True)
813

    
814
    def test_get_object_sharing(self):
815
        info = dict(object_info)
816
        expected = dict(read='u1,g1,u2', write='u1')
817
        info['x-object-sharing'] = '; '.join(
818
            ['%s=%s' % (k, v) for k, v in expected.items()])
819
        with patch.object(PC, 'get_object_info', return_value=info) as GOF:
820
            r = self.client.get_object_sharing(obj)
821
            self.assertEqual(GOF.mock_calls[-1], call(obj))
822
            self.assert_dicts_are_equal(r, expected)
823
            info['x-object-sharing'] = '//'.join(
824
                ['%s=%s' % (k, v) for k, v in expected.items()])
825
            self.assertRaises(
826
                ValueError,
827
                self.client.get_object_sharing,
828
                obj)
829
            info['x-object-sharing'] = '; '.join(
830
                ['%s:%s' % (k, v) for k, v in expected.items()])
831
            self.assertRaises(
832
                ClientError,
833
                self.client.get_object_sharing,
834
                obj)
835
            info['x-object-sharing'] = 'read=%s' % expected['read']
836
            r = self.client.get_object_sharing(obj)
837
            expected.pop('write')
838
            self.assert_dicts_are_equal(r, expected)
839

    
840
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
841
    def test_set_object_sharing(self, OP):
842
        read_perms = ['u1', 'g1', 'u2', 'g2']
843
        write_perms = ['u1', 'g1']
844
        for kwargs in (
845
                dict(read_permition=read_perms, write_permition=write_perms),
846
                dict(read_permition=read_perms),
847
                dict(write_permition=write_perms),
848
                dict()):
849
            self.client.set_object_sharing(obj, **kwargs)
850
            kwargs['read'] = kwargs.pop('read_permition', '')
851
            kwargs['write'] = kwargs.pop('write_permition', '')
852
            self.assertEqual(
853
                OP.mock_calls[-1],
854
                call(obj, update=True, permissions=kwargs))
855

    
856
    @patch('%s.set_object_sharing' % pithos_pkg)
857
    def test_del_object_sharing(self, SOS):
858
        self.client.del_object_sharing(obj)
859
        SOS.assert_called_once_with(obj)
860

    
861
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
862
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
863
    def test_append_object(self, post, GCI):
864
        num_of_blocks = 4
865
        tmpFile = self._create_temp_file(num_of_blocks)
866
        tmpFile.seek(0, 2)
867
        file_size = tmpFile.tell()
868
        for turn in range(2):
869
            tmpFile.seek(0, 0)
870

    
871
            try:
872
                from progress.bar import ShadyBar
873
                apn_bar = ShadyBar('Mock append')
874
            except ImportError:
875
                apn_bar = None
876

    
877
            if apn_bar:
878

    
879
                def append_gen(n):
880
                    for i in apn_bar.iter(range(n)):
881
                        yield
882
                    yield
883

    
884
            else:
885
                append_gen = None
886

    
887
            self.client.append_object(
888
                obj, tmpFile,
889
                upload_cb=append_gen if turn else None)
890
            self.assertEqual((turn + 1) * num_of_blocks, len(post.mock_calls))
891
            (args, kwargs) = post.mock_calls[-1][1:3]
892
            self.assertEqual(args, (obj,))
893
            self.assertEqual(kwargs['content_length'], len(kwargs['data']))
894
            fsize = num_of_blocks * int(kwargs['content_length'])
895
            self.assertEqual(fsize, file_size)
896
            self.assertEqual(kwargs['content_range'], 'bytes */*')
897
            exp = 'application/octet-stream'
898
            self.assertEqual(kwargs['content_type'], exp)
899
            self.assertEqual(kwargs['update'], True)
900

    
901
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
902
    def test_truncate_object(self, post):
903
        upto_bytes = 377
904
        self.client.truncate_object(obj, upto_bytes)
905
        post.assert_called_once_with(
906
            obj,
907
            update=True,
908
            object_bytes=upto_bytes,
909
            content_range='bytes 0-%s/*' % upto_bytes,
910
            content_type='application/octet-stream',
911
            source_object='/%s/%s' % (self.client.container, obj))
912

    
913
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
914
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
915
    def test_overwrite_object(self, post, GCI):
916
        num_of_blocks = 4
917
        tmpFile = self._create_temp_file(num_of_blocks)
918
        tmpFile.seek(0, 2)
919
        file_size = tmpFile.tell()
920
        info = dict(object_info)
921
        info['content-length'] = file_size
922
        block_size = container_info['x-container-block-size']
923
        with patch.object(PC, 'get_object_info', return_value=info) as GOI:
924
            for start, end in (
925
                    (0, file_size + 1),
926
                    (file_size + 1, file_size + 2)):
927
                tmpFile.seek(0, 0)
928
                self.assertRaises(
929
                    ClientError,
930
                    self.client.overwrite_object,
931
                    obj, start, end, tmpFile)
932
            for start, end in ((0, 144), (144, 233), (233, file_size)):
933
                tmpFile.seek(0, 0)
934
                owr_gen = None
935
                exp_size = end - start + 1
936
                if not start or exp_size > block_size:
937
                    try:
938
                        from progress.bar import ShadyBar
939
                        owr_bar = ShadyBar('Mock append')
940
                    except ImportError:
941
                        owr_bar = None
942

    
943
                    if owr_bar:
944

    
945
                        def owr_gen(n):
946
                            for i in owr_bar.iter(range(n)):
947
                                yield
948
                            yield
949

    
950
                    if exp_size > block_size:
951
                        exp_size = exp_size % block_size or block_size
952

    
953
                self.client.overwrite_object(obj, start, end, tmpFile, owr_gen)
954
                self.assertEqual(GOI.mock_calls[-1], call(obj))
955
                self.assertEqual(GCI.mock_calls[-1], call())
956
                (args, kwargs) = post.mock_calls[-1][1:3]
957
                self.assertEqual(args, (obj,))
958
                self.assertEqual(len(kwargs['data']), exp_size)
959
                self.assertEqual(kwargs['content_length'], exp_size)
960
                self.assertEqual(kwargs['update'], True)
961
                exp = 'application/octet-stream'
962
                self.assertEqual(kwargs['content_type'], exp)
963

    
964
    @patch('%s.set_param' % pithos_pkg)
965
    @patch('%s.get' % pithos_pkg, return_value=FR())
966
    def test_get_sharing_accounts(self, get, SP):
967
        FR.json = sharers
968
        for kws in (
969
                dict(),
970
                dict(limit='50m3-11m17'),
971
                dict(marker='X'),
972
                dict(limit='50m3-11m17', marker='X')):
973
            r = self.client.get_sharing_accounts(**kws)
974
            self.assertEqual(get.mock_calls[-1], call('', success=(200, 204)))
975
            self.assertEqual(SP.mock_calls[-3], call('format', 'json'))
976
            limit, marker = kws.get('limit', None), kws.get('marker', None)
977
            self.assertEqual(SP.mock_calls[-2], call(
978
                'limit', limit,
979
                iff=limit is not None))
980
            self.assertEqual(SP.mock_calls[-1], call(
981
                'marker', marker,
982
                iff=marker is not None))
983
            for i in range(len(r)):
984
                self.assert_dicts_are_equal(r[i], sharers[i])
985

    
986
    @patch('%s.object_get' % pithos_pkg, return_value=FR())
987
    def test_get_object_versionlist(self, get):
988
        info = dict(object_info)
989
        info['versions'] = ['v1', 'v2']
990
        FR.json = info
991
        r = self.client.get_object_versionlist(obj)
992
        get.assert_called_once_with(obj, format='json', version='list')
993
        self.assertEqual(r, info['versions'])
994

    
995
if __name__ == '__main__':
996
    from sys import argv
997
    from kamaki.clients.test import runTestCase
998
    runTestCase(Pithos, 'Pithos+ Client', argv[1:])