1 # Copyright 2013 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
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.
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.
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.
34 from unittest import TestCase
35 from mock import patch, call, Mock
36 from tempfile import NamedTemporaryFile
37 from os import urandom
39 from kamaki.clients import ClientError
40 from kamaki.clients.pithos import PithosClient as PC
41 from kamaki.clients.astakos import AstakosClient
42 from kamaki.clients.connection.kamakicon import KamakiHTTPConnection as C
44 user_id = 'ac0un7-1d-5tr1ng'
48 'content-language': 'en-us',
49 'content-type': 'text/html; charset=utf-8',
50 'date': 'Wed, 06 Mar 2013 13:25:51 GMT',
51 'last-modified': 'Mon, 04 Mar 2013 18:22:31 GMT',
52 'server': 'gunicorn/0.14.5',
53 'vary': 'Accept-Language',
54 'x-account-bytes-used': '751615526',
55 'x-account-container-count': 7,
56 'x-account-policy-quota': 53687091200,
57 'x-account-policy-versioning': 'auto'}
59 'content-language': 'en-us',
60 'content-type': 'text/html; charset=utf-8',
61 'date': 'Wed, 06 Mar 2013 15:11:05 GMT',
62 'last-modified': 'Wed, 27 Feb 2013 15:56:13 GMT',
63 'server': 'gunicorn/0.14.5',
64 'vary': 'Accept-Language',
65 'x-container-block-hash': 'sha256',
66 'x-container-block-size': 4194304,
67 'x-container-bytes-used': 309528938,
68 'x-container-object-count': 14,
69 'x-container-object-meta': '',
70 'x-container-policy-quota': 53687091200,
71 'x-container-policy-versioning': 'auto'}
73 'content-language': 'en-us',
74 'content-length': 254965,
75 'content-type': 'application/octet-stream',
76 'date': 'Thu, 07 Mar 2013 13:27:43 GMT',
78 'last-modified': 'Mon, 04 Mar 2013 18:22:31 GMT',
79 'server': 'gunicorn/0.14.5',
80 'vary': 'Accept-Language',
81 'x-object-hash': 'obj3c7h45h1s0bj3c7h45h411r34dY',
82 'x-object-uuid': 'd0c747ca-34bd-49e0-8e98-1d07d8b0cbc7',
83 'x-object-version': '525996',
84 'x-object-version-timestamp': 'Mon, 04 Mar 2013 18:22:31 GMT',
85 'x-object-meta-k1': 'v1',
86 'x-object-meta-k2': 'v2'}
90 last_modified="2013-02-27T11:56:09.893033+00:00",
93 x_container_policy=dict(quota="21474836480", versioning="auto")),
96 last_modified="2012-10-23T12:25:17.229187+00:00",
99 x_container_policy=dict(quota="21474836480", versioning="auto"))]
102 name="The_Secret_Garden.zip",
103 x_object_public="/public/wdp9p",
105 x_object_version_timestamp="1360237915.7027509",
106 x_object_uuid="s0m3uu1df0r0bj0n3",
107 last_modified="2013-02-07T11:51:55.702751+00:00",
108 content_type="application/octet-stream",
109 x_object_hash="0afdf29f71cd53126225c3f54ca",
110 x_object_version=17737,
111 x_object_modified_by=user_id),
113 name="The_Revealed_Garden.zip",
114 x_object_public="/public/wpd7p",
116 x_object_version_timestamp="13602915.7027509",
117 x_object_uuid="s0m3uu1df0r0bj70w",
118 last_modified="2013-02-07T11:51:55.702751+00:00",
119 content_type="application/octet-stream",
120 x_object_hash="0afdf29f71cd53126225c3f54ca",
121 x_object_version=17737,
122 x_object_modified_by=user_id)]
123 object_hashmap = dict(
124 block_hash="sha256", block_size=4194304, bytes=33554432,
126 "4988438cc1c0292c085d289649b28cf547ba3db71c6efaac9f2df7e193d4d0af",
127 "b214244aa56df7d1df7c6cac066e7cef268d9c2beb4dcf7ce68af667b0626f91",
128 "17f365f25e0682565ded30576066bb13377a3d306967e4d74e06bb6bbc20f75f",
129 "2524ae208932669fff89adf8a2fc0df3b67736ca0d3aadce7a2ce640f142af37",
130 "5d807a2129d2fcd3c221c3da418ed52af3fc48d0817b62e0bb437acffccd3514",
131 "609de22ce842d997f645fc49d5f14e0e3766dd51a6cbe66383b2bab82c8dfcd0",
132 "3102851ac168c78be70e35ff5178c7b1ebebd589e5106d565ca1094d1ca8ff59",
133 "bfe306dd24e92a8d85caf7055643f250fd319e8c4cdd4755ddabbf3ff97e83c7"])
136 class Pithos(TestCase):
139 """FR stands for Fake Response"""
151 def _create_temp_file(self, num_of_blocks):
152 self.files.append(NamedTemporaryFile())
153 tmpFile = self.files[-1]
154 file_size = num_of_blocks * 4 * 1024 * 1024
155 print('\n\tCreate tmp file')
156 tmpFile.write(urandom(file_size))
162 def assert_dicts_are_equal(self, d1, d2):
163 for k, v in d1.items():
164 self.assertTrue(k in d2)
165 if isinstance(v, dict):
166 self.assert_dicts_are_equal(v, d2[k])
168 self.assertEqual(unicode(v), unicode(d2[k]))
171 self.url = 'https://www.example.com/pithos'
172 self.token = 'p17h0570k3n'
173 self.client = PC(self.url, self.token)
174 self.client.account = user_id
175 self.client.container = 'c0nt@1n3r_i'
178 self.FR.headers = dict()
179 self.FR.status_code = 200
180 self.FR.json = dict()
181 self.FR.content = self.FR.json
185 # Pithos+ methods that extend storage API
187 def test_get_account_info(self):
188 self.FR.headers = account_info
189 self.FR.status_code = 204
190 with patch.object(C, 'perform_request', return_value=self.FR()):
191 r = self.client.get_account_info()
192 self.assertEqual(self.client.http_client.url, self.url)
193 self.assertEqual(self.client.http_client.path, '/%s' % user_id)
194 self.assert_dicts_are_equal(r, account_info)
195 PC.set_param = Mock()
196 untils = ['date 1', 'date 2', 'date 3']
198 r = self.client.get_account_info(until=unt)
199 self.assert_dicts_are_equal(r, account_info)
200 for i in range(len(untils)):
202 PC.set_param.mock_calls[i],
203 call('until', untils[i], iff=untils[i]))
204 self.FR.status_code = 401
205 self.assertRaises(ClientError, self.client.get_account_info)
207 def test_replace_account_meta(self):
208 self.FR.status_code = 202
209 metas = dict(k1='v1', k2='v2', k3='v3')
210 PC.set_header = Mock()
211 with patch.object(C, 'perform_request', return_value=self.FR()):
212 self.client.replace_account_meta(metas)
213 self.assertEqual(self.client.http_client.url, self.url)
214 self.assertEqual(self.client.http_client.path, '/%s' % user_id)
215 prfx = 'X-Account-Meta-'
216 expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
217 self.assertEqual(PC.set_header.mock_calls, expected)
219 def test_del_account_meta(self):
220 keys = ['k1', 'k2', 'k3']
221 with patch.object(PC, 'account_post', return_value=self.FR()) as ap:
224 self.client.del_account_meta(key)
225 expected.append(call(update=True, metadata={key: ''}))
226 self.assertEqual(ap.mock_calls, expected)
228 def test_create_container(self):
229 self.FR.status_code = 201
230 with patch.object(PC, 'put', return_value=self.FR()) as put:
231 cont = 's0m3c0n731n3r'
232 self.client.create_container(cont)
233 expected = [call('/%s/%s' % (user_id, cont), success=(201, 202))]
234 self.assertEqual(put.mock_calls, expected)
235 self.FR.status_code = 202
236 self.assertRaises(ClientError, self.client.create_container, cont)
238 def test_get_container_info(self):
239 self.FR.headers = container_info
240 with patch.object(PC, 'container_head', return_value=self.FR()) as ch:
241 r = self.client.get_container_info()
242 self.assert_dicts_are_equal(r, container_info)
244 r = self.client.get_container_info(until=u)
245 self.assertEqual(ch.mock_calls, [call(until=None), call(until=u)])
247 def test_delete_container(self):
248 self.FR.status_code = 204
249 with patch.object(PC, 'delete', return_value=self.FR()) as delete:
250 cont = 's0m3c0n731n3r'
251 self.client.delete_container(cont)
252 self.FR.status_code = 404
253 self.assertRaises(ClientError, self.client.delete_container, cont)
254 self.FR.status_code = 409
255 self.assertRaises(ClientError, self.client.delete_container, cont)
256 acall = call('/%s/%s' % (user_id, cont), success=(204, 404, 409))
257 self.assertEqual(delete.mock_calls, [acall] * 3)
259 def test_list_containers(self):
260 self.FR.json = container_list
261 with patch.object(PC, 'account_get', return_value=self.FR()):
262 r = self.client.list_containers()
263 for i in range(len(r)):
264 self.assert_dicts_are_equal(r[i], container_list[i])
266 def test_upload_object(self):
267 PC.get_container_info = Mock(return_value=container_info)
268 PC.container_post = Mock(return_value=self.FR())
269 PC.object_put = Mock(return_value=self.FR())
271 tmpFile = self._create_temp_file(num_of_blocks)
274 self.client.upload_object(obj, tmpFile)
275 self.assertEqual(PC.get_container_info.mock_calls, [call()])
276 [call1, call2] = PC.object_put.mock_calls
278 (args1, kwargs1) = call1[1:3]
279 (args2, kwargs2) = call2[1:3]
280 self.assertEqual(args1, (obj,))
286 hashes=['s0m3h@5h'] * num_of_blocks,
287 bytes=num_of_blocks * 4 * 1024 * 1024),
289 content_encoding=None,
290 content_type='application/octet-stream',
291 content_disposition=None,
294 for k, v in expected1.items():
296 self.assertEqual(len(v['hashes']), len(kwargs1[k]['hashes']))
297 self.assertEqual(v['bytes'], kwargs1[k]['bytes'])
299 self.assertEqual(v, kwargs1[k])
301 (args2, kwargs2) = call2[1:3]
302 self.assertEqual(args2, (obj,))
305 hashes=['s0m3h@5h'] * num_of_blocks,
306 bytes=num_of_blocks * 4 * 1024 * 1024),
307 content_type='application/octet-stream',
311 for k, v in expected2.items():
313 self.assertEqual(len(v['hashes']), len(kwargs2[k]['hashes']))
314 self.assertEqual(v['bytes'], kwargs2[k]['bytes'])
316 self.assertEqual(v, kwargs2[k])
323 from progress.bar import ShadyBar
324 blck_bar = ShadyBar('Mock blck calc.')
325 upld_bar = ShadyBar('Mock uplds')
330 if blck_bar and upld_bar:
333 for i in blck_bar.iter(range(n)):
338 for i in upld_bar.iter(range(n)):
343 self.client.upload_object(
345 hash_cb=blck_gen, upload_cb=upld_gen)
347 for i, c in enumerate(OP.mock_calls[-mock_offset:]):
348 self.assertEqual(OP.mock_calls[i], c)
353 sharing = dict(read=['u1', 'g1', 'u2'], write=['u1'])
354 self.client.upload_object(obj, tmpFile,
355 content_type=ctype, sharing=sharing)
356 self.assertEqual(OP.mock_calls[-1][2]['content_type'], ctype)
357 self.assert_dicts_are_equal(
358 OP.mock_calls[-2][2]['permissions'],
366 content_disposition=ctype + 'd15p051710n',
368 content_encoding='802.11')
369 self.client.upload_object(obj, tmpFile, **kwargs)
370 for arg, val in kwargs.items():
371 self.assertEqual(OP.mock_calls[-2][2][arg], val)
373 def test_create_object(self):
374 PC.set_header = Mock()
375 cont = self.client.container
376 ctype = 'c0n73n7/typ3'
378 call('Content-Type', 'application/octet-stream'),
379 call('Content-length', '0'),
380 call('Content-Type', ctype), call('Content-length', '42')]
381 exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)] * 2
382 with patch.object(PC, 'put', return_value=self.FR()) as put:
383 self.client.create_object(obj)
384 self.client.create_object(obj,
385 content_type=ctype, content_length=42)
386 self.assertEqual(PC.set_header.mock_calls, exp_shd)
387 self.assertEqual(put.mock_calls, exp_put)
389 def test_create_directory(self):
390 PC.set_header = Mock()
391 cont = self.client.container
393 call('Content-Type', 'application/directory'),
394 call('Content-length', '0')]
395 exp_put = [call('/%s/%s/%s' % (user_id, cont, obj), success=201)]
396 with patch.object(PC, 'put', return_value=self.FR()) as put:
397 self.client.create_directory(obj)
398 self.assertEqual(PC.set_header.mock_calls, exp_shd)
399 self.assertEqual(put.mock_calls, exp_put)
401 def test_get_object_info(self):
402 self.FR.headers = object_info
404 with patch.object(PC, 'object_head', return_value=self.FR()) as head:
405 r = self.client.get_object_info(obj)
406 self.assertEqual(r, object_info)
407 r = self.client.get_object_info(obj, version=version)
408 self.assertEqual(head.mock_calls, [
409 call(obj, version=None),
410 call(obj, version=version)])
414 side_effect=ClientError('Obj not found', 404)):
417 self.client.get_object_info,
418 obj, version=version)
420 def test_get_object_meta(self):
422 for k, v in object_info.items():
427 return_value=object_info):
428 r = self.client.get_object_meta(obj)
429 self.assert_dicts_are_equal(r, expected)
431 def test_del_object_meta(self):
432 metakey = '50m3m3t4k3y'
433 with patch.object(PC, 'object_post', return_value=self.FR()) as post:
434 self.client.del_object_meta(obj, metakey)
437 [call(obj, update=True, metadata={metakey: ''})])
439 def test_replace_object_meta(self):
440 PC.set_header = Mock()
441 metas = dict(k1='new1', k2='new2', k3='new3')
442 cont = self.client.container
443 with patch.object(PC, 'post', return_value=self.FR()) as post:
444 self.client.replace_object_meta(metas)
445 self.assertEqual(post.mock_calls, [
446 call('/%s/%s' % (user_id, cont),
448 prfx = 'X-Object-Meta-'
449 expected = [call('%s%s' % (prfx, k), v) for k, v in metas.items()]
450 self.assertEqual(PC.set_header.mock_calls, expected)
452 def test_copy_object(self):
453 src_cont = 'src-c0nt41n3r'
455 dst_cont = 'dst-c0nt41n3r'
462 copy_from='/%s/%s' % (src_cont, src_obj),
467 with patch.object(PC, 'object_put', return_value=self.FR()) as put:
468 self.client.copy_object(src_cont, src_obj, dst_cont)
469 self.assertEqual(put.mock_calls[-1], expected)
470 self.client.copy_object(src_cont, src_obj, dst_cont, dst_obj)
471 self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
473 source_version='src-v3r510n',
474 source_account='src-4cc0un7',
476 content_type='c0n73n7Typ3',
478 self.client.copy_object(src_cont, src_obj, dst_cont, **kwargs)
479 for k, v in kwargs.items():
480 self.assertEqual(v, put.mock_calls[-1][2][k])
482 def test_move_object(self):
483 src_cont = 'src-c0nt41n3r'
485 dst_cont = 'dst-c0nt41n3r'
492 move_from='/%s/%s' % (src_cont, src_obj),
497 with patch.object(PC, 'object_put', return_value=self.FR()) as put:
498 self.client.move_object(src_cont, src_obj, dst_cont)
499 self.assertEqual(put.mock_calls[-1], expected)
500 self.client.move_object(src_cont, src_obj, dst_cont, dst_obj)
501 self.assertEqual(put.mock_calls[-1][1], (dst_obj,))
503 source_version='src-v3r510n',
504 source_account='src-4cc0un7',
506 content_type='c0n73n7Typ3',
508 self.client.move_object(src_cont, src_obj, dst_cont, **kwargs)
509 for k, v in kwargs.items():
510 self.assertEqual(v, put.mock_calls[-1][2][k])
512 def test_delete_object(self):
513 cont = self.client.container
514 with patch.object(PC, 'delete', return_value=self.FR()) as delete:
515 self.client.delete_object(obj)
516 self.assertEqual(delete.mock_calls, [
517 call('/%s/%s/%s' % (user_id, cont, obj), success=(204, 404))])
518 self.FR.status_code = 404
519 self.assertRaises(ClientError, self.client.delete_object, obj)
521 def test_list_objects(self):
522 self.FR.json = object_list
523 acc = self.client.account
524 cont = self.client.container
525 PC.set_param = Mock()
527 with patch.object(PC, 'get', return_value=self.FR()) as get:
528 r = self.client.list_objects()
529 for i in range(len(r)):
530 self.assert_dicts_are_equal(r[i], object_list[i])
531 self.assertEqual(get.mock_calls, [
532 call('/%s/%s' % (acc, cont), success=(200, 204, 304, 404))])
533 self.assertEqual(SP.mock_calls, [call('format', 'json')])
534 self.FR.status_code = 304
535 self.assertEqual(self.client.list_objects(), [])
536 self.FR.status_code = 404
537 self.assertRaises(ClientError, self.client.list_objects)
539 def test_list_objects_in_path(self):
540 self.FR.json = object_list
541 path = '/some/awsome/path'
542 acc = self.client.account
543 cont = self.client.container
544 PC.set_param = Mock()
546 with patch.object(PC, 'get', return_value=self.FR()) as get:
547 self.client.list_objects_in_path(path)
548 self.assertEqual(get.mock_calls, [
549 call('/%s/%s' % (acc, cont), success=(200, 204, 404))])
550 self.assertEqual(SP.mock_calls, [
551 call('format', 'json'), call('path', path)])
552 self.FR.status_code = 404
553 self.assertRaises(ClientError, self.client.list_objects)
555 # Pithos+ only methods
557 def test_purge_container(self):
561 return_value=self.FR()) as cd:
562 self.client.purge_container()
563 self.assertTrue('until' in cd.mock_calls[-1][2])
564 cont = self.client.container
565 self.client.purge_container('another-container')
566 self.assertEqual(self.client.container, cont)
568 def test_upload_object_unchunked(self):
570 tmpFile = self._create_temp_file(num_of_blocks)
573 data=num_of_blocks * 4 * 1024 * 1024,
575 content_encoding='some content_encoding',
576 content_type='some content-type',
577 content_disposition='some content_disposition',
579 permissions=dict(read=['u1', 'g1', 'u2'], write=['u1']))
580 with patch.object(PC, 'object_put', return_value=self.FR()) as put:
581 self.client.upload_object_unchunked(obj, tmpFile)
582 self.assertEqual(put.mock_calls[-1][1], (obj,))
584 sorted(put.mock_calls[-1][2].keys()),
585 sorted(expected.keys()))
586 kwargs = dict(expected)
587 kwargs.pop('success')
588 kwargs['size'] = kwargs.pop('data')
589 kwargs['sharing'] = kwargs.pop('permissions')
591 self.client.upload_object_unchunked(obj, tmpFile, **kwargs)
592 pmc = put.mock_calls[-1][2]
593 for k, v in expected.items():
595 self.assertEqual(len(pmc[k]), v)
597 self.assertEqual(pmc[k], v)
600 self.client.upload_object_unchunked,
601 obj, tmpFile, withHashFile=True)
603 def test_create_object_by_manifestation(self):
604 manifest = '%s/%s' % (self.client.container, obj)
607 content_encoding='some content_encoding',
608 content_type='some content-type',
609 content_disposition='some content_disposition',
611 sharing=dict(read=['u1', 'g1', 'u2'], write=['u1']))
612 with patch.object(PC, 'object_put', return_value=self.FR()) as put:
613 self.client.create_object_by_manifestation(obj)
614 expected = dict(content_length=0, manifest=manifest)
616 expected['permissions' if k == 'sharing' else k] = None
617 self.assertEqual(put.mock_calls[-1], call(obj, **expected))
618 self.client.create_object_by_manifestation(obj, **kwargs)
619 expected.update(kwargs)
620 expected['permissions'] = expected.pop('sharing')
621 self.assertEqual(put.mock_calls[-1], call(obj, **expected))
623 def test_download_object(self):
624 PC.get_object_hashmap = Mock(return_value=object_hashmap)
626 tmpFile = self._create_temp_file(num_of_blocks)
627 self.FR.content = tmpFile.read(4 * 1024 * 1024)
628 tmpFile = self._create_temp_file(num_of_blocks)
629 PC.object_get = Mock(return_value=self.FR())
631 num_of_blocks = len(object_hashmap['hashes'])
637 if_match='if and only if',
638 if_none_match='if and only not',
639 if_modified_since='what if not?',
640 if_unmodified_since='this happens if not!',
641 async_headers=dict(Range='bytes=0-88888888'))
643 self.client.download_object(obj, tmpFile)
644 self.assertEqual(len(GET.mock_calls), num_of_blocks)
645 self.assertEqual(GET.mock_calls[-1][1], (obj,))
646 for k, v in kwargs.items():
647 if k == 'async_headers':
648 self.assertTrue('Range' in GET.mock_calls[-1][2][k])
649 elif k in ('resume', 'range_str'):
652 self.assertEqual(GET.mock_calls[-1][2][k], None)
654 # Check ranges are consecutive
657 for c in GET.mock_calls:
658 rng_str = c[2]['async_headers']['Range']
659 (start, rng_str) = rng_str.split('=')
660 (start, end) = rng_str.split('-')
664 for i, start in enumerate(sorted(starts)):
666 int(ends[i - 1]) == int(start) - 1
670 from progress.bar import ShadyBar
671 dl_bar = ShadyBar('Mock dl')
678 for i in dl_bar.iter(range(n)):
683 self.client.download_object(obj, tmpFile, download_cb=blck_gen)
684 self.assertEqual(len(GET.mock_calls), 2 * num_of_blocks)
687 kwargs.pop('async_headers')
689 self.client.download_object(obj, tmpFile, **kwargs)
690 for k, v in kwargs.items():
693 GET.mock_calls[-1][2]['data_range'],
696 self.assertEqual(GET.mock_calls[-1][2][k], v)
698 # ALl options on no tty
705 self.client.download_object(obj, tmpFile, **kwargs)
706 for k, v in kwargs.items():
708 self.assertTrue('data_range' in GET.mock_calls[-1][2])
710 self.assertEqual(GET.mock_calls[-1][2][k], v)
712 def test_get_object_hashmap(self):
713 self.FR.json = object_hashmap
714 for empty in (304, 412):
717 side_effect=ClientError('Empty', status=empty)):
718 r = self.client.get_object_hashmap(obj)
719 self.assertEqual(r, {})
725 if_etag_not_match=None,
726 if_modified_since=None,
727 if_unmodified_since=None)
729 version='s0m3v3r51on',
731 if_none_match='if non match',
732 if_modified_since='some date here',
733 if_unmodified_since='some date here',
735 with patch.object(PC, 'object_get', return_value=self.FR()) as get:
736 r = self.client.get_object_hashmap(obj)
737 self.assertEqual(r, object_hashmap)
738 self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
739 r = self.client.get_object_hashmap(obj, **kwargs)
740 exp_args['if_etag_match'] = kwargs.pop('if_match')
741 exp_args['if_etag_not_match'] = kwargs.pop('if_none_match')
742 exp_args.update(kwargs)
743 self.assertEqual(get.mock_calls[-1], call(obj, **exp_args))
745 def test_set_account_group(self):
747 usernames = ['u1', 'u2', 'u3']
748 with patch.object(PC, 'account_post', return_value=self.FR()) as post:
749 self.client.set_account_group(group, usernames)
752 call(update=True, groups={group: usernames}))
754 def test_del_account_group(self):
756 with patch.object(PC, 'account_post', return_value=self.FR()) as post:
757 self.client.del_account_group(group)
760 call(update=True, groups={group: []}))
762 def test_get_account_quota(self):
763 key = 'x-account-policy-quota'
764 with patch.object(PC, 'get_account_info', return_value=account_info):
765 r = self.client.get_account_quota()
766 self.assertEqual(r[key], account_info[key])