49725952d44077087acf3ceafd5e4760100df1ae
[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
605 class Pithos(TestCase):
606
607     files = []
608
609     def _create_temp_file(self, num_of_blocks):
610         self.files.append(NamedTemporaryFile())
611         tmpFile = self.files[-1]
612         file_size = num_of_blocks * 4 * 1024 * 1024
613         print('\n\tCreate tmp file')
614         tmpFile.write(urandom(file_size))
615         tmpFile.flush()
616         tmpFile.seek(0)
617         print('\t\tDone')
618         return tmpFile
619
620     def assert_dicts_are_equal(self, d1, d2):
621         for k, v in d1.items():
622             self.assertTrue(k in d2)
623             if isinstance(v, dict):
624                 self.assert_dicts_are_equal(v, d2[k])
625             else:
626                 self.assertEqual(unicode(v), unicode(d2[k]))
627
628     def setUp(self):
629         self.url = 'https://www.example.com/pithos'
630         self.token = 'p17h0570k3n'
631         self.client = PithosClient(self.url, self.token)
632         self.client.account = user_id
633         self.client.container = 'c0nt@1n3r_i'
634
635     def tearDown(self):
636         FR.headers = dict()
637         FR.status_code = 200
638         FR.json = dict()
639         FR.content = FR.json
640         for f in self.files:
641             f.close()
642
643     #  Pithos+ methods that extend storage API
644
645     @patch('%s.account_head' % pithos_pkg, return_value=FR())
646     def test_get_account_info(self, AH):
647         FR.headers = account_info
648         for until in (None, 'un71L-d473'):
649             r = self.client.get_account_info(until=until)
650             self.assert_dicts_are_equal(r, account_info)
651             self.assertEqual(AH.mock_calls[-1], call(until=until))
652         FR.status_code = 401
653         self.assertRaises(ClientError, self.client.get_account_info)
654
655     @patch('%s.account_post' % pithos_pkg, return_value=FR())
656     def test_del_account_meta(self, AP):
657         keys = ['k1', 'k2', 'k3']
658         for key in keys:
659             self.client.del_account_meta(key)
660             self.assertEqual(
661                 AP.mock_calls[-1],
662                 call(update=True, metadata={key: ''}))
663
664     @patch('%s.container_head' % pithos_pkg, return_value=FR())
665     def test_get_container_info(self, CH):
666         FR.headers = container_info
667         r = self.client.get_container_info()
668         self.assert_dicts_are_equal(r, container_info)
669         u = 'some date'
670         r = self.client.get_container_info(until=u)
671         self.assertEqual(CH.mock_calls, [call(until=None), call(until=u)])
672
673     @patch('%s.account_get' % pithos_pkg, return_value=FR())
674     def test_list_containers(self, get):
675         FR.json = container_list
676         r = self.client.list_containers()
677         get.assert_called_once_with()
678         for i in range(len(r)):
679             self.assert_dicts_are_equal(r[i], container_list[i])
680
681     @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
682     @patch('%s.container_post' % pithos_pkg, return_value=FR())
683     @patch('%s.object_put' % pithos_pkg, return_value=FR())
684     def test_upload_object(self, OP, CP, GCI):
685         num_of_blocks = 8
686         tmpFile = self._create_temp_file(num_of_blocks)
687
688         # Without kwargs
689         self.client.upload_object(obj, tmpFile)
690         self.assertEqual(GCI.mock_calls[-1], call())
691         [call1, call2] = OP.mock_calls
692
693         (args1, kwargs1) = call1[1:3]
694         (args2, kwargs2) = call2[1:3]
695         self.assertEqual(args1, (obj,))
696         expected1 = dict(
697             hashmap=True,
698             success=(201, 409),
699             format='json',
700             json=dict(
701                 hashes=['s0m3h@5h'] * num_of_blocks,
702                 bytes=num_of_blocks * 4 * 1024 * 1024),
703             etag=None,
704             content_encoding=None,
705             content_type='application/octet-stream',
706             content_disposition=None,
707             public=None,
708             permissions=None)
709         for k, v in expected1.items():
710             if k == 'json':
711                 self.assertEqual(len(v['hashes']), len(kwargs1[k]['hashes']))
712                 self.assertEqual(v['bytes'], kwargs1[k]['bytes'])
713             else:
714                 self.assertEqual(v, kwargs1[k])
715
716         (args2, kwargs2) = call2[1:3]
717         self.assertEqual(args2, (obj,))
718         expected2 = dict(
719             json=dict(
720                 hashes=['s0m3h@5h'] * num_of_blocks,
721                 bytes=num_of_blocks * 4 * 1024 * 1024),
722             content_type='application/octet-stream',
723             hashmap=True,
724             success=201,
725             format='json')
726         for k, v in expected2.items():
727             if k == 'json':
728                 self.assertEqual(len(v['hashes']), len(kwargs2[k]['hashes']))
729                 self.assertEqual(v['bytes'], kwargs2[k]['bytes'])
730             else:
731                 self.assertEqual(v, kwargs2[k])
732
733         mock_offset = 2
734
735         #  With progress bars
736         try:
737             from progress.bar import ShadyBar
738             blck_bar = ShadyBar('Mock blck calc.')
739             upld_bar = ShadyBar('Mock uplds')
740         except ImportError:
741             blck_bar = None
742             upld_bar = None
743
744         if blck_bar and upld_bar:
745
746             def blck_gen(n):
747                 for i in blck_bar.iter(range(n)):
748                     yield
749                 yield
750
751             def upld_gen(n):
752                 for i in upld_bar.iter(range(n)):
753                     yield
754                 yield
755
756             tmpFile.seek(0)
757             self.client.upload_object(
758                 obj, tmpFile,
759                 hash_cb=blck_gen, upload_cb=upld_gen)
760
761             for i, c in enumerate(OP.mock_calls[-mock_offset:]):
762                 self.assertEqual(OP.mock_calls[i], c)
763
764         #  With content-type
765         tmpFile.seek(0)
766         ctype = 'video/mpeg'
767         sharing = dict(read=['u1', 'g1', 'u2'], write=['u1'])
768         self.client.upload_object(obj, tmpFile,
769             content_type=ctype, sharing=sharing)
770         self.assertEqual(OP.mock_calls[-1][2]['content_type'], ctype)
771         self.assert_dicts_are_equal(
772             OP.mock_calls[-2][2]['permissions'],
773             sharing)
774
775         # With other args
776         tmpFile.seek(0)
777         kwargs = dict(
778             etag='s0m3E74g',
779             content_type=ctype,
780             content_disposition=ctype + 'd15p051710n',
781             public=True,
782             content_encoding='802.11')
783         self.client.upload_object(obj, tmpFile, **kwargs)
784         for arg, val in kwargs.items():
785             self.assertEqual(OP.mock_calls[-2][2][arg], val)
786
787     def test_get_object_info(self):
788         FR.headers = object_info
789         version = 'v3r510n'
790         with patch.object(
791                 PithosClient, 'object_head',
792                 return_value=FR()) as head:
793             r = self.client.get_object_info(obj)
794             self.assertEqual(r, object_info)
795             r = self.client.get_object_info(obj, version=version)
796             self.assertEqual(head.mock_calls, [
797                 call(obj, version=None),
798                 call(obj, version=version)])
799         with patch.object(
800                 PithosClient, 'object_head',
801                 side_effect=ClientError('Obj not found', 404)):
802             self.assertRaises(
803                 ClientError,
804                 self.client.get_object_info,
805                 obj, version=version)
806
807     @patch('%s.get_object_info' % pithos_pkg, return_value=object_info)
808     def test_get_object_meta(self, GOI):
809         for version in (None, 'v3r510n'):
810             r = self.client.get_object_meta(obj, version)
811             for k in [k for k in object_info if k.startswith('x-object-meta')]:
812                 self.assertEqual(r.pop(k), object_info[k])
813             self.assertFalse(len(r))
814             self.assertEqual(GOI.mock_calls[-1], call(obj, version=version))
815
816     @patch('%s.object_post' % pithos_pkg, return_value=FR())
817     def test_del_object_meta(self, post):
818         metakey = '50m3m3t4k3y'
819         self.client.del_object_meta(obj, metakey)
820         post.assert_called_once_with(obj, update=True, metadata={metakey: ''})
821
822     @patch('%s.object_put' % pithos_pkg, return_value=FR())
823     def test_copy_object(self, put):
824         src_cont = 'src-c0nt41n3r'
825         src_obj = 'src-0bj'
826         dst_cont = 'dst-c0nt41n3r'
827         dst_obj = 'dst-0bj'
828         expected = call(
829             src_obj,
830             content_length=0,
831             source_account=None,
832             success=201,
833             copy_from='/%s/%s' % (src_cont, src_obj),
834             delimiter=None,
835             content_type=None,
836             source_version=None,
837             public=False)
838         self.client.copy_object(src_cont, src_obj, dst_cont)
839         self.assertEqual(put.mock_calls[-1], expected)
840         self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
841         self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
842         kwargs = dict(
843             source_version='src-v3r510n',
844             source_account='src-4cc0un7',
845             public=True,
846             content_type='c0n73n7Typ3',
847             delimiter='5')
848         self.client.copy_object(src_cont, src_obj, dst_cont, **kwargs)
849         for k, v in kwargs.items():
850             self.assertEqual(v, put.mock_calls[-1][2][k])
851
852     @patch('%s.object_put' % pithos_pkg, return_value=FR())
853     def test_move_object(self, put):
854         src_cont = 'src-c0nt41n3r'
855         src_obj = 'src-0bj'
856         dst_cont = 'dst-c0nt41n3r'
857         dst_obj = 'dst-0bj'
858         expected = call(
859             src_obj,
860             content_length=0,
861             source_account=None,
862             success=201,
863             move_from='/%s/%s' % (src_cont, src_obj),
864             delimiter=None,
865             content_type=None,
866             source_version=None,
867             public=False)
868         self.client.move_object(src_cont, src_obj, dst_cont)
869         self.assertEqual(put.mock_calls[-1], expected)
870         self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
871         self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
872         kwargs = dict(
873             source_version='src-v3r510n',
874             source_account='src-4cc0un7',
875             public=True,
876             content_type='c0n73n7Typ3',
877             delimiter='5')
878         self.client.move_object(src_cont, src_obj, dst_cont, **kwargs)
879         for k, v in kwargs.items():
880             self.assertEqual(v, put.mock_calls[-1][2][k])
881
882     #  Pithos+ only methods
883
884     @patch('%s.container_delete' % pithos_pkg, return_value=FR())
885     def test_purge_container(self, CD):
886         self.client.purge_container()
887         self.assertTrue('until' in CD.mock_calls[-1][2])
888         cont = self.client.container
889         self.client.purge_container('another-container')
890         self.assertEqual(self.client.container, cont)
891
892     @patch('%s.object_put' % pithos_pkg, return_value=FR())
893     def test_upload_object_unchunked(self, put):
894         num_of_blocks = 8
895         tmpFile = self._create_temp_file(num_of_blocks)
896         expected = dict(
897                 success=201,
898                 data=num_of_blocks * 4 * 1024 * 1024,
899                 etag='some-etag',
900                 content_encoding='some content_encoding',
901                 content_type='some content-type',
902                 content_disposition='some content_disposition',
903                 public=True,
904                 permissions=dict(read=['u1', 'g1', 'u2'], write=['u1']))
905         self.client.upload_object_unchunked(obj, tmpFile)
906         self.assertEqual(put.mock_calls[-1][1], (obj,))
907         self.assertEqual(
908             sorted(put.mock_calls[-1][2].keys()),
909             sorted(expected.keys()))
910         kwargs = dict(expected)
911         kwargs.pop('success')
912         kwargs['size'] = kwargs.pop('data')
913         kwargs['sharing'] = kwargs.pop('permissions')
914         tmpFile.seek(0)
915         self.client.upload_object_unchunked(obj, tmpFile, **kwargs)
916         pmc = put.mock_calls[-1][2]
917         for k, v in expected.items():
918             if k == 'data':
919                 self.assertEqual(len(pmc[k]), v)
920             else:
921                 self.assertEqual(pmc[k], v)
922         self.assertRaises(
923             ClientError,
924             self.client.upload_object_unchunked,
925             obj, tmpFile, withHashFile=True)
926
927     @patch('%s.object_put' % pithos_pkg, return_value=FR())
928     def test_create_object_by_manifestation(self, put):
929         manifest = '%s/%s' % (self.client.container, obj)
930         kwargs = dict(
931                 etag='some-etag',
932                 content_encoding='some content_encoding',
933                 content_type='some content-type',
934                 content_disposition='some content_disposition',
935                 public=True,
936                 sharing=dict(read=['u1', 'g1', 'u2'], write=['u1']))
937         self.client.create_object_by_manifestation(obj)
938         expected = dict(content_length=0, manifest=manifest)
939         for k in kwargs:
940             expected['permissions' if k == 'sharing' else k] = None
941         self.assertEqual(put.mock_calls[-1], call(obj, **expected))
942         self.client.create_object_by_manifestation(obj, **kwargs)
943         expected.update(kwargs)
944         expected['permissions'] = expected.pop('sharing')
945         self.assertEqual(put.mock_calls[-1], call(obj, **expected))
946
947     @patch('%s.get_object_hashmap' % pithos_pkg, return_value=object_hashmap)
948     @patch('%s.object_get' % pithos_pkg, return_value=FR())
949     def test_download_object(self, GET, GOH):
950         num_of_blocks = 8
951         tmpFile = self._create_temp_file(num_of_blocks)
952         FR.content = tmpFile.read(4 * 1024 * 1024)
953         tmpFile = self._create_temp_file(num_of_blocks)
954         num_of_blocks = len(object_hashmap['hashes'])
955         kwargs = dict(
956             resume=True,
957             version='version',
958             range_str='10-20',
959             if_match='if and only if',
960             if_none_match='if and only not',
961             if_modified_since='what if not?',
962             if_unmodified_since='this happens if not!',
963             async_headers=dict(Range='bytes=0-88888888'))
964
965         self.client.download_object(obj, tmpFile)
966         self.assertEqual(len(GET.mock_calls), num_of_blocks)
967         self.assertEqual(GET.mock_calls[-1][1], (obj,))
968         for k, v in kwargs.items():
969             if k == 'async_headers':
970                 self.assertTrue('Range' in GET.mock_calls[-1][2][k])
971             elif k in ('resume', 'range_str'):
972                 continue
973             else:
974                 self.assertEqual(GET.mock_calls[-1][2][k], None)
975
976         #  Check ranges are consecutive
977         starts = []
978         ends = []
979         for c in GET.mock_calls:
980             rng_str = c[2]['async_headers']['Range']
981             (start, rng_str) = rng_str.split('=')
982             (start, end) = rng_str.split('-')
983             starts.append(start)
984             ends.append(end)
985         ends = sorted(ends)
986         for i, start in enumerate(sorted(starts)):
987             if i:
988                 int(ends[i - 1]) == int(start) - 1
989
990         #  With progress bars
991         try:
992             from progress.bar import ShadyBar
993             dl_bar = ShadyBar('Mock dl')
994         except ImportError:
995             dl_bar = None
996
997         if dl_bar:
998
999             def blck_gen(n):
1000                 for i in dl_bar.iter(range(n)):
1001                     yield
1002                 yield
1003
1004             tmpFile.seek(0)
1005             self.client.download_object(obj, tmpFile, download_cb=blck_gen)
1006             self.assertEqual(len(GET.mock_calls), 2 * num_of_blocks)
1007
1008         tmpFile.seek(0)
1009         kwargs.pop('async_headers')
1010         kwargs.pop('resume')
1011         self.client.download_object(obj, tmpFile, **kwargs)
1012         for k, v in kwargs.items():
1013             if k == 'range_str':
1014                 self.assertEqual(
1015                     GET.mock_calls[-1][2]['data_range'],
1016                     'bytes=%s' % v)
1017             else:
1018                 self.assertEqual(GET.mock_calls[-1][2][k], v)
1019
1020         #  ALl options on no tty
1021
1022         def foo():
1023             return True
1024
1025         tmpFile.seek(0)
1026         tmpFile.isatty = foo
1027         self.client.download_object(obj, tmpFile, **kwargs)
1028         for k, v in kwargs.items():
1029             if k == 'range_str':
1030                 self.assertTrue('data_range' in GET.mock_calls[-1][2])
1031             else:
1032                 self.assertEqual(GET.mock_calls[-1][2][k], v)
1033
1034     def test_get_object_hashmap(self):
1035         FR.json = object_hashmap
1036         for empty in (304, 412):
1037             with patch.object(
1038                     PithosClient, 'object_get',
1039                     side_effect=ClientError('Empty', status=empty)):
1040                 r = self.client.get_object_hashmap(obj)
1041                 self.assertEqual(r, {})
1042         exp_args = dict(
1043             hashmap=True,
1044             data_range=None,
1045             version=None,
1046             if_etag_match=None,
1047             if_etag_not_match=None,
1048             if_modified_since=None,
1049             if_unmodified_since=None)
1050         kwargs = dict(
1051             version='s0m3v3r51on',
1052             if_match='if match',
1053             if_none_match='if non match',
1054             if_modified_since='some date here',
1055             if_unmodified_since='some date here',
1056             data_range='10-20')
1057         with patch.object(
1058                 PithosClient, 'object_get',
1059                 return_value=FR()) as get:
1060             r = self.client.get_object_hashmap(obj)
1061             self.assertEqual(r, object_hashmap)
1062             self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
1063             r = self.client.get_object_hashmap(obj, **kwargs)
1064             exp_args['if_etag_match'] = kwargs.pop('if_match')
1065             exp_args['if_etag_not_match'] = kwargs.pop('if_none_match')
1066             exp_args.update(kwargs)
1067             self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
1068
1069     @patch('%s.account_post' % pithos_pkg, return_value=FR())
1070     def test_set_account_group(self, post):
1071         (group, usernames) = ('aU53rGr0up', ['u1', 'u2', 'u3'])
1072         self.client.set_account_group(group, usernames)
1073         post.assert_called_once_with(update=True, groups={group: usernames})
1074
1075     @patch('%s.account_post' % pithos_pkg, return_value=FR())
1076     def test_del_account_group(self, post):
1077         group = 'aU53rGr0up'
1078         self.client.del_account_group(group)
1079         post.assert_called_once_with(update=True, groups={group: []})
1080
1081     @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
1082     def test_get_account_quota(self, GAI):
1083         key = 'x-account-policy-quota'
1084         r = self.client.get_account_quota()
1085         GAI.assert_called_once_with()
1086         self.assertEqual(r[key], account_info[key])
1087
1088     @patch('%s.get_account_info' % pithos_pkg, return_value=account_info)
1089     def test_get_account_versioning(self, GAI):
1090         key = 'x-account-policy-versioning'
1091         r = self.client.get_account_versioning()
1092         GAI.assert_called_once_with()
1093         self.assertEqual(r[key], account_info[key])
1094
1095     def test_get_account_meta(self):
1096         key = 'x-account-meta-'
1097         with patch.object(
1098                 PithosClient, 'get_account_info',
1099                 return_value=account_info):
1100             r = self.client.get_account_meta()
1101             keys = [k for k in r if k.startswith(key)]
1102             self.assertFalse(keys)
1103         acc_info = dict(account_info)
1104         acc_info['%sk1' % key] = 'v1'
1105         acc_info['%sk2' % key] = 'v2'
1106         acc_info['%sk3' % key] = 'v3'
1107         with patch.object(
1108                 PithosClient, 'get_account_info',
1109                 return_value=acc_info):
1110             r = self.client.get_account_meta()
1111             for k in [k for k in acc_info if k.startswith(key)]:
1112                 self.assertEqual(r[k], acc_info[k])
1113
1114     def test_get_account_group(self):
1115         key = 'x-account-group-'
1116         with patch.object(
1117                 PithosClient, 'get_account_info',
1118                 return_value=account_info):
1119             r = self.client.get_account_group()
1120             keys = [k for k in r if k.startswith(key)]
1121             self.assertFalse(keys)
1122         acc_info = dict(account_info)
1123         acc_info['%sk1' % key] = 'g1'
1124         acc_info['%sk2' % key] = 'g2'
1125         acc_info['%sk3' % key] = 'g3'
1126         with patch.object(
1127                 PithosClient, 'get_account_info',
1128                 return_value=acc_info):
1129             r = self.client.get_account_group()
1130             for k in [k for k in acc_info if k.startswith(key)]:
1131                 self.assertEqual(r[k], acc_info[k])
1132
1133     @patch('%s.account_post' % pithos_pkg, return_value=FR())
1134     def test_set_account_meta(self, post):
1135         metas = dict(k1='v1', k2='v2', k3='v3')
1136         self.client.set_account_meta(metas)
1137         post.assert_called_once_with(update=True, metadata=metas)
1138
1139     @patch('%s.account_post' % pithos_pkg, return_value=FR())
1140     def test_set_account_quota(self, post):
1141         qu = 1024
1142         self.client.set_account_quota(qu)
1143         post.assert_called_once_with(update=True, quota=qu)
1144
1145     @patch('%s.account_post' % pithos_pkg, return_value=FR())
1146     def test_set_account_versioning(self, post):
1147         vrs = 'n3wV3r51on1ngTyp3'
1148         self.client.set_account_versioning(vrs)
1149         post.assert_called_once_with(update=True, versioning=vrs)
1150
1151     @patch('%s.container_delete' % pithos_pkg, return_value=FR())
1152     def test_del_container(self, delete):
1153         for kwarg in (
1154                 dict(delimiter=None, until=None),
1155                 dict(delimiter='X', until='50m3d473')):
1156             self.client.del_container(**kwarg)
1157             expected = dict(kwarg)
1158             expected['success'] = (204, 404, 409)
1159             self.assertEqual(delete.mock_calls[-1], call(**expected))
1160         for status_code in (404, 409):
1161             FR.status_code = status_code
1162             self.assertRaises(ClientError, self.client.del_container)
1163
1164     @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1165     def test_get_container_versioning(self, GCI):
1166         key = 'x-container-policy-versioning'
1167         cont = 'c0n7-417'
1168         bu_cnt = self.client.container
1169         for container in (None, cont):
1170             r = self.client.get_container_versioning(container=container)
1171             self.assertEqual(r[key], container_info[key])
1172             self.assertEqual(GCI.mock_calls[-1], call())
1173             self.assertEqual(bu_cnt, self.client.container)
1174
1175     @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1176     def test_get_container_quota(self, GCI):
1177         key = 'x-container-policy-quota'
1178         cont = 'c0n7-417'
1179         bu_cnt = self.client.container
1180         for container in (None, cont):
1181             r = self.client.get_container_quota(container=container)
1182             self.assertEqual(r[key], container_info[key])
1183             self.assertEqual(GCI.mock_calls[-1], call())
1184             self.assertEqual(bu_cnt, self.client.container)
1185
1186     def test_get_container_meta(self):
1187         somedate = '50m3d473'
1188         key = 'x-container-meta'
1189         metaval = '50m3m374v41'
1190         container_plus = dict(container_info)
1191         container_plus[key] = metaval
1192         for ret in ((container_info, {}), (container_plus, {key: metaval})):
1193             with patch.object(
1194                     PithosClient,
1195                     'get_container_info',
1196                     return_value=ret[0]) as GCI:
1197                 for until in (None, somedate):
1198                     r = self.client.get_container_meta(until=until)
1199                     self.assertEqual(r, ret[1])
1200                     self.assertEqual(GCI.mock_calls[-1], call(until=until))
1201
1202     def test_get_container_object_meta(self):
1203         somedate = '50m3d473'
1204         key = 'x-container-object-meta'
1205         metaval = '50m3m374v41'
1206         container_plus = dict(container_info)
1207         container_plus[key] = metaval
1208         for ret in (
1209                 (container_info, {key: ''}),
1210                 (container_plus, {key: metaval})):
1211             with patch.object(
1212                     PithosClient,
1213                     'get_container_info',
1214                     return_value=ret[0]) as GCI:
1215                 for until in (None, somedate):
1216                     r = self.client.get_container_object_meta(until=until)
1217                     self.assertEqual(r, ret[1])
1218                     self.assertEqual(GCI.mock_calls[-1], call(until=until))
1219
1220     @patch('%s.container_post' % pithos_pkg, return_value=FR())
1221     def test_set_container_meta(self, post):
1222         metas = dict(k1='v1', k2='v2', k3='v3')
1223         self.client.set_container_meta(metas)
1224         post.assert_called_once_with(update=True, metadata=metas)
1225
1226     @patch('%s.container_post' % pithos_pkg, return_value=FR())
1227     def test_del_container_meta(self, AP):
1228         self.client.del_container_meta('somekey')
1229         AP.assert_called_once_with(update=True, metadata={'somekey': ''})
1230
1231     @patch('%s.container_post' % pithos_pkg, return_value=FR())
1232     def test_set_container_quota(self, post):
1233         qu = 1024
1234         self.client.set_container_quota(qu)
1235         post.assert_called_once_with(update=True, quota=qu)
1236
1237     @patch('%s.container_post' % pithos_pkg, return_value=FR())
1238     def test_set_container_versioning(self, post):
1239         vrs = 'n3wV3r51on1ngTyp3'
1240         self.client.set_container_versioning(vrs)
1241         post.assert_called_once_with(update=True, versioning=vrs)
1242
1243     @patch('%s.object_delete' % pithos_pkg, return_value=FR())
1244     def test_del_object(self, delete):
1245         for kwarg in (
1246                 dict(delimiter=None, until=None),
1247                 dict(delimiter='X', until='50m3d473')):
1248             self.client.del_object(obj, **kwarg)
1249             self.assertEqual(delete.mock_calls[-1], call(obj, **kwarg))
1250
1251     @patch('%s.object_post' % pithos_pkg, return_value=FR())
1252     def test_set_object_meta(self, post):
1253         metas = dict(k1='v1', k2='v2', k3='v3')
1254         self.assertRaises(
1255             AssertionError,
1256             self.client.set_object_meta,
1257             obj, 'Non dict arg')
1258         self.client.set_object_meta(obj, metas)
1259         post.assert_called_once_with(obj, update=True, metadata=metas)
1260
1261     @patch('%s.object_post' % pithos_pkg, return_value=FR())
1262     def test_publish_object(self, post):
1263         oinfo = dict(object_info)
1264         val = 'pubL1c'
1265         oinfo['x-object-public'] = val
1266         with patch.object(
1267                 PithosClient, 'get_object_info',
1268                 return_value=oinfo) as GOF:
1269             r = self.client.publish_object(obj)
1270             self.assertEqual(
1271                 post.mock_calls[-1],
1272                 call(obj, public=True, update=True))
1273             self.assertEqual(GOF.mock_calls[-1], call(obj))
1274             self.assertEqual(r, '%s%s' % (self.url[:-6], val))
1275
1276     @patch('%s.object_post' % pithos_pkg, return_value=FR())
1277     def test_unpublish_object(self, post):
1278         self.client.unpublish_object(obj)
1279         post.assert_called_once_with(obj, public=False, update=True)
1280
1281     def test_get_object_sharing(self):
1282         info = dict(object_info)
1283         expected = dict(read='u1,g1,u2', write='u1')
1284         info['x-object-sharing'] = '; '.join(
1285             ['%s=%s' % (k, v) for k, v in expected.items()])
1286         with patch.object(
1287                 PithosClient, 'get_object_info',
1288                 return_value=info) as GOF:
1289             r = self.client.get_object_sharing(obj)
1290             self.assertEqual(GOF.mock_calls[-1], call(obj))
1291             self.assert_dicts_are_equal(r, expected)
1292             info['x-object-sharing'] = '//'.join(
1293                 ['%s=%s' % (k, v) for k, v in expected.items()])
1294             self.assertRaises(
1295                 ValueError,
1296                 self.client.get_object_sharing,
1297                 obj)
1298             info['x-object-sharing'] = '; '.join(
1299                 ['%s:%s' % (k, v) for k, v in expected.items()])
1300             self.assertRaises(
1301                 ClientError,
1302                 self.client.get_object_sharing,
1303                 obj)
1304             info['x-object-sharing'] = 'read=%s' % expected['read']
1305             r = self.client.get_object_sharing(obj)
1306             expected.pop('write')
1307             self.assert_dicts_are_equal(r, expected)
1308
1309     @patch('%s.object_post' % pithos_pkg, return_value=FR())
1310     def test_set_object_sharing(self, OP):
1311         read_perms = ['u1', 'g1', 'u2', 'g2']
1312         write_perms = ['u1', 'g1']
1313         for kwargs in (
1314                 dict(read_permition=read_perms, write_permition=write_perms),
1315                 dict(read_permition=read_perms),
1316                 dict(write_permition=write_perms),
1317                 dict()):
1318             self.client.set_object_sharing(obj, **kwargs)
1319             kwargs['read'] = kwargs.pop('read_permition', '')
1320             kwargs['write'] = kwargs.pop('write_permition', '')
1321             self.assertEqual(
1322                 OP.mock_calls[-1],
1323                 call(obj, update=True, permissions=kwargs))
1324
1325     @patch('%s.set_object_sharing' % pithos_pkg)
1326     def test_del_object_sharing(self, SOS):
1327         self.client.del_object_sharing(obj)
1328         SOS.assert_called_once_with(obj)
1329
1330     @patch('%s.get_container_info' % pithos_pkg, return_value=container_info)
1331     @patch('%s.object_post' % pithos_pkg, return_value=FR())
1332     def test_append_object(self, post, GCI):
1333         num_of_blocks = 4
1334         tmpFile = self._create_temp_file(num_of_blocks)
1335         tmpFile.seek(0, 2)
1336         file_size = tmpFile.tell()
1337         for turn in range(2):
1338             tmpFile.seek(0, 0)
1339
1340             try:
1341                 from progress.bar import ShadyBar
1342                 apn_bar = ShadyBar('Mock append')
1343             except ImportError:
1344                 apn_bar = None
1345
1346             if apn_bar:
1347
1348                 def append_gen(n):
1349                     for i in apn_bar.iter(range(n)):
1350                         yield
1351                     yield
1352
1353             else:
1354                 append_gen = None
1355
1356             self.client.append_object(
1357                 obj, tmpFile,
1358                 upload_cb=append_gen if turn else None)
1359             self.assertEqual((turn + 1) * num_of_blocks, len(post.mock_calls))
1360             (args, kwargs) = post.mock_calls[-1][1:3]
1361             self.assertEqual(args, (obj,))
1362             self.assertEqual(kwargs['content_length'], len(kwargs['data']))
1363             fsize = num_of_blocks * int(kwargs['content_length'])
1364             self.assertEqual(fsize, file_size)
1365             self.assertEqual(kwargs['content_range'], 'bytes */*')
1366             exp = 'application/octet-stream'
1367             self.assertEqual(kwargs['content_type'], exp)
1368             self.assertEqual(kwargs['update'], True)
1369
1370     @patch('%s.object_post' % pithos_pkg, return_value=FR())
1371     def test_truncate_object(self, post):
1372         upto_bytes = 377
1373         self.client.truncate_object(obj, upto_bytes)
1374         post.assert_called_once_with(
1375             obj,
1376             update=True,
1377             object_bytes=upto_bytes,
1378             content_range='bytes 0-%s/*' % upto_bytes,
1379             content_type='application/octet-stream',
1380             source_object='/%s/%s' % (self.client.container, 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_overwrite_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         info = dict(object_info)
1390         info['content-length'] = file_size
1391         block_size = container_info['x-container-block-size']
1392         with patch.object(
1393                 PithosClient, 'get_object_info',
1394                 return_value=info) as GOI:
1395             for start, end in (
1396                     (0, file_size + 1),
1397                     (file_size + 1, file_size + 2)):
1398                 tmpFile.seek(0, 0)
1399                 self.assertRaises(
1400                     ClientError,
1401                     self.client.overwrite_object,
1402                     obj, start, end, tmpFile)
1403             for start, end in ((0, 144), (144, 233), (233, file_size)):
1404                 tmpFile.seek(0, 0)
1405                 owr_gen = None
1406                 exp_size = end - start + 1
1407                 if not start or exp_size > block_size:
1408                     try:
1409                         from progress.bar import ShadyBar
1410                         owr_bar = ShadyBar('Mock append')
1411                     except ImportError:
1412                         owr_bar = None
1413
1414                     if owr_bar:
1415
1416                         def owr_gen(n):
1417                             for i in owr_bar.iter(range(n)):
1418                                 yield
1419                             yield
1420
1421                     if exp_size > block_size:
1422                         exp_size = exp_size % block_size or block_size
1423
1424                 self.client.overwrite_object(obj, start, end, tmpFile, owr_gen)
1425                 self.assertEqual(GOI.mock_calls[-1], call(obj))
1426                 self.assertEqual(GCI.mock_calls[-1], call())
1427                 (args, kwargs) = post.mock_calls[-1][1:3]
1428                 self.assertEqual(args, (obj,))
1429                 self.assertEqual(len(kwargs['data']), exp_size)
1430                 self.assertEqual(kwargs['content_length'], exp_size)
1431                 self.assertEqual(kwargs['update'], True)
1432                 exp = 'application/octet-stream'
1433                 self.assertEqual(kwargs['content_type'], exp)
1434
1435     @patch('%s.set_param' % pithos_pkg)
1436     @patch('%s.get' % pithos_pkg, return_value=FR())
1437     def test_get_sharing_accounts(self, get, SP):
1438         FR.json = sharers
1439         for kws in (
1440                 dict(),
1441                 dict(limit='50m3-11m17'),
1442                 dict(marker='X'),
1443                 dict(limit='50m3-11m17', marker='X')):
1444             r = self.client.get_sharing_accounts(**kws)
1445             self.assertEqual(get.mock_calls[-1], call('', success=(200, 204)))
1446             self.assertEqual(SP.mock_calls[-3], call('format', 'json'))
1447             limit, marker = kws.get('limit', None), kws.get('marker', None)
1448             self.assertEqual(SP.mock_calls[-2], call(
1449                 'limit', limit,
1450                 iff=limit is not None))
1451             self.assertEqual(SP.mock_calls[-1], call(
1452                 'marker', marker,
1453                 iff=marker is not None))
1454             for i in range(len(r)):
1455                 self.assert_dicts_are_equal(r[i], sharers[i])
1456
1457     @patch('%s.object_get' % pithos_pkg, return_value=FR())
1458     def test_get_object_versionlist(self, get):
1459         info = dict(object_info)
1460         info['versions'] = ['v1', 'v2']
1461         FR.json = info
1462         r = self.client.get_object_versionlist(obj)
1463         get.assert_called_once_with(obj, format='json', version='list')
1464         self.assertEqual(r, info['versions'])
1465
1466 if __name__ == '__main__':
1467     from sys import argv
1468     from kamaki.clients.test import runTestCase
1469     not_found = True
1470     if not argv[1:] or argv[1] == 'Pithos':
1471         not_found = False
1472         runTestCase(Pithos, 'Pithos Client', argv[2:])
1473     if not argv[1:] or argv[1] == 'PithosRest':
1474         not_found = False
1475         runTestCase(PithosRest, 'PithosRest Client', argv[2:])
1476     if not_found:
1477         print('TestCase %s not found' % argv[1])