Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (37.7 kB)

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

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

    
39
from kamaki.clients import ClientError
40
from kamaki.clients.pithos import PithosClient as PC
41
from kamaki.clients.connection.kamakicon import KamakiHTTPConnection as C
42

    
43
client_pkg = 'kamaki.clients.Client'
44
pithos_pkg = 'kamaki.clients.pithos.PithosClient'
45

    
46
user_id = 'ac0un7-1d-5tr1ng'
47
obj = 'obj3c7N4m3'
48

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

    
137

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

    
146
    def release(self):
147
        pass
148

    
149

    
150
class Pithos(TestCase):
151

    
152
    files = []
153

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

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

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

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

    
188
    #  Pithos+ methods that extend storage API
189

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

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

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

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

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

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

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

    
269
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
270
    @patch('%s.container_post' % pithos_pkg, return_value=FR())
271
    @patch('%s.object_put' % pithos_pkg, return_value=FR())
272
    def test_upload_object(self, CI, CP, OP):
273
        num_of_blocks = 8
274
        tmpFile = self._create_temp_file(num_of_blocks)
275

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

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

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

    
321
        OP = PC.object_put
322
        mock_offset = 2
323

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

    
333
        if blck_bar and upld_bar:
334

    
335
            def blck_gen(n):
336
                for i in blck_bar.iter(range(n)):
337
                    yield
338
                yield
339

    
340
            def upld_gen(n):
341
                for i in upld_bar.iter(range(n)):
342
                    yield
343
                yield
344

    
345
            tmpFile.seek(0)
346
            self.client.upload_object(
347
                obj, tmpFile,
348
                hash_cb=blck_gen, upload_cb=upld_gen)
349

    
350
            for i, c in enumerate(OP.mock_calls[-mock_offset:]):
351
                self.assertEqual(OP.mock_calls[i], c)
352

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

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

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

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

    
404
    def test_get_object_info(self):
405
        FR.headers = object_info
406
        version = 'v3r510n'
407
        with patch.object(PC, 'object_head', return_value=FR()) as head:
408
            r = self.client.get_object_info(obj)
409
            self.assertEqual(r, object_info)
410
            r = self.client.get_object_info(obj, version=version)
411
            self.assertEqual(head.mock_calls, [
412
                call(obj, version=None),
413
                call(obj, version=version)])
414
        with patch.object(
415
                PC,
416
                'object_head',
417
                side_effect=ClientError('Obj not found', 404)):
418
            self.assertRaises(
419
                ClientError,
420
                self.client.get_object_info,
421
                obj, version=version)
422

    
423
    def test_get_object_meta(self):
424
        expected = dict()
425
        for k, v in object_info.items():
426
            expected[k] = v
427
        with patch.object(PC, 'get_object_info', return_value=object_info):
428
            r = self.client.get_object_meta(obj)
429
            self.assert_dicts_are_equal(r, expected)
430

    
431
    def test_del_object_meta(self):
432
        metakey = '50m3m3t4k3y'
433
        with patch.object(PC, 'object_post', return_value=FR()) as post:
434
            self.client.del_object_meta(obj, metakey)
435
            self.assertEqual(
436
                post.mock_calls,
437
                [call(obj, update=True, metadata={metakey: ''})])
438

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

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

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

    
512
    def test_delete_object(self):
513
        cont = self.client.container
514
        with patch.object(PC, 'delete', return_value=FR()) as delete:
515
            self.client.delete_object(obj)
516
            self.assertEqual(delete.mock_calls, [
517
                call('/%s/%s/%s' % (user_id, cont, obj), success=(204, 404))])
518
            FR.status_code = 404
519
            self.assertRaises(ClientError, self.client.delete_object, obj)
520

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

    
539
    @patch('%s.set_param' % client_pkg)
540
    def test_list_objects_in_path(self, SP):
541
        FR.json = object_list
542
        path = '/some/awsome/path'
543
        acc = self.client.account
