Unittest PithosRest.object_move
[kamaki] / kamaki / clients / pithos / test.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
36 from tempfile import NamedTemporaryFile
37 from os import urandom
38 from itertools import product
39 from random import randint
40
41 try:
42     from collections import OrderedDict
43 except ImportError:
44     from kamaki.clients.commisioning.utils.ordereddict import OrderedDict
45
46 from kamaki.clients import ClientError
47 from kamaki.clients.pithos import PithosClient, PithosRestClient
48
49
50 rest_pkg = 'kamaki.clients.pithos.rest_api.PithosRestClient'
51 pithos_pkg = 'kamaki.clients.pithos.PithosClient'
52
53 user_id = 'ac0un7-1d-5tr1ng'
54 obj = 'obj3c7N4m3'
55
56 account_info = {
57     'content-language': 'en-us',
58     'content-type': 'text/html; charset=utf-8',
59     'date': 'Wed, 06 Mar 2013 13:25:51 GMT',
60     'last-modified': 'Mon, 04 Mar 2013 18:22:31 GMT',
61     'server': 'gunicorn/0.14.5',
62     'vary': 'Accept-Language',
63     'x-account-bytes-used': '751615526',
64     'x-account-container-count': 7,
65     'x-account-policy-quota': 53687091200,
66     'x-account-policy-versioning': 'auto'}
67 container_info = {
68     'content-language': 'en-us',
69     'content-type': 'text/html; charset=utf-8',
70     'date': 'Wed, 06 Mar 2013 15:11:05 GMT',
71     'last-modified': 'Wed, 27 Feb 2013 15:56:13 GMT',
72     'server': 'gunicorn/0.14.5',
73     'vary': 'Accept-Language',
74     'x-container-block-hash': 'sha256',
75     'x-container-block-size': 4194304,
76     'x-container-bytes-used': 309528938,
77     'x-container-object-count': 14,
78     'x-container-object-meta': '',
79     'x-container-policy-quota': 53687091200,
80     'x-container-policy-versioning': 'auto'}
81 object_info = {
82     'content-language': 'en-us',
83     'content-length': 254965,
84     'content-type': 'application/octet-stream',
85     'date': 'Thu, 07 Mar 2013 13:27:43 GMT',
86     'etag': '',
87     'last-modified': 'Mon, 04 Mar 2013 18:22:31 GMT',
88     'server': 'gunicorn/0.14.5',
89     'vary': 'Accept-Language',
90     'x-object-hash': 'obj3c7h45h1s0bj3c7h45h411r34dY',
91     'x-object-uuid': 'd0c747ca-34bd-49e0-8e98-1d07d8b0cbc7',
92     'x-object-version': '525996',
93     'x-object-version-timestamp': 'Mon, 04 Mar 2013 18:22:31 GMT',
94     'x-object-meta-k1': 'v1',
95     'x-object-meta-k2': 'v2'}
96 container_list = [
97     dict(
98         count=2,
99         last_modified="2013-02-27T11:56:09.893033+00:00",
100         bytes=677076979,
101         name="pithos",
102         x_container_policy=dict(quota="21474836480", versioning="auto")),
103     dict(
104         count=0,
105         last_modified="2012-10-23T12:25:17.229187+00:00",
106         bytes=0,
107         name="trash",
108         x_container_policy=dict(quota="21474836480", versioning="auto"))]
109 object_list = [
110     dict(hash="",
111         name="The_Secret_Garden.zip",
112         x_object_public="/public/wdp9p",
113         bytes=203304947,
114         x_object_version_timestamp="1360237915.7027509",
115         x_object_uuid="s0m3uu1df0r0bj0n3",
116         last_modified="2013-02-07T11:51:55.702751+00:00",
117         content_type="application/octet-stream",
118         x_object_hash="0afdf29f71cd53126225c3f54ca",
119         x_object_version=17737,
120         x_object_modified_by=user_id),
121     dict(hash="",
122         name="The_Revealed_Garden.zip",
123         x_object_public="/public/wpd7p",
124         bytes=20330947,
125         x_object_version_timestamp="13602915.7027509",
126         x_object_uuid="s0m3uu1df0r0bj70w",
127         last_modified="2013-02-07T11:51:55.702751+00:00",
128         content_type="application/octet-stream",
129         x_object_hash="0afdf29f71cd53126225c3f54ca",
130         x_object_version=17737,
131         x_object_modified_by=user_id)]
132 object_hashmap = dict(
133     block_hash="sha256", block_size=4194304, bytes=33554432,
134     hashes=[
135         "4988438cc1c0292c085d289649b28cf547ba3db71c6efaac9f2df7e193d4d0af",
136         "b214244aa56df7d1df7c6cac066e7cef268d9c2beb4dcf7ce68af667b0626f91",
137         "17f365f25e0682565ded30576066bb13377a3d306967e4d74e06bb6bbc20f75f",
138         "2524ae208932669fff89adf8a2fc0df3b67736ca0d3aadce7a2ce640f142af37",
139         "5d807a2129d2fcd3c221c3da418ed52af3fc48d0817b62e0bb437acffccd3514",
140         "609de22ce842d997f645fc49d5f14e0e3766dd51a6cbe66383b2bab82c8dfcd0",
141         "3102851ac168c78be70e35ff5178c7b1ebebd589e5106d565ca1094d1ca8ff59",
142         "bfe306dd24e92a8d85caf7055643f250fd319e8c4cdd4755ddabbf3ff97e83c7"])
143 sharers = [
144     dict(last_modified="2013-01-29T16:50:06.084674+00:00", name="0b1a-82d5"),
145     dict(last_modified="2013-01-29T16:50:06.084674+00:00", name="0b2a-f2d5"),
146     dict(last_modified="2013-01-29T16:50:06.084674+00:00", name="2b1a-82d6")]
147
148
149 class FR(object):
150     """FR stands for Fake Response"""
151     json = dict()
152     headers = dict()
153     content = json
154     status = None
155     status_code = 200
156
157     def release(self):
158         pass
159
160
161 class PithosRest(TestCase):
162
163     def setUp(self):
164         self.url = 'https://www.example.com/pithos'
165         self.token = 'p17h0570k3n'
166         self.client = PithosRestClient(self.url, self.token)
167         self.client.account = user_id
168         self.client.container = 'c0nt@1n3r_i'
169
170     def tearDown(self):
171         FR.headers = dict()
172         FR.json = dict()
173         FR.content = FR.json
174
175     @patch('%s.set_param' % rest_pkg)
176     @patch('%s.set_header' % rest_pkg)
177     @patch('%s.head' % rest_pkg, return_value=FR())
178     def test_account_head(self, head, SH, SP):
179         for params in product(
180                 (None, '50m3-d473'),
181                 (None, '50m3-07h3r-d473'),
182                 (None, 'y37-4n7h3r-d473'),
183                 ((), ('someval',), ('v1', 'v2',)),
184                 (dict(), dict(success=200), dict(k='v', v='k'))):
185             args, kwargs = params[-2], params[-1]
186             params = params[:-2]
187             self.client.account_head(*(params + args), **kwargs)
188             unt = params[0]
189             self.assertEqual(SP.mock_calls[-1], call('until', unt, iff=unt))
190             IMS, IUS = params[1], params[2]
191             self.assertEqual(SH.mock_calls[-2:], [
192                 call('If-Modified-Since', IMS),
193                 call('If-Unmodified-Since', IUS)])
194             self.assertEqual(head.mock_calls[-1], call(
195                 '/%s' % self.client.account,
196                 *args,
197                 success=kwargs.pop('success', 204),
198                 **kwargs))
199
200     @patch('%s.set_param' % rest_pkg)
201     @patch('%s.set_header' % rest_pkg)
202     @patch('%s.get' % rest_pkg, return_value=FR())
203     def test_account_get(self, get, SH, SP):
204         keys = ('limit', 'marker', 'format', 'shared', 'until')
205         for params in product(
206                 (None, 42),
207                 (None, 'X'),
208                 ('json', 'xml'),
209                 (False, True),
210                 (None, '50m3-d473'),
211                 (None, '50m3-07h3r-d473'),
212                 (None, 'y37-4n7h3r-d473'),
213                 ((), ('someval',), ('v1', 'v2',)),
214                 (dict(), dict(success=200), dict(k='v', v='k'))):
215             args, kwargs = params[-2], params[-1]
216             params = params[:-2]
217             self.client.account_get(*(params + args), **kwargs)
218             self.assertEqual(SP.mock_calls[-5:],
219                 [call(keys[i], iff=X) if (
220                     i == 3) else call(
221                         keys[i], X, iff=X) for i, X in enumerate(params[:5])])
222             IMS, IUS = params[5], params[6]
223             self.assertEqual(SH.mock_calls[-2:], [
224                 call('If-Modified-Since', IMS),
225                 call('If-Unmodified-Since', IUS)])
226             self.assertEqual(get.mock_calls[-1], call(
227                 '/%s' % self.client.account,
228                 *args,
229                 success=kwargs.pop('success', (200, 204)),
230                 **kwargs))
231
232     @patch('%s.set_param' % rest_pkg)
233     @patch('%s.set_header' % rest_pkg)
234     @patch('%s.post' % rest_pkg, return_value=FR())
235     def test_account_post(self, post, SH, SP):
236         #keys = ('update', 'groups', 'metadata', 'quota', 'versioning')
237         for pm in product(
238                 (True, False),
239                 ({}, dict(g=['u1', 'u2']), dict(g1=[], g2=['u1', 'u2'])),
240                 (None, dict(k1='v1', k2='v2', k3='v2'), dict(k='v')),
241                 (None, 42),
242                 (None, 'v3r510n1ng'),
243                 ((), ('someval',), ('v1', 'v2',)),
244                 (dict(), dict(success=200), dict(k='v', v='k'))):
245             args, kwargs = pm[-2:]
246             pm = pm[:-2]
247             self.client.account_post(*(pm + args), **kwargs)
248             upd = pm[0]
249             self.assertEqual(SP.mock_calls[-1], call('update', iff=upd))
250             expected = []
251             if pm[1]:
252                 expected += [
253                 call('X-Account-Group-%s' % k, v) for k, v in pm[1].items()]
254             if pm[2]:
255                 expected = [
256                 call('X-Account-Meta-%s' % k, v) for k, v in pm[2].items()]
257             expected = [
258                 call('X-Account-Policy-Quota', pm[3]),
259                 call('X-Account-Policy-Versioning', pm[4])]
260             self.assertEqual(SH.mock_calls[- len(expected):], expected)
261             self.assertEqual(post.mock_calls[-1], call(
262                 '/%s' % self.client.account,
263                 *args,
264                 success=kwargs.pop('success', 202),
265                 **kwargs))
266
267     @patch('%s.set_param' % rest_pkg)
268     @patch('%s.set_header' % rest_pkg)
269     @patch('%s.head' % rest_pkg, return_value=FR())
270     def test_container_head(self, head, SH, SP):
271         for pm in product(
272                 (None, '4-d473'),
273                 (None, '47h3r-d473'),
274                 (None, 'y37-4n47h3r'),
275                 ((), ('someval',)),
276                 (dict(), dict(success=200), dict(k='v', v='k'))):
277             args, kwargs = pm[-2:]
278             pm = pm[:-2]
279             self.client.container_head(*(pm + args), **kwargs)
280             unt, ims, ius = pm[0:3]
281             self.assertEqual(SP.mock_calls[-1], call('until', unt, iff=unt))
282             self.assertEqual(SH.mock_calls[-2:], [
283                 call('If-Modified-Since', ims),
284                 call('If-Unmodified-Since', ius)])
285             self.assertEqual(head.mock_calls[-1], call(
286                 '/%s/%s' % (self.client.account, self.client.container),
287                 *args,
288                 success=kwargs.pop('success', 204),
289                 **kwargs))
290
291     @patch('%s.set_param' % rest_pkg)
292     @patch('%s.set_header' % rest_pkg)
293     @patch('%s.get' % rest_pkg, return_value=FR())
294     def test_container_get(self, get, SH, SP):
295         for pm in product(
296                 (None, 42),
297                 (None, 'X'),
298                 (None, 'some/prefix'),
299                 (None, 'delimiter'),
300                 (None, '/some/path'),
301                 ('json', 'some-format'),
302                 ([], ['k1', 'k2', 'k3']),
303                 (False, True),
304                 (None, 'unt1l-d473'),
305                 (None, 'y37-4n47h3r'),
306                 (None, '4n47h3r-d473'),
307                 ((), ('someval',)),
308                 (dict(), dict(success=400), dict(k='v', v='k'))):
309             args, kwargs = pm[-2:]
310             pm = pm[:-2]
311             self.client.container_get(*(pm + args), **kwargs)
312             lmt, mrk, prfx, dlm, path, frmt, meta, shr, unt = pm[:-2]
313             exp = [call('limit', lmt, iff=lmt), call('marker', mrk, iff=mrk)]
314             exp += [call('path', path)] if path else [
315                 call('prefix', prfx, iff=prfx),
316                 call('delimiter', dlm, iff=dlm)]
317             exp += [call('format', frmt, iff=frmt), call('shared', iff=shr)]
318             if meta:
319                 exp += [call('meta', ','.join(meta))]
320             exp += [call('until', unt, iff=unt)]
321             self.assertEqual(SP.mock_calls[- len(exp):], exp)
322             ims, ius = pm[-2:]
323             self.assertEqual(SH.mock_calls[-2:], [
324                 call('If-Modified-Since', ims),
325                 call('If-Unmodified-Since', ius)])
326             self.assertEqual(get.mock_calls[-1], call(
327                 '/%s/%s' % (self.client.account, self.client.container),
328                 *args,
329                 success=kwargs.pop('success', 200),
330                 **kwargs))
331
332     @patch('%s.set_header' % rest_pkg)
333     @patch('%s.put' % rest_pkg, return_value=FR())
334     def test_container_put(self, put, SH):
335         for pm in product(
336                 (None, 42),
337                 (None, 'v3r51on1ng'),
338                 (dict(), dict(k1='v2'), dict(k2='v2', k3='v3')),
339                 ((), ('someval',)),
340                 (dict(), dict(success=400), dict(k='v', v='k'))):
341             args, kwargs = pm[-2:]
342             pm = pm[:-2]
343             self.client.container_put(*(pm + args), **kwargs)
344             quota, versioning, metas = pm[-3:]
345             exp = [
346                 call('X-Container-Policy-Quota', quota),
347                 call('X-Container-Policy-Versioning', versioning)] + [
348                 call('X-Container-Meta-%s' % k, v) for k, v in metas.items()]
349             self.assertEqual(SH.mock_calls[- len(exp):], exp)
350             self.assertEqual(put.mock_calls[-1], call(
351                 '/%s/%s' % (self.client.account, self.client.container),
352                 *args,
353                 success=kwargs.pop('success', (201, 202)),
354                 **kwargs))
355
356     @patch('%s.set_param' % rest_pkg)
357     @patch('%s.set_header' % rest_pkg)
358     @patch('%s.post' % rest_pkg, return_value=FR())
359     def test_container_post(self, post, SH, SP):
360         for pm in product(
361                 (True, False),
362                 ('json', 'some-format'),
363                 (None, 'quota'),
364                 (None, 'v3r51on1ng'),
365                 (dict(), dict(k1='v2'), dict(k2='v2', k3='v3')),
366                 (None, 'content-type'),
367                 (None, 42),
368                 (None, 'transfer-encoding'),
369                 ((), ('someval',)),
370                 (dict(), dict(success=400), dict(k='v', v='k'))):
371             args, kwargs = pm[-2:]
372             pm = pm[:-2]
373             self.client.container_post(*(pm + args), **kwargs)
374             upd, frmt = pm[:2]
375             self.assertEqual(SP.mock_calls[-2:], [
376                 call('update', iff=upd),
377                 call('format', frmt, iff=frmt)])
378             qta, vrs, metas, ctype, clen, trenc = pm[2:]
379             prfx = 'X-Container-Meta-'
380             exp = [
381                 call('X-Container-Policy-Quota', qta),
382                 call('X-Container-Policy-Versioning', vrs)] + [
383                 call('%s%s' % (prfx, k), v) for k, v in metas.items()] + [
384                 call('Content-Type', ctype),
385                 call('Content-Length', clen),
386                 call('Transfer-Encoding', trenc)]
387             self.assertEqual(SH.mock_calls[- len(exp):], exp)
388             ims, ius = pm[-2:]
389             self.assertEqual(post.mock_calls[-1], call(
390                 '/%s/%s' % (self.client.account, self.client.container),
391                 *args,
392                 success=kwargs.pop('success', 202),
393                 **kwargs))
394
395     @patch('%s.set_param' % rest_pkg)
396     @patch('%s.delete' % rest_pkg, return_value=FR())
397     def test_container_delete(self, delete, SP):
398         for pm in product(
399                 (None, 'd473'),
400                 (None, 'd3l1m'),
401                 ((), ('someval',)),
402                 (dict(), dict(success=400), dict(k='v', v='k'))):
403             args, kwargs = pm[-2:]
404             pm = pm[:-2]
405             self.client.container_delete(*(pm + args), **kwargs)
406             unt, dlm = pm[-2:]
407             self.assertEqual(SP.mock_calls[-2:], [
408                 call('until', unt, iff=unt),
409                 call('delimiter', dlm, iff=dlm)])
410             self.assertEqual(delete.mock_calls[-1], call(
411                 '/%s/%s' % (self.client.account, self.client.container),
412                 *args,
413                 success=kwargs.pop('success', 204),
414                 **kwargs))
415
416     @patch('%s.set_param' % rest_pkg)
417     @patch('%s.set_header' % rest_pkg)
418     @patch('%s.head' % rest_pkg, return_value=FR())
419     def test_object_head(self, head, SH, SP):
420         for pm in product(
421                 (None, 'v3r510n'),
422                 (None, '1f-374g'),
423                 (None, '1f-n0-74g'),
424                 (None, '1f-m0d-51nc3'),
425                 (None, '1f-unm0d-51nc3'),
426                 ((), ('someval',)),
427                 (dict(), dict(success=400), dict(k='v', v='k'))):
428             args, kwargs = pm[-2:]
429             pm = pm[:-2]
430             self.client.object_head(obj, *(pm + args), **kwargs)
431             vrs, etag, netag, ims, ius = pm[:5]
432             self.assertEqual(
433                 SP.mock_calls[-1],
434                 call('version', vrs, iff=vrs))
435             self.assertEqual(SH.mock_calls[-4:], [
436                 call('If-Match', etag),
437                 call('If-None-Match', netag),
438                 call('If-Modified-Since', ims),
439                 call('If-Unmodified-Since', ius)])
440             acc, cont = self.client.account, self.client.container
441             self.assertEqual(head.mock_calls[-1], call(
442                 '/%s/%s/%s' % (acc, cont, obj),
443                 *args,
444                 success=kwargs.pop('success', 200),
445                 **kwargs))
446
447     @patch('%s.set_param' % rest_pkg)
448     @patch('%s.set_header' % rest_pkg)
449     @patch('%s.get' % rest_pkg, return_value=FR())
450     def test_object_get(self, get, SH, SP):
451         for pm in product(
452                 ('json', 'f0rm47'),
453                 (False, True),
454                 (None, 'v3r510n'),
455                 (None, 'range=74-63'),
456                 (False, True),
457                 (None, '3746'),
458                 (None, 'non-3746'),
459                 (None, '1f-m0d'),
460                 (None, '1f-unm0d'),
461                 ((), ('someval',)),
462                 (dict(), dict(success=400), dict(k='v', v='k'))):
463             args, kwargs = pm[-2:]
464             pm = pm[:-2]
465             self.client.object_get(obj, *(pm + args), **kwargs)
466             format, hashmap, version = pm[:3]
467             self.assertEqual(SP.mock_calls[-3:], [
468                 call('format', format, iff=format),
469                 call('hashmap', hashmap, iff=hashmap),
470                 call('version', version, iff=version)])
471             rng, ifrng, im, inm, ims, ius = pm[-6:]
472             self.assertEqual(SH.mock_calls[-6:], [
473                 call('Range', rng),
474                 call('If-Range', '', ifrng and rng),
475                 call('If-Match', im),
476                 call('If-None-Match', inm),
477                 call('If-Modified-Since', ims),
478                 call('If-Unmodified-Since', ius)])
479             acc, cont = self.client.account, self.client.container
480             self.assertEqual(get.mock_calls[-1], call(
481                 '/%s/%s/%s' % (acc, cont, obj),
482                 *args,
483                 success=kwargs.pop('success', 200),
484                 **kwargs))
485
486     @patch('%s.set_param' % rest_pkg)
487     @patch('%s.set_header' % rest_pkg)
488     @patch('%s.put' % rest_pkg, return_value=FR())
489     def test_object_put(self, put, SH, SP):
490         for pm in product(
491                 ('json', 'f0rm47'),
492                 (False, True),
493                 (None, 'delim',),
494                 (dict(), dict(read=['u1', 'g2'], write=['u1'])),
495                 (False, True),
496                 (dict(), dict(k2='v2', k3='v3')),
497                 ((), ('someval',)),
498                 (dict(), dict(success=400), dict(k='v', v='k'))):
499             args, kwargs = pm[-2:]
500             pm = pm[:-2]
501             terms = [None] * 13
502             for i in range(len(terms)):
503                 if randint(0, 2):
504                     terms[i] = 'val_%s' % randint(13, 1024)
505             self.client.object_put(
506                 obj,
507                 *(pm[:3] + tuple(terms) + pm[3:] + args),
508                 **kwargs)
509             format, hashmap, delimiter = pm[:3]
510             self.assertEqual(SP.mock_calls[-3:], [
511                 call('format', format, iff=format),
512                 call('hashmap', hashmap, iff=hashmap),
513                 call('delimiter', delimiter, iff=delimiter)])
514             (
515                 im, inm, etag, clen, ctype, trenc,
516                 cp, mv, srcacc, srcvrs, conenc, condis, mnf) = terms
517             perms, public, metas = pm[3:]
518             exp = [
519                 call('If-Match', im),
520                 call('If-None-Match', inm),
521                 call('ETag', etag),
522                 call('Content-Length', clen),
523                 call('Content-Type', ctype),
524                 call('Transfer-Encoding', trenc),
525                 call('X-Copy-From', cp),
526                 call('X-Move-From', mv),
527                 call('X-Source-Account', srcacc),
528                 call('X-Source-Version', srcvrs),
529                 call('Content-Encoding', conenc),
530                 call('Content-Disposition', condis),
531                 call('X-Object-Manifest', mnf)]
532             if perms:
533                 perm_str = ''
534                 for ptype, pval in perms.items():
535                     if pval:
536                         perm_str += ';' if perm_str else ''
537                         perm_str += '%s=%s' % (ptype, ','.join(pval))
538                 exp += [call('X-Object-Sharing', perm_str)]
539             exp += [call('X-Object-Public', public)]
540             for k, v in metas.items():
541                 exp += [call('X-Object-Meta-%s' % k, v)]
542             self.assertEqual(SH.mock_calls[- len(exp):], exp)
543             acc, cont = self.client.account, self.client.container
544             self.assertEqual(put.mock_calls[-1], call(
545                 '/%s/%s/%s' % (acc, cont, obj),
546                 *args,
547                 success=kwargs.pop('success', 201),
548                 **kwargs))
549
550     @patch('%s.set_param' % rest_pkg)
551     @patch('%s.set_header' % rest_pkg)
552     @patch('%s.copy' % rest_pkg, return_value=FR())
553     def test_object_copy(self, copy, SH, SP):
554         dest = 'dest1n4710n'
555         for pm in product(
556                 ('json', 'f0rm47'),
557                 (False, True),
558                 (None, 'ifmatch'),
559                 (None, 'ifnonematch'),
560                 (None, 'destinationaccount'),
561                 (None, 'content-type'),
562                 (None, 'content-encoding'),
563                 (None, 'content-disp'),
564                 (None, 'source-version'),
565                 (dict(), dict(read=['u1', 'g2'], write=['u1'])),
566                 (False, True),
567                 (dict(), dict(k2='v2', k3='v3')),
568                 ((), ('someval',)),
569                 (dict(), dict(success=400), dict(k='v', v='k'))):
570             args, kwargs = pm[-2:]
571             pm = pm[:-2]
572             self.client.object_copy(obj, dest, *(pm + args), **kwargs)
573             format, ict = pm[:2]
574             self.assertEqual(SP.mock_calls[-2:], [
575                 call('format', format, iff=format),
576                 call('ignore_content_type', iff=ict)])
577             im, inm, da, ct, ce, cd, sv, perms, public, metas = pm[2:]
578             exp = [call('If-Match', im),
579                 call('If-None-Match', inm),
580                 call('Destination', dest),
581                 call('Destination-Account', da),
582                 call('Content-Type', ct),
583                 call('Content-Encoding', ce),
584                 call('Content-Disposition', cd),
585                 call('X-Source-Version', sv)]
586             if perms:
587                 perm_str = ''
588                 for ptype, pval in perms.items():
589                     if pval:
590                         perm_str += ';' if perm_str else ''
591                         perm_str += '%s=%s' % (ptype, ','.join(pval))
592                 exp += [call('X-Object-Sharing', perm_str)]
593             exp += [call('X-Object-Public', public)]
594             for k, v in metas.items():
595                 exp += [call('X-Object-Meta-%s' % k, v)]
596             self.assertEqual(SH.mock_calls[- len(exp):], exp)
597             acc, cont = self.client.account, self.client.container
598             self.assertEqual(copy.mock_calls[-1], call(
599                 '/%s/%s/%s' % (acc, cont, obj),
600                 *args,
601                 success=kwargs.pop('success', 201),
602                 **kwargs))
603
604     @patch('%s.set_param' % rest_pkg)
605     @patch('%s.set_header' % rest_pkg)
606     @patch('%s.move' % rest_pkg, return_value=FR())
607     def test_object_move(self, move, SH, SP):
608         for pm in product(
609                 ('json', 'f0rm47'),
610                 (False, True),
611                 (None, 'ifmatch'),
612                 (None, 'ifnonematch'),
613                 (None, 'destination'),
614                 (None, 'destinationaccount'),
615                 (None, 'content-type'),
616                 (None, 'content-encoding'),
617                 (None, 'content-disp'),
618                 (dict(), dict(read=['u1', 'g2'], write=['u1'])),
619                 (False, True),
620                 (dict(), dict(k2='v2', k3='v3')),
621                 ((), ('someval',)),
622                 (dict(), dict(success=400), dict(k='v', v='k'))):
623             args, kwargs = pm[-2:]
624             pm = pm[:-2]
625             self.client.object_move(obj, *(pm + args), **kwargs)
626             format, ict = pm[:2]
627             self.assertEqual(SP.mock_calls[-2:], [
628                 call('format', format, iff=format),
629                 call('ignore_content_type', iff=ict)])
630             im, inm, d, da, ct, ce, cd, perms, public, metas = pm[2:]
631             exp = [call('If-Match', im),
632                 call('If-None-Match', inm),
633                 call('Destination', d),
634                 call('Destination-Account', da),
635                 call('Content-Type', ct),
636                 call('Content-Encoding', ce),
637                 call('Content-Disposition', cd)]
638             if perms:
639                 perm_str = ''
640                 for ptype, pval in perms.items():
641                     if pval:
642                         perm_str += ';' if perm_str else ''
643                         perm_str += '%s=%s' % (ptype, ','.join(pval))
644                 exp += [call('X-Object-Sharing', perm_str)]
645             exp += [call('X-Object-Public', public)]
646             for k, v in metas.items():
647                 exp += [call('X-Object-Meta-%s' % k, v)]
648             self.assertEqual(SH.mock_calls[- len(exp):], exp)
649             acc, cont = self.client.account, self.client.container
650             self.assertEqual(move.mock_calls[-1], call(
651                 '/%s/%s/%s' % (acc, cont, obj),
652                 *args,
653                 success=kwargs.pop('success', 201),
654                 **kwargs))
655
656
657 class Pithos(TestCase):
658
659     files = []
660
661     def _create_temp_file(self, num_of_blocks):
662         self.files.append(NamedTemporaryFile())
663         tmpFile = self.files[-1]
664         file_size = num_of_blocks * 4 * 1024 * 1024
665         print('\n\tCreate tmp file')
666         tmpFile.write(urandom(file_size))
667         tmpFile.flush()
668         tmpFile.seek(0)
669         print('\t\tDone')
670         return tmpFile
671
672     def assert_dicts_are_equal(self, d1, d2):
673         for k, v in d1.items():
674             self.assertTrue(k in d2)
675             if isinstance(v, dict):
676                 self.assert_dicts_are_equal(v, d2[k])
677             else:
678                 self.assertEqual(unicode(v), unicode(d2[k]))
679
680     def setUp(self):
681         self.url = 'https://www.example.com/pithos'
682         self.token = 'p17h0570k3n'
683         self.client = PithosClient(self.url, self.token)
684         self.client.account = user_id
685         self.client.container = 'c0nt@1n3r_i'
686
687     def tearDown(self):
688         FR.headers = dict()
689         FR.status_code = 200
690         FR.json = dict()
691         FR.content = FR.json
692         for f in self.files:
693             f.close()
694
695     #  Pithos+ methods that extend storage API
696
697     @patch('%s.account_head' % pithos_pkg, return_value=FR())
698     def test_get_account_info(self, AH):
699         FR.headers = account_info
700         for until in (None, 'un71L-d473'):
701             r = self.client.get_account_info(until=until)
702             self.assert_dicts_are_equal(r, account_info)
703             self.assertEqual(AH.mock_calls[-1], call(until=until))
704         FR.status_code = 401
705         self.assertRaises(ClientError, self.client.get_account_info)
706
707     @patch('%s.account_post' % pithos_pkg, return_value=FR())
708     def test_del_account_meta(self, AP):
709         keys = ['k1', 'k2', 'k3']
710         for key in keys:
711             self.client.del_account_meta(key)
712             self.assertEqual(
713                 AP.mock_calls[-1],
714                 call(update=True, metadata={key: ''}))
715
716     @patch('%s.container_head' % pithos_pkg, return_value=FR())
717     def test_get_container_info(self, CH):
718         FR.headers = container_info
719         r = self.client.get_container_info()
720         self.assert_dicts_are_equal(r, container_info)
721         u = 'some date'
722         r = self.client.get_container_info(until=u)
723         self.assertEqual(CH.mock_calls, [call(until=None), call(until=u)])
724
725     @patch('%s.account_get' % pithos_pkg, return_value=FR())
726     def test_list_containers(self, get):
727         FR.json = container_list
728         r = self.client.list_containers()
729         get.assert_called_once_with()
730         for i in range(len(r)):
731             self.assert_dicts_are_equal(r[i], container_list[i])
732
733     @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
734     @patch('%s.container_post' % pithos_pkg, return_value=FR())
735     @patch('%s.object_put' % pithos_pkg, return_value=FR())
736     def test_upload_object(self, OP, CP, GCI):
737         num_of_blocks = 8
738         tmpFile = self._create_temp_file(num_of_blocks)
739
740         # Without kwargs
741         self.client.upload_object(obj, tmpFile)
742         self.assertEqual(GCI.mock_calls[-1], call())
743         [call1, call2] = OP.mock_calls
744
745         (args1, kwargs1) = call1[1:3]
746         (args2, kwargs2) = call2[1:3]
747         self.assertEqual(args1, (obj,))
748         expected1 = dict(
749             hashmap=True,
750             success=(201, 409),
751             format='json',
752             json=dict(
753                 hashes=['s0m3h@5h'] * num_of_blocks,
754                 bytes=num_of_blocks * 4 * 1024 * 1024),
755             etag=None,
756             content_encoding=None,
757             content_type='application/octet-stream',
758             content_disposition=None,
759             public=None,
760             permissions=None)
761         for k, v in expected1.items():
762             if k == 'json':
763                 self.assertEqual(len(v['hashes']), len(kwargs1[k]['hashes']))
764                 self.assertEqual(v['bytes'], kwargs1[k]['bytes'])
765             else:
766                 self.assertEqual(v, kwargs1[k])
767
768         (args2, kwargs2) = call2[1:3]
769         self.assertEqual(args2, (obj,))
770         expected2 = dict(
771             json=dict(
772                 hashes=['s0m3h@5h'] * num_of_blocks,
773                 bytes=num_of_blocks * 4 * 1024 * 1024),
774             content_type='application/octet-stream',
775             hashmap=True,
776             success=201,
777             format='json')
778         for k, v in expected2.items():
779             if k == 'json':
780                 self.assertEqual(len(v['hashes']), len(kwargs2[k]['hashes']))
781                 self.assertEqual(v['bytes'], kwargs2[k]['bytes'])
782             else:
783                 self.assertEqual(v, kwargs2[k])
784
785         mock_offset = 2
786
787         #  With progress bars
788         try:
789             from progress.bar import ShadyBar
790             blck_bar = ShadyBar('Mock blck calc.')
791             upld_bar = ShadyBar('Mock uplds')
792         except ImportError:
793             blck_bar = None
794             upld_bar = None
795
796         if blck_bar and upld_bar:
797
798             def blck_gen(n):
799                 for i in blck_bar.iter(range(n)):
800                     yield
801                 yield
802
803             def upld_gen(n):
804                 for i in upld_bar.iter(range(n)):
805                     yield
806                 yield
807
808             tmpFile.seek(0)
809             self.client.upload_object(
810                 obj, tmpFile,
811                 hash_cb=blck_gen, upload_cb=upld_gen)
812
813             for i, c in enumerate(OP.mock_calls[-mock_offset:]):
814                 self.assertEqual(OP.mock_calls[i], c)
815
816         #  With content-type
817         tmpFile.seek(0)
818         ctype = 'video/mpeg'
819         sharing = dict(read=['u1', 'g1', 'u2'], write=['u1'])
820         self.client.upload_object(obj, tmpFile,
821             content_type=ctype, sharing=sharing)
822         self.assertEqual(OP.mock_calls[-1][2]['content_type'], ctype)
823         self.assert_dicts_are_equal(
824             OP.mock_calls[-2][2]['permissions'],
825             sharing)
826
827         # With other args
828         tmpFile.seek(0)
829         kwargs = dict(
830             etag='s0m3E74g',
831             content_type=ctype,
832             content_disposition=ctype + 'd15p051710n',
833             public=True,
834             content_encoding='802.11')
835         self.client.upload_object(obj, tmpFile, **kwargs)
836         for arg, val in kwargs.items():
837             self.assertEqual(OP.mock_calls[-2][2][arg], val)
838
839     def test_get_object_info(self):
840         FR.headers = object_info
841         version = 'v3r510n'
842         with patch.object(
843                 PithosClient, 'object_head',
844                 return_value=FR()) as head:
845             r = self.client.get_object_info(obj)
846             self.assertEqual(r, object_info)
847             r = self.client.get_object_info(obj, version=version)
848             self.assertEqual(head.mock_calls, [
849                 call(obj, version=None),
850                 call(obj, version=version)])
851         with patch.object(
852                 PithosClient, 'object_head',
853                 side_effect=ClientError('Obj not found', 404)):
854             self.assertRaises(
855                 ClientError,
856                 self.client.get_object_info,
857                 obj, version=version)
858
859     @patch('%s.get_object_info' % pithos_pkg, return_value=object_info)
860     def test_get_object_meta(self, GOI):
861         for version in (None, 'v3r510n'):
862             r = self.client.get_object_meta(obj, version)
863             for k in [k for k in object_info if k.startswith('x-object-meta')]:
864                 self.assertEqual(r.pop(k), object_info[k])
865             self.assertFalse(len(r))
866             self.assertEqual(GOI.mock_calls[-1], call(obj, version=version))
867
868     @patch('%s.object_post' % pithos_pkg, return_value=FR())
869     def test_del_object_meta(self, post):
870         metakey = '50m3m3t4k3y'
871         self.client.del_object_meta(obj, metakey)
872         post.assert_called_once_with(obj, update=True, metadata={metakey: ''})
873
874     @patch('%s.object_put' % pithos_pkg, return_value=FR())
875     def test_copy_object(self, put):
876         src_cont = 'src-c0nt41n3r'
877         src_obj = 'src-0bj'
878         dst_cont = 'dst-c0nt41n3r'
879         dst_obj = 'dst-0bj'
880         expected = call(
881             src_obj,
882             content_length=0,
883             source_account=None,
884             success=201,
885             copy_from='/%s/%s' % (src_cont, src_obj),
886             delimiter=None,
887             content_type=None,
888             source_version=None,
889             public=False)
890         self.client.copy_object(src_cont, src_obj, dst_cont)
891         self.assertEqual(put.mock_calls[-1], expected)
892         self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
893         self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
894         kwargs = dict(
895             source_version='src-v3r510n',
896             source_account='src-4cc0un7',
897             public=True,
898             content_type='c0n73n7Typ3',
899             delimiter='5')
900         self.client.copy_object(src_cont, src_obj, dst_cont, **kwargs)
901         for k, v in kwargs.items():
902             self.assertEqual(v, put.mock_calls[-1][2][k])
903
904     @patch('%s.object_put' % pithos_pkg, return_value=FR())
905     def test_move_object(self, put):
906         src_cont = 'src-c0nt41n3r'
907         src_obj = 'src-0bj'
908         dst_cont = 'dst-c0nt41n3r'
909         dst_obj = 'dst-0bj'
910         expected = call(
911             src_obj,
912             content_length=0,
913             source_account=None,
914             success=201,
915             move_from='/%s/%s' % (src_cont, src_obj),
916             delimiter=None,
917             content_type=None,
918             source_version=None,
919             public=False)
920         self.client.move_object(src_cont, src_obj, dst_cont)
921         self.assertEqual(put.mock_calls[-1], expected)
922         self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
923         self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
924         kwargs = dict(
925             source_version='src-v3r510n',
926             source_account='src-4cc0un7',
927             public=True,
928             content_type='c0n73n7Typ3',
929             delimiter='5')
930         self.client.move_object(src_cont, src_obj, dst_cont, **kwargs)
931         for k, v in kwargs.items():
932             self.assertEqual(v, put.mock_calls[-1][2][k])
933
934     #  Pithos+ only methods
935
936     @patch('%s.container_delete' % pithos_pkg, return_value=FR())
937     def test_purge_container(self, CD):
938         self.client.purge_container()
939         self.assertTrue('until' in CD.mock_calls[-1][2])
940         cont = self.client.container
941         self.client.purge_container('another-container')
942         self.assertEqual(self.client.container, cont)
943
944     @patch('%s.object_put' % pithos_pkg, return_value=FR())
945     def test_upload_object_unchunked(self, put):
946         num_of_blocks = 8
947         tmpFile = self._create_temp_file(num_of_blocks)
948         expected = dict(
949                 success=201,
950                 data=num_of_blocks * 4 * 1024 * 1024,
951                 etag='some-etag',
952                 content_encoding='some content_encoding',
953                 content_type='some content-type',
954                 content_disposition='some content_disposition',
955                 public=True,
956                 permissions=dict(read=['u1', 'g1', 'u2'], write=['u1']))
957         self.client.upload_object_unchunked(obj, tmpFile)
958         self.assertEqual(put.mock_calls[-1][1], (obj,))
959         self.assertEqual(
960             sorted(put.mock_calls[-1][2].keys()),
961             sorted(expected.keys()))
962         kwargs = dict(expected)
963         kwargs.pop('success')
964         kwargs['size'] = kwargs.pop('data')
965         kwargs['sharing'] = kwargs.pop('permissions')
966         tmpFile.seek(0)
967         self.client.upload_object_unchunked(obj, tmpFile, **kwargs)
968         pmc = put.mock_calls[-1][2]
969         for k, v in expected.items():
970             if k == 'data':
971                 self.assertEqual(len(pmc[k]), v)
972             else:
973                 self.assertEqual(pmc[k], v)
974         self.assertRaises(
975             ClientError,
976             self.client.upload_object_unchunked,
977             obj, tmpFile, withHashFile=True)
978
979     @patch('%s.object_put' % pithos_pkg, return_value=FR())
980     def test_create_object_by_manifestation(self, put):
981         manifest = '%s/%s' % (self.client.container, obj)
982         kwargs = dict(
983                 etag='some-etag',
984                 content_encoding='some content_encoding',
985                 content_type='some content-type',
986                 content_disposition='some content_disposition',
987                 public=True,
988                 sharing=dict(read=['u1', 'g1', 'u2'], write=['u1']))
989         self.client.create_object_by_manifestation(obj)
990         expected = dict(content_length=0, manifest=manifest)
991         for k in kwargs:
992             expected['permissions' if k == 'sharing' else k] = None
993         self.assertEqual(put.mock_calls[-1], call(obj, **expected))
994         self.client.create_object_by_manifestation(obj, **kwargs)
995         expected.update(kwargs)
996         expected['permissions'] = expected.pop('sharing')
997         self.assertEqual(put.mock_calls[-1], call(obj, **expected))
998
999     @patch('%s.get_object_hashmap' % pithos_pkg, return_value=object_hashmap)
1000     @patch('%s.object_get' % pithos_pkg, return_value=FR())
1001     def test_download_object(self, GET, GOH):
1002         num_of_blocks = 8
1003         tmpFile = self._create_temp_file(num_of_blocks)
1004         FR.content = tmpFile.read(4 * 1024 * 1024)
1005         tmpFile = self._create_temp_file(num_of_blocks)
1006         num_of_blocks = len(object_hashmap['hashes'])
1007         kwargs = dict(
1008             resume=True,
1009             version='version',
1010             range_str='10-20',
1011             if_match='if and only if',
1012             if_none_match='if and only not',
1013             if_modified_since='what if not?',
1014             if_unmodified_since='this happens if not!',
1015             async_headers=dict(Range='bytes=0-88888888'))
1016
1017         self.client.download_object(obj, tmpFile)
1018         self.assertEqual(len(GET.mock_calls), num_of_blocks)
1019         self.assertEqual(GET.mock_calls[-1][1], (obj,))
1020         for k, v in kwargs.items():
1021             if k == 'async_headers':
1022                 self.assertTrue('Range' in GET.mock_calls[-1][2][k])
1023             elif k in ('resume', 'range_str'):
1024                 continue
1025             else:
1026                 self.assertEqual(GET.mock_calls[-1][2][k], None)
1027
1028         #  Check ranges are consecutive
1029         starts = []
1030         ends = []
1031         for c in GET.mock_calls:
1032             rng_str = c[2]['async_headers']['Range']
1033             (start, rng_str) = rng_str.split('=')
1034             (start, end) = rng_str.split('-')
1035             starts.append(start)
1036             ends.append(end)
1037         ends = sorted(ends)
1038         for i, start in enumerate(sorted(starts)):
1039             if i:
1040                 int(ends[i - 1]) == int(start) - 1
1041
1042         #  With progress bars
1043         try:
1044             from progress.bar import ShadyBar
1045             dl_bar = ShadyBar('Mock dl')
1046         except ImportError:
1047             dl_bar = None
1048
1049         if dl_bar:
1050
1051             def blck_gen(n):
1052                 for i in dl_bar.iter(range(n)):
1053                     yield
1054                 yield
1055
1056             tmpFile.seek(0)
1057             self.client.download_object(obj, tmpFile, download_cb=blck_gen)
1058             self.assertEqual(len(GET.mock_calls), 2 * num_of_blocks)
1059
1060         tmpFile.seek(0)
1061         kwargs.pop('async_headers')
1062         kwargs.pop('resume')
1063         self.client.download_object(obj, tmpFile, **kwargs)
1064         for k, v in kwargs.items():
1065             if k == 'range_str':
1066                 self.assertEqual(
1067                     GET.mock_calls[-1][2]['data_range'],
1068                     'bytes=%s' % v)
1069             else:
1070                 self.assertEqual(GET.mock_calls[-1][2][k], v)
1071
1072         #  ALl options on no tty
1073
1074         def foo():
1075             return True
1076
1077         tmpFile.seek(0)
1078         tmpFile.isatty = foo
1079         self.client.download_object(obj, tmpFile, **kwargs)
1080         for k, v in kwargs.items():
1081             if k == 'range_str':
1082                 self.assertTrue('data_range' in GET.mock_calls[-1][2])
1083             else:
1084                 self.assertEqual(GET.mock_calls[-1][2][k], v)
1085
1086     def test_get_object_hashmap(self):
1087         FR.json = object_hashmap
1088         for empty in (304, 412):
1089             with patch.object(
1090                     PithosClient, 'object_get',
1091                     side_effect=ClientError('Empty', status=empty)):
1092                 r = self.client.get_object_hashmap(obj)
1093                 self.assertEqual(r, {})
1094         exp_args = dict(
1095             hashmap=True,
1096             data_range=None,
1097             version=None,
1098             if_etag_match=None,
1099             if_etag_not_match=None,
1100             if_modified_since=None,
1101             if_unmodified_since=None)
1102         kwargs = dict(
1103             version='s0m3v3r51on',
1104             if_match='if match',
1105             if_none_match='if non match',
1106             if_modified_since='some date here',
1107             if_unmodified_since='some date here',
1108             data_range='10-20')
1109         with patch.object(
1110                 PithosClient, 'object_get',
1111                 return_value=FR()) as get:
1112             r = self.client.get_object_hashmap(obj)
1113             self.assertEqual(r, object_hashmap)
1114             self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
1115             r = self.client.get_object_hashmap(obj, **kwargs)
1116             exp_args['if_etag_match'] = kwargs.pop('if_match')
1117             exp_args['if_etag_not_match'] = kwargs.pop('if_none_match')
1118             exp_args.update(kwargs)
1119             self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
1120
1121     @patch('%s.account_post' % pithos_pkg, return_value=FR())
1122     def test_set_account_group(self, post):
1123         (group, usernames) = ('aU53rGr0up', ['u1', 'u2', 'u3'])
1124         self.client.set_account_group(group, usernames)
1125         post.assert_called_once_with(update=True, groups={group: usernames})
1126
1127     @patch('%s.account_post' % pithos_pkg, return_value=FR())
1128     def test_del_account_group(self, post):
1129         group = 'aU53rGr0up'
1130         self.client.del_account_group(group)
1131         post.assert_called_once_with(update=True, groups={group: []})
1132
1133     @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
1134     def test_get_account_quota(self, GAI):
1135         key = 'x-account-policy-quota'
1136         r = self.client.get_account_quota()
1137         GAI.assert_called_once_with()
1138         self.assertEqual(r[key], account_info[key])
1139
1140     @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
1141     def test_get_account_versioning(self, GAI):
1142         key = 'x-account-policy-versioning'
1143         r = self.client.get_account_versioning()
1144         GAI.assert_called_once_with()
1145         self.assertEqual(r[key], account_info[key])
1146
1147     def test_get_account_meta(self):
1148         key = 'x-account-meta-'
1149         with patch.object(
1150                 PithosClient, 'get_account_info',
1151                 return_value=account_info):
1152             r = self.client.get_account_meta()
1153             keys = [k for k in r if k.startswith(key)]
1154             self.assertFalse(keys)
1155         acc_info = dict(account_info)
1156         acc_info['%sk1' % key] = 'v1'
1157         acc_info['%sk2' % key] = 'v2'
1158         acc_info['%sk3' % key] = 'v3'
1159         with patch.object(
1160                 PithosClient, 'get_account_info',
1161                 return_value=acc_info):
1162             r = self.client.get_account_meta()
1163             for k in [k for k in acc_info if k.startswith(key)]:
1164                 self.assertEqual(r[k], acc_info[k])
1165
1166     def test_get_account_group(self):
1167         key = 'x-account-group-'
1168         with patch.object(
1169                 PithosClient, 'get_account_info',
1170                 return_value=account_info):
1171             r = self.client.get_account_group()
1172             keys = [k for k in r if k.startswith(key)]
1173             self.assertFalse(keys)
1174         acc_info = dict(account_info)
1175         acc_info['%sk1' % key] = 'g1'
1176         acc_info['%sk2' % key] = 'g2'
1177         acc_info['%sk3' % key] = 'g3'
1178         with patch.object(
1179                 PithosClient, 'get_account_info',
1180                 return_value=acc_info):
1181             r = self.client.get_account_group()
1182             for k in [k for k in acc_info if k.startswith(key)]:
1183                 self.assertEqual(r[k], acc_info[k])
1184
1185     @patch('%s.account_post' % pithos_pkg, return_value=FR())
1186     def test_set_account_meta(self, post):
1187         metas = dict(k1='v1', k2='v2', k3='v3')
1188         self.client.set_account_meta(metas)
1189         post.assert_called_once_with(update=True, metadata=metas)
1190
1191     @patch('%s.account_post' % pithos_pkg, return_value=FR())
1192     def test_set_account_quota(self, post):
1193         qu = 1024
1194         self.client.set_account_quota(qu)
1195         post.assert_called_once_with(update=True, quota=qu)
1196
1197     @patch('%s.account_post' % pithos_pkg, return_value=FR())
1198     def test_set_account_versioning(self, post):
1199         vrs = 'n3wV3r51on1ngTyp3'
1200         self.client.set_account_versioning(vrs)
1201         post.assert_called_once_with(update=True, versioning=vrs)
1202
1203     @patch('%s.container_delete' % pithos_pkg, return_value=FR())
1204     def test_del_container(self, delete):
1205         for kwarg in (
1206                 dict(delimiter=None, until=None),
1207                 dict(delimiter='X', until='50m3d473')):
1208             self.client.del_container(**kwarg)
1209             expected = dict(kwarg)
1210             expected['success'] = (204, 404, 409)
1211             self.assertEqual(delete.mock_calls[-1], call(**expected))
1212         for status_code in (404, 409):
1213             FR.status_code = status_code
1214             self.assertRaises(ClientError, self.client.del_container)
1215
1216     @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1217     def test_get_container_versioning(self, GCI):
1218         key = 'x-container-policy-versioning'
1219         cont = 'c0n7-417'
1220         bu_cnt = self.client.container
1221         for container in (None, cont):
1222             r = self.client.get_container_versioning(container=container)
1223             self.assertEqual(r[key], container_info[key])
1224             self.assertEqual(GCI.mock_calls[-1], call())
1225             self.assertEqual(bu_cnt, self.client.container)
1226
1227     @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1228     def test_get_container_quota(self, GCI):
1229         key = 'x-container-policy-quota'
1230         cont = 'c0n7-417'
1231         bu_cnt = self.client.container
1232         for container in (None, cont):
1233             r = self.client.get_container_quota(container=container)
1234             self.assertEqual(r[key], container_info[key])
1235             self.assertEqual(GCI.mock_calls[-1], call())
1236             self.assertEqual(bu_cnt, self.client.container)
1237
1238     def test_get_container_meta(self):
1239         somedate = '50m3d473'
1240         key = 'x-container-meta'
1241         metaval = '50m3m374v41'
1242         container_plus = dict(container_info)
1243         container_plus[key] = metaval
1244         for ret in ((container_info, {}), (container_plus, {key: metaval})):
1245             with patch.object(
1246                     PithosClient,
1247                     'get_container_info',
1248                     return_value=ret[0]) as GCI:
1249                 for until in (None, somedate):
1250                     r = self.client.get_container_meta(until=until)
1251                     self.assertEqual(r, ret[1])
1252                     self.assertEqual(GCI.mock_calls[-1], call(until=until))
1253
1254     def test_get_container_object_meta(self):
1255         somedate = '50m3d473'
1256         key = 'x-container-object-meta'
1257         metaval = '50m3m374v41'
1258         container_plus = dict(container_info)
1259         container_plus[key] = metaval
1260         for ret in (
1261                 (container_info, {key: ''}),
1262                 (container_plus, {key: metaval})):
1263             with patch.object(
1264                     PithosClient,
1265                     'get_container_info',
1266                     return_value=ret[0]) as GCI:
1267                 for until in (None, somedate):
1268                     r = self.client.get_container_object_meta(until=until)
1269                     self.assertEqual(r, ret[1])
1270                     self.assertEqual(GCI.mock_calls[-1], call(until=until))
1271
1272     @patch('%s.container_post' % pithos_pkg, return_value=FR())
1273     def test_set_container_meta(self, post):
1274         metas = dict(k1='v1', k2='v2', k3='v3')
1275         self.client.set_container_meta(metas)
1276         post.assert_called_once_with(update=True, metadata=metas)
1277
1278     @patch('%s.container_post' % pithos_pkg, return_value=FR())
1279     def test_del_container_meta(self, AP):
1280         self.client.del_container_meta('somekey')
1281         AP.assert_called_once_with(update=True, metadata={'somekey': ''})
1282
1283     @patch('%s.container_post' % pithos_pkg, return_value=FR())
1284     def test_set_container_quota(self, post):
1285         qu = 1024
1286         self.client.set_container_quota(qu)
1287         post.assert_called_once_with(update=True, quota=qu)
1288
1289     @patch('%s.container_post' % pithos_pkg, return_value=FR())
1290     def test_set_container_versioning(self, post):
1291         vrs = 'n3wV3r51on1ngTyp3'
1292         self.client.set_container_versioning(vrs)
1293         post.assert_called_once_with(update=True, versioning=vrs)
1294
1295     @patch('%s.object_delete' % pithos_pkg, return_value=FR())
1296     def test_del_object(self, delete):
1297         for kwarg in (
1298                 dict(delimiter=None, until=None),
1299                 dict(delimiter='X', until='50m3d473')):
1300             self.client.del_object(obj, **kwarg)
1301             self.assertEqual(delete.mock_calls[-1], call(obj, **kwarg))
1302
1303     @patch('%s.object_post' % pithos_pkg, return_value=FR())
1304     def test_set_object_meta(self, post):
1305         metas = dict(k1='v1', k2='v2', k3='v3')
1306         self.assertRaises(
1307             AssertionError,
1308             self.client.set_object_meta,
1309             obj, 'Non dict arg')
1310         self.client.set_object_meta(obj, metas)
1311         post.assert_called_once_with(obj, update=True, metadata=metas)
1312
1313     @patch('%s.object_post' % pithos_pkg, return_value=FR())
1314     def test_publish_object(self, post):
1315         oinfo = dict(object_info)
1316         val = 'pubL1c'
1317         oinfo['x-object-public'] = val
1318         with patch.object(
1319                 PithosClient, 'get_object_info',
1320                 return_value=oinfo) as GOF:
1321             r = self.client.publish_object(obj)
1322             self.assertEqual(
1323                 post.mock_calls[-1],
1324                 call(obj, public=True, update=True))
1325             self.assertEqual(GOF.mock_calls[-1], call(obj))
1326             self.assertEqual(r, '%s%s' % (self.url[:-6], val))
1327
1328     @patch('%s.object_post' % pithos_pkg, return_value=FR())
1329     def test_unpublish_object(self, post):
1330         self.client.unpublish_object(obj)
1331         post.assert_called_once_with(obj, public=False, update=True)
1332
1333     def test_get_object_sharing(self):
1334         info = dict(object_info)
1335         expected = dict(read='u1,g1,u2', write='u1')
1336         info['x-object-sharing'] = '; '.join(
1337             ['%s=%s' % (k, v) for k, v in expected.items()])
1338         with patch.object(
1339                 PithosClient, 'get_object_info',
1340                 return_value=info) as GOF:
1341             r = self.client.get_object_sharing(obj)
1342             self.assertEqual(GOF.mock_calls[-1], call(obj))
1343             self.assert_dicts_are_equal(r, expected)
1344             info['x-object-sharing'] = '//'.join(
1345                 ['%s=%s' % (k, v) for k, v in expected.items()])
1346             self.assertRaises(
1347                 ValueError,
1348                 self.client.get_object_sharing,
1349                 obj)
1350             info['x-object-sharing'] = '; '.join(
1351                 ['%s:%s' % (k, v) for k, v in expected.items()])
1352             self.assertRaises(
1353                 ClientError,
1354                 self.client.get_object_sharing,
1355                 obj)
1356             info['x-object-sharing'] = 'read=%s' % expected['read']
1357             r = self.client.get_object_sharing(obj)
1358             expected.pop('write')
1359             self.assert_dicts_are_equal(r, expected)
1360
1361     @patch('%s.object_post' % pithos_pkg, return_value=FR())
1362     def test_set_object_sharing(self, OP):
1363         read_perms = ['u1', 'g1', 'u2', 'g2']
1364         write_perms = ['u1', 'g1']
1365         for kwargs in (
1366                 dict(read_permition=read_perms, write_permition=write_perms),
1367                 dict(read_permition=read_perms),
1368                 dict(write_permition=write_perms),
1369                 dict()):
1370             self.client.set_object_sharing(obj, **kwargs)
1371             kwargs['read'] = kwargs.pop('read_permition', '')
1372             kwargs['write'] = kwargs.pop('write_permition', '')
1373             self.assertEqual(
1374                 OP.mock_calls[-1],
1375                 call(obj, update=True, permissions=kwargs))
1376
1377     @patch('%s.set_object_sharing' % pithos_pkg)
1378     def test_del_object_sharing(self, SOS):
1379         self.client.del_object_sharing(obj)
1380         SOS.assert_called_once_with(obj)
1381
1382     @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1383     @patch('%s.object_post' % pithos_pkg, return_value=FR())
1384     def test_append_object(self, post, GCI):
1385         num_of_blocks = 4
1386         tmpFile = self._create_temp_file(num_of_blocks)
1387         tmpFile.seek(0, 2)
1388         file_size = tmpFile.tell()
1389         for turn in range(2):
1390             tmpFile.seek(0, 0)
1391
1392             try:
1393                 from progress.bar import ShadyBar
1394                 apn_bar = ShadyBar('Mock append')
1395             except ImportError:
1396                 apn_bar = None
1397
1398             if apn_bar:
1399
1400                 def append_gen(n):
1401                     for i in apn_bar.iter(range(n)):
1402                         yield
1403                     yield
1404
1405             else:
1406                 append_gen = None
1407
1408             self.client.append_object(
1409                 obj, tmpFile,
1410                 upload_cb=append_gen if turn else None)
1411             self.assertEqual((turn + 1) * num_of_blocks, len(post.mock_calls))
1412             (args, kwargs) = post.mock_calls[-1][1:3]
1413             self.assertEqual(args, (obj,))
1414             self.assertEqual(kwargs['content_length'], len(kwargs['data']))
1415             fsize = num_of_blocks * int(kwargs['content_length'])
1416             self.assertEqual(fsize, file_size)
1417             self.assertEqual(kwargs['content_range'], 'bytes */*')
1418             exp = 'application/octet-stream'
1419             self.assertEqual(kwargs['content_type'], exp)
1420             self.assertEqual(kwargs['update'], True)
1421
1422     @patch('%s.object_post' % pithos_pkg, return_value=FR())
1423     def test_truncate_object(self, post):
1424         upto_bytes = 377
1425         self.client.truncate_object(obj, upto_bytes)
1426         post.assert_called_once_with(
1427             obj,
1428             update=True,
1429             object_bytes=upto_bytes,
1430             content_range='bytes 0-%s/*' % upto_bytes,
1431             content_type='application/octet-stream',
1432             source_object='/%s/%s' % (self.client.container, obj))
1433
1434     @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1435     @patch('%s.object_post' % pithos_pkg, return_value=FR())
1436     def test_overwrite_object(self, post, GCI):
1437         num_of_blocks = 4
1438         tmpFile = self._create_temp_file(num_of_blocks)
1439         tmpFile.seek(0, 2)
1440         file_size = tmpFile.tell()
1441         info = dict(object_info)
1442         info['content-length'] = file_size
1443         block_size = container_info['x-container-block-size']
1444         with patch.object(
1445                 PithosClient, 'get_object_info',
1446                 return_value=info) as GOI:
1447             for start, end in (
1448                     (0, file_size + 1),
1449                     (file_size + 1, file_size + 2)):
1450                 tmpFile.seek(0, 0)
1451                 self.assertRaises(
1452                     ClientError,
1453                     self.client.overwrite_object,
1454                     obj, start, end, tmpFile)
1455             for start, end in ((0, 144), (144, 233), (233, file_size)):
1456                 tmpFile.seek(0, 0)
1457                 owr_gen = None
1458                 exp_size = end - start + 1
1459                 if not start or exp_size > block_size:
1460                     try:
1461                         from progress.bar import ShadyBar
1462                         owr_bar = ShadyBar('Mock append')
1463                     except ImportError:
1464                         owr_bar = None
1465
1466                     if owr_bar:
1467
1468                         def owr_gen(n):
1469                             for i in owr_bar.iter(range(n)):
1470                                 yield
1471                             yield
1472
1473                     if exp_size > block_size:
1474                         exp_size = exp_size % block_size or block_size
1475
1476                 self.client.overwrite_object(obj, start, end, tmpFile, owr_gen)
1477                 self.assertEqual(GOI.mock_calls[-1], call(obj))
1478                 self.assertEqual(GCI.mock_calls[-1], call())
1479                 (args, kwargs) = post.mock_calls[-1][1:3]
1480                 self.assertEqual(args, (obj,))
1481                 self.assertEqual(len(kwargs['data']), exp_size)
1482                 self.assertEqual(kwargs['content_length'], exp_size)
1483                 self.assertEqual(kwargs['update'], True)
1484                 exp = 'application/octet-stream'
1485                 self.assertEqual(kwargs['content_type'], exp)
1486
1487     @patch('%s.set_param' % pithos_pkg)
1488     @patch('%s.get' % pithos_pkg, return_value=FR())
1489     def test_get_sharing_accounts(self, get, SP):
1490         FR.json = sharers
1491         for kws in (
1492                 dict(),
1493                 dict(limit='50m3-11m17'),
1494                 dict(marker='X'),
1495                 dict(limit='50m3-11m17', marker='X')):
1496             r = self.client.get_sharing_accounts(**kws)
1497             self.assertEqual(get.mock_calls[-1], call('', success=(200, 204)))
1498             self.assertEqual(SP.mock_calls[-3], call('format', 'json'))
1499             limit, marker = kws.get('limit', None), kws.get('marker', None)
1500             self.assertEqual(SP.mock_calls[-2], call(
1501                 'limit', limit,
1502                 iff=limit is not None))
1503             self.assertEqual(SP.mock_calls[-1], call(
1504                 'marker', marker,
1505                 iff=marker is not None))
1506             for i in range(len(r)):
1507                 self.assert_dicts_are_equal(r[i], sharers[i])
1508
1509     @patch('%s.object_get' % pithos_pkg, return_value=FR())
1510     def test_get_object_versionlist(self, get):
1511         info = dict(object_info)
1512         info['versions'] = ['v1', 'v2']
1513         FR.json = info
1514         r = self.client.get_object_versionlist(obj)
1515         get.assert_called_once_with(obj, format='json', version='list')
1516         self.assertEqual(r, info['versions'])
1517
1518 if __name__ == '__main__':
1519     from sys import argv
1520     from kamaki.clients.test import runTestCase
1521     not_found = True
1522     if not argv[1:] or argv[1] == 'Pithos':
1523         not_found = False
1524         runTestCase(Pithos, 'Pithos Client', argv[2:])
1525     if not argv[1:] or argv[1] == 'PithosRest':
1526         not_found = False
1527         runTestCase(PithosRest, 'PithosRest Client', argv[2:])
1528     if not_found:
1529         print('TestCase %s not found' % argv[1])