ba6de41e3224d506a1110f4bcee758c2ff583275
[kamaki] / kamaki / clients / test / pithos.py
1 # Copyright 2013 GRNET S.A. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
5 # conditions are met:
6 #
7 #   1. Redistributions of source code must retain the above
8 #      copyright notice, this list of conditions and the following
9 #      disclaimer.
10 #
11 #   2. Redistributions in binary form must reproduce the above
12 #      copyright notice, this list of conditions and the following
13 #      disclaimer in the documentation and/or other materials
14 #      provided with the distribution.
15 #
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
28 #
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
33
34 from unittest import TestCase
35 from mock import patch, call, Mock
36 from tempfile import NamedTemporaryFile
37 from os import urandom
38
39 from kamaki.clients import ClientError
40 from kamaki.clients.pithos import PithosClient as PC
41 from kamaki.clients.astakos import AstakosClient
42 from kamaki.clients.connection.kamakicon import KamakiHTTPConnection as C
43
44 user_id = 'ac0un7-1d-5tr1ng'
45 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
135
136 class Pithos(TestCase):
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     files = []
150
151     def _create_temp_file(self, num_of_blocks):
152         self.files.append(NamedTemporaryFile())
153         tmpFile = self.files[-1]
154         file_size = num_of_blocks * 4 * 1024 * 1024
155         print('\n\tCreate tmp file')
156         tmpFile.write(urandom(file_size))
157         tmpFile.flush()
158         tmpFile.seek(0)
159         print('\t\tDone')
160         return tmpFile
161
162     def assert_dicts_are_equal(self, d1, d2):
163         for k, v in d1.items():
164             self.assertTrue(k in d2)
165             if isinstance(v, dict):
166                 self.assert_dicts_are_equal(v, d2[k])
167             else:
168                 self.assertEqual(unicode(v), unicode(d2[k]))
169
170     def setUp(self):
171         self.url = 'https://www.example.com/pithos'
172         self.token = 'p17h0570k3n'
173         self.client = PC(self.url, self.token)
174         self.client.account = user_id
175         self.client.container = 'c0nt@1n3r_i'
176
177     def tearDown(self):
178         self.FR.headers = dict()
179         self.FR.status_code = 200
180         self.FR.json = dict()
181         self.FR.content = self.FR.json
182         for f in self.files:
183             f.close()
184
185     #  Pithos+ methods that extend storage API
186
187     def test_get_account_info(self):
188         self.FR.headers = account_info
189         self.FR.status_code = 204
190         with patch.object(C, 'perform_request', return_value=self.FR()):
191             r = self.client.get_account_info()
192             self.assertEqual(self.client.http_client.url, self.url)
193             self.assertEqual(self.client.http_client.path, '/%s' % user_id)
194             self.assert_dicts_are_equal(r, account_info)
195             PC.set_param = Mock()
196             untils = ['date 1', 'date 2', 'date 3']
197             for unt in untils:
198                 r = self.client.get_account_info(until=unt)
199                 self.assert_dicts_are_equal(r, account_info)
200             for i in range(len(untils)):
201                 self.assertEqual(
202                     PC.set_param.mock_calls[i],
203                     call('until', untils[i], iff=untils[i]))
204             self.FR.status_code = 401
205             self.assertRaises(ClientError, self.client.get_account_info)
206
207     def test_replace_account_meta(self):
208         self.FR.status_code = 202
209         metas = dict(k1='v1', k2='v2', k3='v3')
210         PC.set_header = Mock()
211         with patch.object(C, 'perform_request', return_value=self.FR()):
212             self.client.replace_account_meta(metas)
213             self.assertEqual(self.client.http_client.url, self.url)
214             self.assertEqual(self.client.http_client.path, '/%s' % user_id)
215             prfx = 'X-Account-Meta-'
216             expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
217             self.assertEqual(PC.set_header.mock_calls, expected)
218
219     def test_del_account_meta(self):
220         keys = ['k1', 'k2', 'k3']
221         with patch.object(PC, 'account_post', return_value=self.FR()) as ap:
222             expected = []
223             for key in keys:
224                 self.client.del_account_meta(key)
225                 expected.append(call(update=True, metadata={key: ''}))
226             self.assertEqual(ap.mock_calls, expected)
227
228     def test_create_container(self):
229         self.FR.status_code = 201
230         with patch.object(PC, 'put', return_value=self.FR()) as put:
231             cont = 's0m3c0n731n3r'
232             self.client.create_container(cont)
233             expected = [call('/%s/%s' % (user_id, cont), success=(201, 202))]
234             self.assertEqual(put.mock_calls, expected)
235             self.FR.status_code = 202
236             self.assertRaises(ClientError, self.client.create_container, cont)
237
238     def test_get_container_info(self):
239         self.FR.headers = container_info
240         with patch.object(PC, 'container_head', return_value=self.FR()) as ch:
241             r = self.client.get_container_info()
242             self.assert_dicts_are_equal(r, container_info)
243             u = 'some date'
244             r = self.client.get_container_info(until=u)
245             self.assertEqual(ch.mock_calls, [call(until=None), call(until=u)])
246
247     def test_delete_container(self):
248         self.FR.status_code = 204
249         with patch.object(PC, 'delete', return_value=self.FR()) as delete:
250             cont = 's0m3c0n731n3r'
251             self.client.delete_container(cont)
252             self.FR.status_code = 404
253             self.assertRaises(ClientError, self.client.delete_container, cont)
254             self.FR.status_code = 409
255             self.assertRaises(ClientError, self.client.delete_container, cont)
256             acall = call('/%s/%s' % (user_id, cont), success=(204, 404, 409))
257             self.assertEqual(delete.mock_calls, [acall] * 3)
258
259     def test_list_containers(self):
260         self.FR.json = container_list
261         with patch.object(PC, 'account_get', return_value=self.FR()):
262             r = self.client.list_containers()
263             for i in range(len(r)):
264                 self.assert_dicts_are_equal(r[i], container_list[i])
265
266     def test_upload_object(self):
267         PC.get_container_info = Mock(return_value=container_info)
268         PC.container_post = Mock(return_value=self.FR())
269         PC.object_put = Mock(return_value=self.FR())
270         num_of_blocks = 8
271         tmpFile = self._create_temp_file(num_of_blocks)
272
273         # Without kwargs
274         self.client.upload_object(obj, tmpFile)
275         self.assertEqual(PC.get_container_info.mock_calls, [call()])
276         [call1, call2] = PC.object_put.mock_calls
277
278         (args1, kwargs1) = call1[1:3]
279         (args2, kwargs2) = call2[1:3]
280         self.assertEqual(args1, (obj,))
281         expected1 = dict(
282             hashmap=True,
283             success=(201, 409),
284             format='json',
285             json=dict(
286                 hashes=['s0m3h@5h'] * num_of_blocks,
287                 bytes=num_of_blocks * 4 * 1024 * 1024),
288             etag=None,
289             content_encoding=None,
290             content_type='application/octet-stream',
291             content_disposition=None,
292             public=None,
293             permissions=None)
294         for k, v in expected1.items():
295             if k == 'json':
296                 self.assertEqual(len(v['hashes']), len(kwargs1[k]['hashes']))
297                 self.assertEqual(v['bytes'], kwargs1[k]['bytes'])
298             else:
299                 self.assertEqual(v, kwargs1[k])
300
301         (args2, kwargs2) = call2[1:3]
302         self.assertEqual(args2, (obj,))
303         expected2 = dict(
304             json=dict(
305                 hashes=['s0m3h@5h'] * num_of_blocks,
306                 bytes=num_of_blocks * 4 * 1024 * 1024),
307             content_type='application/octet-stream',
308             hashmap=True,
309             success=201,
310             format='json')
311         for k, v in expected2.items():
312             if k == 'json':
313                 self.assertEqual(len(v['hashes']), len(kwargs2[k]['hashes']))
314                 self.assertEqual(v['bytes'], kwargs2[k]['bytes'])
315             else:
316                 self.assertEqual(v, kwargs2[k])
317
318         OP = PC.object_put
319         mock_offset = 2
320
321         #  With progress bars
322         try:
323             from progress.bar import ShadyBar
324             blck_bar = ShadyBar('Mock blck calc.')
325             upld_bar = ShadyBar('Mock uplds')
326         except ImportError:
327             blck_bar = None
328             upld_bar = None
329
330         if blck_bar and upld_bar:
331
332             def blck_gen(n):
333                 for i in blck_bar.iter(range(n)):
334                     yield
335                 yield
336
337             def upld_gen(n):
338                 for i in upld_bar.iter(range(n)):
339                     yield
340                 yield
341
342             tmpFile.seek(0)
343             self.client.upload_object(
344                 obj, tmpFile,
345                 hash_cb=blck_gen, upload_cb=upld_gen)
346
347             for i, c in enumerate(OP.mock_calls[-mock_offset:]):
348                 self.assertEqual(OP.mock_calls[i], c)
349
350         #  With content-type
351         tmpFile.seek(0)
352         ctype = 'video/mpeg'
353         sharing = dict(read=['u1', 'g1', 'u2'], write=['u1'])
354         self.client.upload_object(obj, tmpFile,
355             content_type=ctype, sharing=sharing)
356         self.assertEqual(OP.mock_calls[-1][2]['content_type'], ctype)
357         self.assert_dicts_are_equal(
358             OP.mock_calls[-2][2]['permissions'],
359             sharing)
360
361         # With other args
362         tmpFile.seek(0)
363         kwargs = dict(
364             etag='s0m3E74g',
365             content_type=ctype,
366             content_disposition=ctype + 'd15p051710n',
367             public=True,
368             content_encoding='802.11')
369         self.client.upload_object(obj, tmpFile, **kwargs)
370         for arg, val in kwargs.items():
371             self.assertEqual(OP.mock_calls[-2][2][arg], val)
372
373     def test_create_object(self):
374         PC.set_header = Mock()
375         cont = self.client.container
376         ctype = 'c0n73n7/typ3'
377         exp_shd = [
378             call('Content-Type', 'application/octet-stream'),
379             call('Content-length', '0'),
380             call('Content-Type', ctype), call('Content-length', '42')]
381         exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)] * 2
382         with patch.object(PC, 'put', return_value=self.FR()) as put:
383             self.client.create_object(obj)
384             self.client.create_object(obj,
385                 content_type=ctype, content_length=42)
386             self.assertEqual(PC.set_header.mock_calls, exp_shd)
387             self.assertEqual(put.mock_calls, exp_put)
388
389     def test_create_directory(self):
390         PC.set_header = Mock()
391         cont = self.client.container
392         exp_shd = [
393             call('Content-Type', 'application/directory'),
394             call('Content-length', '0')]
395         exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)]
396         with patch.object(PC, 'put', return_value=self.FR()) as put:
397             self.client.create_directory(obj)
398             self.assertEqual(PC.set_header.mock_calls, exp_shd)
399             self.assertEqual(put.mock_calls, exp_put)
400
401     def test_get_object_info(self):
402         self.FR.headers = object_info
403         version = 'v3r510n'
404         with patch.object(PC, 'object_head', return_value=self.FR()) as head:
405             r = self.client.get_object_info(obj)
406             self.assertEqual(r, object_info)
407             r = self.client.get_object_info(obj, version=version)
408             self.assertEqual(head.mock_calls, [
409                 call(obj, version=None),
410                 call(obj, version=version)])
411         with patch.object(
412                 PC,
413                 'object_head',
414                 side_effect=ClientError('Obj not found', 404)):
415             self.assertRaises(
416                 ClientError,
417                 self.client.get_object_info,
418                 obj, version=version)
419
420     def test_get_object_meta(self):
421         expected = dict()
422         for k, v in object_info.items():
423             expected[k] = v
424         with patch.object(
425                 PC,
426                 'get_object_info',
427                 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=self.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     def test_replace_object_meta(self):
440         PC.set_header = Mock()
441         metas = dict(k1='new1', k2='new2', k3='new3')
442         cont = self.client.container
443         with patch.object(PC, 'post', return_value=self.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=self.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=self.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=self.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             self.FR.status_code = 404
519             self.assertRaises(ClientError, self.client.delete_object, obj)
520
521     def test_list_objects(self):
522         self.FR.json = object_list
523         acc = self.client.account
524         cont = self.client.container
525         PC.set_param = Mock()
526         SP = PC.set_param
527         with patch.object(PC, 'get', return_value=self.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             self.FR.status_code = 304
535             self.assertEqual(self.client.list_objects(), [])
536             self.FR.status_code = 404
537             self.assertRaises(ClientError, self.client.list_objects)
538
539     def test_list_objects_in_path(self):
540         self.FR.json = object_list
541         path = '/some/awsome/path'
542         acc = self.client.account
543         cont = self.client.container
544         PC.set_param = Mock()
545         SP = PC.set_param
546         with patch.object(PC, 'get', return_value=self.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             self.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(
559                 PC,
560                 'container_delete',
561                 return_value=self.FR()) as cd:
562             self.client.purge_container()
563             self.assertTrue('until' in cd.mock_calls[-1][2])
564             cont = self.client.container
565             self.client.purge_container('another-container')
566             self.assertEqual(self.client.container, cont)
567
568     def test_upload_object_unchunked(self):
569         num_of_blocks = 8
570         tmpFile = self._create_temp_file(num_of_blocks)
571         expected = dict(
572                 success=201,
573                 data=num_of_blocks * 4 * 1024 * 1024,
574                 etag='some-etag',
575                 content_encoding='some content_encoding',
576                 content_type='some content-type',
577                 content_disposition='some content_disposition',
578                 public=True,
579                 permissions=dict(read=['u1', 'g1', 'u2'], write=['u1']))
580         with patch.object(PC, 'object_put', return_value=self.FR()) as put:
581             self.client.upload_object_unchunked(obj, tmpFile)
582             self.assertEqual(put.mock_calls[-1][1], (obj,))
583             self.assertEqual(
584                 sorted(put.mock_calls[-1][2].keys()),
585                 sorted(expected.keys()))
586             kwargs = dict(expected)
587             kwargs.pop('success')
588             kwargs['size'] = kwargs.pop('data')
589             kwargs['sharing'] = kwargs.pop('permissions')
590             tmpFile.seek(0)
591             self.client.upload_object_unchunked(obj, tmpFile, **kwargs)
592             pmc = put.mock_calls[-1][2]
593             for k, v in expected.items():
594                 if k == 'data':
595                     self.assertEqual(len(pmc[k]), v)
596                 else:
597                     self.assertEqual(pmc[k], v)
598             self.assertRaises(
599                 ClientError,
600                 self.client.upload_object_unchunked,
601                 obj, tmpFile, withHashFile=True)
602
603     def test_create_object_by_manifestation(self):
604         manifest = '%s/%s' % (self.client.container, obj)
605         kwargs = dict(
606                 etag='some-etag',
607                 content_encoding='some content_encoding',
608                 content_type='some content-type',
609                 content_disposition='some content_disposition',
610                 public=True,
611                 sharing=dict(read=['u1', 'g1', 'u2'], write=['u1']))
612         with patch.object(PC, 'object_put', return_value=self.FR()) as put:
613             self.client.create_object_by_manifestation(obj)
614             expected = dict(content_length=0, manifest=manifest)
615             for k in kwargs:
616                 expected['permissions' if k == 'sharing' else k] = None
617             self.assertEqual(put.mock_calls[-1], call(obj, **expected))
618             self.client.create_object_by_manifestation(obj, **kwargs)
619             expected.update(kwargs)
620             expected['permissions'] = expected.pop('sharing')
621             self.assertEqual(put.mock_calls[-1], call(obj, **expected))
622
623     def test_download_object(self):
624         PC.get_object_hashmap = Mock(return_value=object_hashmap)
625         num_of_blocks = 8
626         tmpFile = self._create_temp_file(num_of_blocks)
627         self.FR.content = tmpFile.read(4 * 1024 * 1024)
628         tmpFile = self._create_temp_file(num_of_blocks)
629         PC.object_get = Mock(return_value=self.FR())
630         GET = PC.object_get
631         num_of_blocks = len(object_hashmap['hashes'])
632
633         kwargs = dict(
634             resume=True,
635             version='version',
636             range_str='10-20',
637             if_match='if and only if',
638             if_none_match='if and only not',
639             if_modified_since='what if not?',
640             if_unmodified_since='this happens if not!',
641             async_headers=dict(Range='bytes=0-88888888'))
642
643         self.client.download_object(obj, tmpFile)
644         self.assertEqual(len(GET.mock_calls), num_of_blocks)
645         self.assertEqual(GET.mock_calls[-1][1], (obj,))
646         for k, v in kwargs.items():
647             if k == 'async_headers':
648                 self.assertTrue('Range' in GET.mock_calls[-1][2][k])
649             elif k in ('resume', 'range_str'):
650                 continue
651             else:
652                 self.assertEqual(GET.mock_calls[-1][2][k], None)
653
654         #  Check ranges are consecutive
655         starts = []
656         ends = []
657         for c in GET.mock_calls:
658             rng_str = c[2]['async_headers']['Range']
659             (start, rng_str) = rng_str.split('=')
660             (start, end) = rng_str.split('-')
661             starts.append(start)
662             ends.append(end)
663         ends = sorted(ends)
664         for i, start in enumerate(sorted(starts)):
665             if i:
666                 int(ends[i - 1]) == int(start) - 1
667
668         #  With progress bars
669         try:
670             from progress.bar import ShadyBar
671             dl_bar = ShadyBar('Mock dl')
672         except ImportError:
673             dl_bar = None
674
675         if dl_bar:
676
677             def blck_gen(n):
678                 for i in dl_bar.iter(range(n)):
679                     yield
680                 yield
681
682             tmpFile.seek(0)
683             self.client.download_object(obj, tmpFile, download_cb=blck_gen)
684             self.assertEqual(len(GET.mock_calls), 2 * num_of_blocks)
685
686         tmpFile.seek(0)
687         kwargs.pop('async_headers')
688         kwargs.pop('resume')
689         self.client.download_object(obj, tmpFile, **kwargs)
690         for k, v in kwargs.items():
691             if k == 'range_str':
692                 self.assertEqual(
693                     GET.mock_calls[-1][2]['data_range'],
694                     'bytes=%s' % v)
695             else:
696                 self.assertEqual(GET.mock_calls[-1][2][k], v)
697
698         #  ALl options on no tty
699
700         def foo():
701             return True
702
703         tmpFile.seek(0)
704         tmpFile.isatty = foo
705         self.client.download_object(obj, tmpFile, **kwargs)
706         for k, v in kwargs.items():
707             if k == 'range_str':
708                 self.assertTrue('data_range' in GET.mock_calls[-1][2])
709             else:
710                 self.assertEqual(GET.mock_calls[-1][2][k], v)
711
712     def test_get_object_hashmap(self):
713         self.FR.json = object_hashmap
714         for empty in (304, 412):
715             with patch.object(
716                     PC, 'object_get',
717                     side_effect=ClientError('Empty', status=empty)):
718                 r = self.client.get_object_hashmap(obj)
719                 self.assertEqual(r, {})
720         exp_args = dict(
721             hashmap=True,
722             data_range=None,
723             version=None,
724             if_etag_match=None,
725             if_etag_not_match=None,
726             if_modified_since=None,
727             if_unmodified_since=None)
728         kwargs = dict(
729             version='s0m3v3r51on',
730             if_match='if match',
731             if_none_match='if non match',
732             if_modified_since='some date here',
733             if_unmodified_since='some date here',
734             data_range='10-20')
735         with patch.object(PC, 'object_get', return_value=self.FR()) as get:
736             r = self.client.get_object_hashmap(obj)
737             self.assertEqual(r, object_hashmap)
738             self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
739             r = self.client.get_object_hashmap(obj, **kwargs)
740             exp_args['if_etag_match'] = kwargs.pop('if_match')
741             exp_args['if_etag_not_match'] = kwargs.pop('if_none_match')
742             exp_args.update(kwargs)
743             self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
744
745     def test_set_account_group(self):
746         group = 'aU53rGr0up'
747         usernames = ['u1', 'u2', 'u3']
748         with patch.object(PC, 'account_post', return_value=self.FR()) as post:
749             self.client.set_account_group(group, usernames)
750             self.assertEqual(
751                 post.mock_calls[-1],
752                 call(update=True, groups={group: usernames}))
753
754     def test_del_account_group(self):
755         group = 'aU53rGr0up'
756         with patch.object(PC, 'account_post', return_value=self.FR()) as post:
757             self.client.del_account_group(group)
758             self.assertEqual(
759                 post.mock_calls[-1],
760                 call(update=True, groups={group: []}))
761
762     def test_get_account_quota(self):
763         key = 'x-account-policy-quota'
764         with patch.object(PC, 'get_account_info', return_value=account_info):
765             r = self.client.get_account_quota()
766             self.assertEqual(r[key], account_info[key])