544
        cont = self.client.container
545
        SP = PC.set_param
546
        with patch.object(PC, 'get', return_value=FR()) as get:
547
            self.client.list_objects_in_path(path)
548
            self.assertEqual(get.mock_calls, [
549
                call('/%s/%s' % (acc, cont), success=(200, 204, 404))])
550
            self.assertEqual(SP.mock_calls, [
551
                call('format', 'json'), call('path', path)])
552
            FR.status_code = 404
553
            self.assertRaises(ClientError, self.client.list_objects)
554

    
555
    #  Pithos+ only methods
556

    
557
    def test_purge_container(self):
558
        with patch.object(PC, 'container_delete', return_value=FR()) as cd:
559
            self.client.purge_container()
560
            self.assertTrue('until' in cd.mock_calls[-1][2])
561
            cont = self.client.container
562
            self.client.purge_container('another-container')
563
            self.assertEqual(self.client.container, cont)
564

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

    
600
    def test_create_object_by_manifestation(self):
601
        manifest = '%s/%s' % (self.client.container, obj)
602
        kwargs = dict(
603
                etag='some-etag',
604
                content_encoding='some content_encoding',
605
                content_type='some content-type',
606
                content_disposition='some content_disposition',
607
                public=True,
608
                sharing=dict(read=['u1', 'g1', 'u2'], write=['u1']))
609
        with patch.object(PC, 'object_put', return_value=FR()) as put:
610
            self.client.create_object_by_manifestation(obj)
611
            expected = dict(content_length=0, manifest=manifest)
612
            for k in kwargs:
613
                expected['permissions' if k == 'sharing' else k] = None
614
            self.assertEqual(put.mock_calls[-1], call(obj, **expected))
615
            self.client.create_object_by_manifestation(obj, **kwargs)
616
            expected.update(kwargs)
617
            expected['permissions'] = expected.pop('sharing')
618
            self.assertEqual(put.mock_calls[-1], call(obj, **expected))
619

    
620
    @patch('%s.get_object_hashmap' % pithos_pkg, return_value=object_hashmap)
621
    @patch('%s.object_get' % pithos_pkg, return_value=FR())
622
    def test_download_object(self, GOH, GET):
623
        num_of_blocks = 8
624
        tmpFile = self._create_temp_file(num_of_blocks)
625
        FR.content = tmpFile.read(4 * 1024 * 1024)
626
        tmpFile = self._create_temp_file(num_of_blocks)
627
        GET = PC.object_get
628
        num_of_blocks = len(object_hashmap['hashes'])
629
        kwargs = dict(
630
            resume=True,
631
            version='version',
632
            range_str='10-20',
633
            if_match='if and only if',
634
            if_none_match='if and only not',
635
            if_modified_since='what if not?',
636
            if_unmodified_since='this happens if not!',
637
            async_headers=dict(Range='bytes=0-88888888'))
638

    
639
        self.client.download_object(obj, tmpFile)
640
        self.assertEqual(len(GET.mock_calls), num_of_blocks)
641
        self.assertEqual(GET.mock_calls[-1][1], (obj,))
642
        for k, v in kwargs.items():
643
            if k == 'async_headers':
644
                self.assertTrue('Range' in GET.mock_calls[-1][2][k])
645
            elif k in ('resume', 'range_str'):
646
                continue
647
            else:
648
                self.assertEqual(GET.mock_calls[-1][2][k], None)
649

    
650
        #  Check ranges are consecutive
651
        starts = []
652
        ends = []
653
        for c in GET.mock_calls:
654
            rng_str = c[2]['async_headers']['Range']
655
            (start, rng_str) = rng_str.split('=')
656
            (start, end) = rng_str.split('-')
657
            starts.append(start)
658
            ends.append(end)
659
        ends = sorted(ends)
660
        for i, start in enumerate(sorted(starts)):
661
            if i:
662
                int(ends[i - 1]) == int(start) - 1
663

    
664
        #  With progress bars
665
        try:
666
            from progress.bar import ShadyBar
667
            dl_bar = ShadyBar('Mock dl')
668
        except ImportError:
669
            dl_bar = None
670

    
671
        if dl_bar:
672

    
673
            def blck_gen(n):
674
                for i in dl_bar.iter(range(n)):
675
                    yield
676
                yield
677

    
678
            tmpFile.seek(0)
679
            self.client.download_object(obj, tmpFile, download_cb=blck_gen)
680
            self.assertEqual(len(GET.mock_calls), 2 * num_of_blocks)
681

    
682
        tmpFile.seek(0)
683
        kwargs.pop('async_headers')
684
        kwargs.pop('resume')
685
        self.client.download_object(obj, tmpFile, **kwargs)
686
        for k, v in kwargs.items():
687
            if k == 'range_str':
688
                self.assertEqual(
689
                    GET.mock_calls[-1][2]['data_range'],
690
                    'bytes=%s' % v)
691
            else:
692
                self.assertEqual(GET.mock_calls[-1][2][k], v)
693

    
694
        #  ALl options on no tty
695

    
696
        def foo():
697
            return True
698

    
699
        tmpFile.seek(0)
700
        tmpFile.isatty = foo
701
        self.client.download_object(obj, tmpFile, **kwargs)
702
        for k, v in kwargs.items():
703
            if k == 'range_str':
704
                self.assertTrue('data_range' in GET.mock_calls[-1][2])
705
            else:
706
                self.assertEqual(GET.mock_calls[-1][2][k], v)
707

    
708
    def test_get_object_hashmap(self):
709
        FR.json = object_hashmap
710
        for empty in (304, 412):
711
            with patch.object(
712
                    PC, 'object_get',
713
                    side_effect=ClientError('Empty', status=empty)):
714
                r = self.client.get_object_hashmap(obj)
715
                self.assertEqual(r, {})
716
        exp_args = dict(
717
            hashmap=True,
718
            data_range=None,
719
            version=None,
720
            if_etag_match=None,
721
            if_etag_not_match=None,
722
            if_modified_since=None,
723
            if_unmodified_since=None)
724
        kwargs = dict(
725
            version='s0m3v3r51on',
726
            if_match='if match',
727
            if_none_match='if non match',
728
            if_modified_since='some date here',
729
            if_unmodified_since='some date here',
730
            data_range='10-20')
731
        with patch.object(PC, 'object_get', return_value=FR()) as get:
732
            r = self.client.get_object_hashmap(obj)
733
            self.assertEqual(r, object_hashmap)
734
            self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
735
            r = self.client.get_object_hashmap(obj, **kwargs)
736
            exp_args['if_etag_match'] = kwargs.pop('if_match')
737
            exp_args['if_etag_not_match'] = kwargs.pop('if_none_match')
738
            exp_args.update(kwargs)
739
            self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
740

    
741
    def test_set_account_group(self):
742
        group = 'aU53rGr0up'
743
        usernames = ['u1', 'u2', 'u3']
744
        with patch.object(PC, 'account_post', return_value=FR()) as post:
745
            self.client.set_account_group(group, usernames)
746
            self.assertEqual(
747
                post.mock_calls[-1],
748
                call(update=True, groups={group: usernames}))
749

    
750
    def test_del_account_group(self):
751
        group = 'aU53rGr0up'
752
        with patch.object(PC, 'account_post', return_value=FR()) as post:
753
            self.client.del_account_group(group)
754
            self.assertEqual(
755
                post.mock_calls[-1],
756
                call(update=True, groups={group: []}))
757

    
758
    def test_get_account_quota(self):
759
        key = 'x-account-policy-quota'
760
        with patch.object(PC, 'get_account_info', return_value=account_info):
761
            r = self.client.get_account_quota()
762
            self.assertEqual(r[key], account_info[key])
763

    
764
    def test_get_account_versioning(self):
765
        key = 'x-account-policy-versioning'
766
        with patch.object(PC, 'get_account_info', return_value=account_info):
767
            r = self.client.get_account_versioning()
768
            self.assertEqual(r[key], account_info[key])
769

    
770
    def test_get_account_meta(self):
771
        key = 'x-account-meta-'
772
        with patch.object(PC, 'get_account_info', return_value=account_info):
773
            r = self.client.get_account_meta()
774
            keys = [k for k in r if k.startswith(key)]
775
            self.assertFalse(keys)
776
        acc_info = dict(account_info)
777
        acc_info['%sk1' % key] = 'v1'
778
        acc_info['%sk2' % key] = 'v2'
779
        acc_info['%sk3' % key] = 'v3'
780
        with patch.object(PC, 'get_account_info', return_value=acc_info):
781
            r = self.client.get_account_meta()
782
            for k in [k for k in acc_info if k.startswith(key)]:
783
                self.assertEqual(r[k], acc_info[k])
784

    
785
    def test_get_account_group(self):
786
        key = 'x-account-group-'
787
        with patch.object(PC, 'get_account_info', return_value=account_info):
788
            r = self.client.get_account_group()
789
            keys = [k for k in r if k.startswith(key)]
790
            self.assertFalse(keys)
791
        acc_info = dict(account_info)
792
        acc_info['%sk1' % key] = 'g1'
793
        acc_info['%sk2' % key] = 'g2'
794
        acc_info['%sk3' % key] = 'g3'
795
        with patch.object(PC, 'get_account_info', return_value=acc_info):
796
            r = self.client.get_account_group()
797
            for k in [k for k in acc_info if k.startswith(key)]:
798
                self.assertEqual(r[k], acc_info[k])
799

    
800
    def test_set_account_meta(self):
801
        metas = dict(k1='v1', k2='v2', k3='v3')
802
        with patch.object(PC, 'account_post', return_value=FR()) as post:
803
            self.client.set_account_meta(metas)
804
            self.assertEqual(
805
                post.mock_calls[-1],
806
                call(update=True, metadata=metas))
807

    
808
    def test_set_account_quota(self):
809
        qu = 1024
810
        with patch.object(PC, 'account_post', return_value=FR()) as post:
811
            self.client.set_account_quota(qu)
812
            self.assertEqual(post.mock_calls[-1], call(update=True, quota=qu))
813

    
814
    def test_set_account_versioning(self):
815
        vrs = 'n3wV3r51on1ngTyp3'
816
        with patch.object(PC, 'account_post', return_value=FR()) as post:
817
            self.client.set_account_versioning(vrs)
818
            self.assertEqual(
819
                post.mock_calls[-1],
820
                call(update=True, versioning=vrs))
821

    
822
    def test_del_container(self):
823
        kwarg_list = [
824
            dict(delimiter=None, until=None),
825
            dict(delimiter='X', until='50m3d473')]
826
        with patch.object(PC, 'container_delete', return_value=FR()) as delete:
827
            for kwarg in kwarg_list:
828
                self.client.del_container(**kwarg)
829
                expected = dict(kwarg)
830
                expected['success'] = (204, 404, 409)
831
                self.assertEqual(delete.mock_calls[-1], call(**expected))
832
            for status_code in (404, 409):
833
                FR.status_code = status_code
834
                self.assertRaises(ClientError, self.client.del_container)
835

    
836
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
837
    def test_get_container_versioning(self, GCI):
838
        key = 'x-container-policy-versioning'
839
        cont = 'c0n7-417'
840
        bu_cnt = self.client.container
841
        for container in (None, cont):
842
            r = self.client.get_container_versioning(container=container)
843
            self.assertEqual(r[key], container_info[key])
844
            self.assertEqual(GCI.mock_calls[-1], call())
845
            self.assertEqual(bu_cnt, self.client.container)
846

    
847
    @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
848
    def test_get_container_quota(self, GCI):
849
        key = 'x-container-policy-quota'
850
        cont = 'c0n7-417'
851
        bu_cnt = self.client.container
852
        for container in (None, cont):
853
            r = self.client.get_container_quota(container=container)
854
            self.assertEqual(r[key], container_info[key])
855
            self.assertEqual(GCI.mock_calls[-1], call())
856
            self.assertEqual(bu_cnt, self.client.container)
857

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

    
874
    def test_get_container_object_meta(self):
875
        somedate = '50m3d473'
876
        key = 'x-container-object-meta'
877
        metaval = '50m3m374v41'
878
        container_plus = dict(container_info)
879
        container_plus[key] = metaval
880
        for ret in (
881
                (container_info, {key: ''}),
882
                (container_plus, {key: metaval})):
883
            with patch.object(
884
                    PC,
885
                    'get_container_info',
886
                    return_value=ret[0]) as gci:
887
                for until in (None, somedate):
888
                    r = self.client.get_container_object_meta(until=until)
889
                    self.assertEqual(r, ret[1])
890
                    self.assertEqual(gci.mock_calls[-1], call(until=until))
891

    
892
    def test_set_container_meta(self):
893
        metas = dict(k1='v1', k2='v2', k3='v3')
894
        with patch.object(PC, 'container_post', return_value=FR()) as post:
895
            self.client.set_container_meta(metas)
896
            self.assertEqual(
897
                post.mock_calls[-1],
898
                call(update=True, metadata=metas))
899

    
900
    def test_del_container_meta(self):
901
        with patch.object(PC, 'container_post', return_value=FR()) as ap:
902
            self.client.del_container_meta('somekey')
903
            expected = [call(update=True, metadata={'somekey': ''})]
904
            self.assertEqual(ap.mock_calls, expected)
905

    
906
    def test_set_container_quota(self):
907
        qu = 1024
908
        with patch.object(PC, 'container_post', return_value=FR()) as post:
909
            self.client.set_container_quota(qu)
910
            self.assertEqual(post.mock_calls[-1], call(update=True, quota=qu))
911

    
912
    def test_set_container_versioning(self):
913
        vrs = 'n3wV3r51on1ngTyp3'
914
        with patch.object(PC, 'container_post', return_value=FR()) as post:
915
            self.client.set_container_versioning(vrs)
916
            self.assertEqual(
917
                post.mock_calls[-1],
918
                call(update=True, versioning=vrs))
919

    
920
    def test_del_object(self):
921
        kwargs = [
922
            dict(delimiter=None, until=None),
923
            dict(delimiter='X', until='50m3d473')]
924
        with patch.object(PC, 'object_delete', return_value=FR()) as delete:
925
            for kwarg in kwargs:
926
                self.client.del_object(obj, **kwarg)
927
                self.assertEqual(delete.mock_calls[-1], call(obj, **kwarg))
928

    
929
    def test_set_object_meta(self):
930
        metas = dict(k1='v1', k2='v2', k3='v3')
931
        with patch.object(PC, 'object_post', return_value=FR()) as post:
932
            self.assertRaises(
933
                AssertionError,
934
                self.client.set_object_meta,
935
                obj, 'Non dict arg')
936
            self.client.set_object_meta(obj, metas)
937
            self.assertEqual(
938
                post.mock_calls[-1],
939
                call(obj, update=True, metadata=metas))
940

    
941
    @patch('%s.object_post' % pithos_pkg, return_value=FR())
942
    def test_publish_object(self, post):
943
        oinfo = dict(object_info)
944
        val = 'pubL1c'
945
        oinfo['x-object-public'] = val
946
        with patch.object(PC, 'get_object_info', return_value=oinfo) as gof:
947
            r = self.client.publish_object(obj)
948
            self.assertEqual(
949
                post.mock_calls[-1],
950
                call(obj, public=True, update=True))
951
            self.assertEqual(gof.mock_calls[-1], call(obj))
952
            self.assertEqual(r, '%s%s' % (self.url[:-6], val))