Add example syncing tool
[pithos] / tools / test
1 #!/usr/bin/env python
2 #coding=utf8
3
4 # Copyright 2011 GRNET S.A. All rights reserved.
5
6 # Redistribution and use in source and binary forms, with or
7 # without modification, are permitted provided that the following
8 # conditions are met:
9
10 #   1. Redistributions of source code must retain the above
11 #      copyright notice, this list of conditions and the following
12 #      disclaimer.
13
14 #   2. Redistributions in binary form must reproduce the above
15 #      copyright notice, this list of conditions and the following
16 #      disclaimer in the documentation and/or other materials
17 #      provided with the distribution.
18
19 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
20 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
23 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 # POSSIBILITY OF SUCH DAMAGE.
31
32 # The views and conclusions contained in the software and
33 # documentation are those of the authors and should not be
34 # interpreted as representing official policies, either expressed
35 # or implied, of GRNET S.A.
36
37 from lib.client import Pithos_Client, Fault
38 from lib.util import get_user, get_auth, get_server, get_api
39 from xml.dom import minidom
40 from StringIO import StringIO
41 import json
42 import unittest
43 import time as _time
44 import types
45 import hashlib
46 import mimetypes
47 import random
48 import datetime
49 import string
50 import re
51
52 DATE_FORMATS = ["%a %b %d %H:%M:%S %Y",
53                 "%A, %d-%b-%y %H:%M:%S GMT",
54                 "%a, %d %b %Y %H:%M:%S GMT"]
55
56 OTHER_ACCOUNTS = {
57     '0001': 'verigak',
58     '0002': 'chazapis',
59     '0003': 'gtsouk',
60     '0004': 'papagian',
61     '0005': 'louridas',
62     '0006': 'chstath',
63     '0007': 'pkanavos',
64     '0008': 'mvasilak',
65     '0009': 'διογένης'}
66
67 class BaseTestCase(unittest.TestCase):
68     #TODO unauthorized request
69     def setUp(self):
70         self.client = Pithos_Client(get_server(), get_auth(), get_user(),
71                                     get_api())
72         self.invalid_client = Pithos_Client(get_server(), get_auth(), 'invalid',
73                                             get_api())
74         #self.headers = {
75         #    'account': ('x-account-container-count',
76         #                'x-account-bytes-used',
77         #                'last-modified',
78         #                'content-length',
79         #                'date',
80         #                'content_type',
81         #                'server',),
82         #    'object': ('etag',
83         #               'content-length',
84         #               'content_type',
85         #               'content-encoding',
86         #               'last-modified',
87         #               'date',
88         #               'x-object-manifest',
89         #               'content-range',
90         #               'x-object-modified-by',
91         #               'x-object-version',
92         #               'x-object-version-timestamp',
93         #               'server',),
94         #    'container': ('x-container-object-count',
95         #                  'x-container-bytes-used',
96         #                  'content_type',
97         #                  'last-modified',
98         #                  'content-length',
99         #                  'date',
100         #                  'x-container-block-size',
101         #                  'x-container-block-hash',
102         #                  'x-container-policy-quota',
103         #                  'x-container-policy-versioning',
104         #                  'server',
105         #                  'x-container-object-meta',
106         #                  'x-container-policy-versioning',
107         #                  'server',)}
108         #
109         #self.contentTypes = {'xml':'application/xml',
110         #                     'json':'application/json',
111         #                     '':'text/plain'}
112         self.extended = {
113             'container':(
114                 'name',
115                 'count',
116                 'bytes',
117                 'last_modified',
118                 'x_container_policy_quota',
119                 'x_container_policy_versioning',),
120             'object':(
121                 'name',
122                 'hash',
123                 'bytes',
124                 'content_type',
125                 'content_encoding',
126                 'last_modified',)}
127         self.return_codes = (400, 401, 404, 503,)
128     
129     def tearDown(self):
130         for c in self.client.list_containers():
131             while True:
132                 #list objects returns at most 10000 objects
133                 #so repeat until there are no more objects
134                 objects = self.client.list_objects(c)
135                 if not objects:
136                     break
137                 for o in objects:
138                     self.client.delete_object(c, o)
139             self.client.delete_container(c)
140     
141     def assert_status(self, status, codes):
142         l = [elem for elem in self.return_codes]
143         if type(codes) == types.ListType:
144             l.extend(codes)
145         else:
146             l.append(codes)
147         self.assertTrue(status in l)
148     
149     #def assert_headers(self, headers, type, **exp_meta):
150     #    prefix = 'x-%s-meta-' %type
151     #    system_headers = [h for h in headers if not h.startswith(prefix)]
152     #    for k,v in headers.items():
153     #        if k in system_headers:
154     #            self.assertTrue(k in headers[type])
155     #        elif exp_meta:
156     #            k = k.split(prefix)[-1]
157     #            self.assertEqual(v, exp_meta[k])
158     
159     def assert_extended(self, data, format, type, size=10000):
160         if format == 'xml':
161             self._assert_xml(data, type, size)
162         elif format == 'json':
163             self._assert_json(data, type, size)
164     
165     def _assert_json(self, data, type, size):
166         convert = lambda s: s.lower()
167         info = [convert(elem) for elem in self.extended[type]]
168         self.assertTrue(len(data) <= size)
169         for item in info:
170             for i in data:
171                 if 'subdir' in i.keys():
172                     continue
173                 self.assertTrue(item in i.keys())
174     
175     def _assert_xml(self, data, type, size):
176         convert = lambda s: s.lower()
177         info = [convert(elem) for elem in self.extended[type]]
178         try:
179             info.remove('content_encoding')
180         except ValueError:
181             pass
182         xml = data
183         entities = xml.getElementsByTagName(type)
184         self.assertTrue(len(entities) <= size)
185         for e in entities:
186             for item in info:
187                 self.assertTrue(e.getElementsByTagName(item))
188     
189     def assert_raises_fault(self, status, callableObj, *args, **kwargs):
190         """
191         asserts that a Fault with a specific status is raised
192         when callableObj is called with the specific arguments
193         """
194         try:
195             r = callableObj(*args, **kwargs)
196             self.fail('Should never reach here')
197         except Fault, f:
198             if type(status) == types.ListType:
199                 self.failUnless(f.status in status)
200             else:
201                 self.failUnless(f.status == status)
202     
203     def assert_not_raises_fault(self, status, callableObj, *args, **kwargs):
204         """
205         asserts that a Fault with a specific status is not raised
206         when callableObj is called with the specific arguments
207         """
208         try:
209             r = callableObj(*args, **kwargs)
210         except Fault, f:
211             self.failIfEqual(f.status, status)
212     
213     def assert_container_exists(self, container):
214         """
215         asserts the existence of a container
216         """
217         try:
218             self.client.retrieve_container_metadata(container)
219         except Fault, f:
220             self.failIf(f.status == 404)
221     
222     def assert_container_not_exists(self, container):
223         """
224         asserts there is no such a container
225         """
226         self.assert_raises_fault(404, self.client.retrieve_container_metadata,
227                                  container)
228     
229     def assert_object_exists(self, container, object):
230         """
231         asserts the existence of an object
232         """
233         try:
234             self.client.retrieve_object_metadata(container, object)
235         except Fault, f:
236             self.failIf(f.status == 404)
237     
238     def assert_object_not_exists(self, container, object):
239         """
240         asserts there is no such an object
241         """
242         self.assert_raises_fault(404, self.client.retrieve_object_metadata,
243                                  container, object)
244     
245     def assert_versionlist_structure(self, versionlist):
246         self.assertTrue(type(versionlist) == types.ListType)
247         for elem in versionlist:
248             self.assertTrue(type(elem) == types.ListType)
249             self.assertEqual(len(elem), 2)
250     
251     def upload_random_data(self, container, name, length=1024, type=None,
252                            enc=None, **meta):
253         data = get_random_data(length)
254         return self.upload_data(container, name, data, type, enc, **meta)
255     
256     def upload_data(self, container, name, data, type=None, enc=None, etag=None,
257                     **meta):
258         obj = {}
259         obj['name'] = name
260         try:
261             obj['data'] = data
262             obj['hash'] = compute_md5_hash(obj['data'])
263             
264             args = {}
265             args['etag'] = etag if etag else obj['hash']
266             
267             try:
268                 guess = mimetypes.guess_type(name)
269                 type = type if type else guess[0]
270                 enc = enc if enc else guess[1]
271             except:
272                 pass
273             args['content_type'] = type if type else 'plain/text'
274             args['content_encoding'] = enc if enc else None
275             
276             obj['meta'] = args
277             
278             path = '/%s/%s' % (container, name)
279             self.client.create_object(container, name, f=StringIO(obj['data']),
280                                       meta=meta, **args)
281             
282             return obj
283         except IOError:
284             return
285
286 class AccountHead(BaseTestCase):
287     def setUp(self):
288         BaseTestCase.setUp(self)
289         self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
290         for item in self.containers:
291             self.client.create_container(item)
292         
293                 #keep track of initial account groups
294         self.initial_groups = self.client.retrieve_account_groups()
295         
296         #keep track of initial account meta
297         self.initial_meta = self.client.retrieve_account_metadata(restricted=True)
298         
299         meta = {'foo':'bar'}
300         self.client.update_account_metadata(**meta)
301         self.updated_meta = self.initial_meta.update(meta)
302     
303     def tearDown(self):
304         #delete additionally created meta
305         l = []
306         for m in self.client.retrieve_account_metadata(restricted=True):
307             if m not in self.initial_meta:
308                 l.append(m)
309         self.client.delete_account_metadata(l)
310         
311         #delete additionally created groups
312         l = []
313         for g in self.client.retrieve_account_groups():
314             if g not in self.initial_groups:
315                 l.append(g)
316         self.client.unset_account_groups(l)
317         
318         BaseTestCase.tearDown(self)
319     
320     def test_get_account_meta(self):
321         meta = self.client.retrieve_account_metadata()
322         
323         containers = self.client.list_containers()
324         l = str(len(containers))
325         self.assertEqual(meta['x-account-container-count'], l)
326         size = 0
327         for c in containers:
328             m = self.client.retrieve_container_metadata(c)
329             size = size + int(m['x-container-bytes-used'])
330         self.assertEqual(meta['x-account-bytes-used'], str(size))
331     
332     def test_get_account_401(self):
333         self.assert_raises_fault(401,
334                                  self.invalid_client.retrieve_account_metadata)
335     
336     def test_get_account_meta_until(self):
337         t = datetime.datetime.utcnow()
338         past = t - datetime.timedelta(minutes=-15)
339         past = int(_time.mktime(past.timetuple()))
340         
341         meta = {'premium':True}
342         self.client.update_account_metadata(**meta)
343         meta = self.client.retrieve_account_metadata(restricted=True,
344                                                      until=past)
345         self.assertTrue('premium' not in meta)
346         
347         meta = self.client.retrieve_account_metadata(restricted=True)
348         self.assertTrue('premium' in meta)
349     
350     def test_get_account_meta_until_invalid_date(self):
351         meta = {'premium':True}
352         self.client.update_account_metadata(**meta)
353         meta = self.client.retrieve_account_metadata(restricted=True,
354                                                      until='kshfksfh')
355         self.assertTrue('premium' in meta)
356     
357 class AccountGet(BaseTestCase):
358     def setUp(self):
359         BaseTestCase.setUp(self)
360         #create some containers
361         self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
362         for item in self.containers:
363             self.client.create_container(item)
364     
365     def test_list(self):
366         #list containers
367         containers = self.client.list_containers()
368         self.assertEquals(self.containers, containers)
369     
370     def test_list_401(self):
371         self.assert_raises_fault(401, self.invalid_client.list_containers)
372     
373     def test_list_with_limit(self):
374         limit = 2
375         containers = self.client.list_containers(limit=limit)
376         self.assertEquals(len(containers), limit)
377         self.assertEquals(self.containers[:2], containers)
378     
379     def test_list_with_marker(self):
380         l = 2
381         m = 'bananas'
382         containers = self.client.list_containers(limit=l, marker=m)
383         i = self.containers.index(m) + 1
384         self.assertEquals(self.containers[i:(i+l)], containers)
385         
386         m = 'oranges'
387         containers = self.client.list_containers(limit=l, marker=m)
388         i = self.containers.index(m) + 1
389         self.assertEquals(self.containers[i:(i+l)], containers)
390     
391     def test_list_json_with_marker(self):
392         l = 2
393         m = 'bananas'
394         containers = self.client.list_containers(limit=l, marker=m, format='json')
395         self.assert_extended(containers, 'json', 'container', l)
396         self.assertEqual(containers[0]['name'], 'kiwis')
397         self.assertEqual(containers[1]['name'], 'oranges')
398     
399     def test_list_xml_with_marker(self):
400         l = 2
401         m = 'oranges'
402         xml = self.client.list_containers(limit=l, marker=m, format='xml')
403         self.assert_extended(xml, 'xml', 'container', l)
404         nodes = xml.getElementsByTagName('name')
405         self.assertEqual(len(nodes), 1)
406         self.assertEqual(nodes[0].childNodes[0].data, 'pears')
407     
408     def test_if_modified_since(self):
409         t = datetime.datetime.utcnow()
410         t2 = t - datetime.timedelta(minutes=10)
411         
412         #add a new container
413         self.client.create_container('dummy')
414         
415         for f in DATE_FORMATS:
416             past = t2.strftime(f)
417             try:
418                 c = self.client.list_containers(if_modified_since=past)
419                 self.assertEqual(len(c), len(self.containers) + 1)
420             except Fault, f:
421                 self.failIf(f.status == 304) #fail if not modified
422     
423     def test_if_modified_since_invalid_date(self):
424         c = self.client.list_containers(if_modified_since='')
425         self.assertEqual(len(c), len(self.containers))
426     
427     def test_if_not_modified_since(self):
428         now = datetime.datetime.utcnow()
429         since = now + datetime.timedelta(1)
430         
431         for f in DATE_FORMATS:
432             args = {'if_modified_since':'%s' %since.strftime(f)}
433             
434             #assert not modified
435             self.assert_raises_fault(304, self.client.list_containers, **args)
436     
437     def test_if_unmodified_since(self):
438         now = datetime.datetime.utcnow()
439         since = now + datetime.timedelta(1)
440         
441         for f in DATE_FORMATS:
442             c = self.client.list_containers(if_unmodified_since=since.strftime(f))
443             
444             #assert success
445             self.assertEqual(self.containers, c)
446     
447     def test_if_unmodified_since_precondition_failed(self):
448         t = datetime.datetime.utcnow()
449         t2 = t - datetime.timedelta(minutes=10)
450         
451         #add a new container
452         self.client.create_container('dummy')
453         
454         for f in DATE_FORMATS:
455             past = t2.strftime(f)
456             
457             args = {'if_unmodified_since':'%s' %past}
458             
459             #assert precondition failed
460             self.assert_raises_fault(412, self.client.list_containers, **args)
461     
462 class AccountPost(BaseTestCase):
463     def setUp(self):
464         BaseTestCase.setUp(self)
465         self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
466         for item in self.containers:
467             self.client.create_container(item)
468         
469         #keep track of initial account groups
470         self.initial_groups = self.client.retrieve_account_groups()
471         
472         #keep track of initial account meta
473         self.initial_meta = self.client.retrieve_account_metadata(restricted=True)
474         
475         meta = {'foo':'bar'}
476         self.client.update_account_metadata(**meta)
477         self.updated_meta = self.initial_meta.update(meta)
478     
479     def tearDown(self):
480         #delete additionally created meta
481         l = []
482         for m in self.client.retrieve_account_metadata(restricted=True):
483             if m not in self.initial_meta:
484                 l.append(m)
485         self.client.delete_account_metadata(l)
486         
487         #delete additionally created groups
488         l = []
489         for g in self.client.retrieve_account_groups():
490             if g not in self.initial_groups:
491                 l.append(g)
492         self.client.unset_account_groups(l)
493         
494         BaseTestCase.tearDown(self)
495     
496     def test_update_meta(self):
497         with AssertMappingInvariant(self.client.retrieve_account_groups):
498             meta = {'test':'test', 'tost':'tost'}
499             self.client.update_account_metadata(**meta)
500             
501             meta.update(self.initial_meta)
502             self.assertEqual(meta,
503                              self.client.retrieve_account_metadata(
504                                 restricted=True))
505         
506     def test_invalid_account_update_meta(self):
507         meta = {'test':'test', 'tost':'tost'}
508         self.assert_raises_fault(401,
509                                  self.invalid_client.update_account_metadata,
510                                  **meta)
511     
512     def test_reset_meta(self):
513         with AssertMappingInvariant(self.client.retrieve_account_groups):
514             meta = {'test':'test', 'tost':'tost'}
515             self.client.update_account_metadata(**meta)
516             
517             meta = {'test':'test33'}
518             self.client.reset_account_metadata(**meta)
519             
520             self.assertEqual(meta, self.client.retrieve_account_metadata(restricted=True))
521     
522     def test_delete_meta(self):
523         with AssertMappingInvariant(self.client.retrieve_account_groups):
524             meta = {'test':'test', 'tost':'tost'}
525             self.client.update_account_metadata(**meta)
526             
527             self.client.delete_account_metadata(meta.keys())
528             
529             account_meta = self.client.retrieve_account_metadata(restricted=True)
530             for m in meta:
531                 self.assertTrue(m not in account_meta.keys())
532     
533     def test_set_account_groups(self):
534         with AssertMappingInvariant(self.client.retrieve_account_metadata):
535             groups = {'pithosdev':'verigak,gtsouk,chazapis'}
536             self.client.set_account_groups(**groups)
537             
538             self.assertEqual(set(groups['pithosdev']),
539                              set(self.client.retrieve_account_groups()['pithosdev']))
540             
541             more_groups = {'clientsdev':'pkanavos,mvasilak'}
542             self.client.set_account_groups(**more_groups)
543             
544             groups.update(more_groups)
545             self.assertEqual(set(groups['clientsdev']),
546                              set(self.client.retrieve_account_groups()['clientsdev']))
547     
548     def test_reset_account_groups(self):
549         with AssertMappingInvariant(self.client.retrieve_account_metadata):
550             groups = {'pithosdev':'verigak,gtsouk,chazapis',
551                       'clientsdev':'pkanavos,mvasilak'}
552             self.client.set_account_groups(**groups)
553             
554             self.assertEqual(set(groups['pithosdev'].split(',')),
555                              set(self.client.retrieve_account_groups()['pithosdev'].split(',')))
556             self.assertEqual(set(groups['clientsdev'].split(',')),
557                              set(self.client.retrieve_account_groups()['clientsdev'].split(',')))
558             
559             groups = {'pithosdev':'verigak,gtsouk,chazapis,papagian'}
560             self.client.reset_account_groups(**groups)
561             
562             self.assertEqual(set(groups['pithosdev'].split(',')),
563                              set(self.client.retrieve_account_groups()['pithosdev'].split(',')))
564     
565     def test_delete_account_groups(self):
566         with AssertMappingInvariant(self.client.retrieve_account_metadata):
567             groups = {'pithosdev':'verigak,gtsouk,chazapis',
568                       'clientsdev':'pkanavos,mvasilak'}
569             self.client.set_account_groups(**groups)
570             
571             self.client.unset_account_groups(groups.keys())
572             
573             self.assertEqual({}, self.client.retrieve_account_groups())
574     
575 class ContainerHead(BaseTestCase):
576     def setUp(self):
577         BaseTestCase.setUp(self)
578         self.container = 'apples'
579         self.client.create_container(self.container)
580     
581     def test_get_meta(self):
582         meta = {'trash':'true'}
583         t1 = datetime.datetime.utcnow()
584         o = self.upload_random_data(self.container, o_names[0], **meta)
585         if o:
586             headers = self.client.retrieve_container_metadata(self.container)
587             self.assertEqual(headers['x-container-object-count'], '1')
588             self.assertEqual(headers['x-container-bytes-used'], str(len(o['data'])))
589             t2 = datetime.datetime.strptime(headers['last-modified'], DATE_FORMATS[2])
590             delta = (t2 - t1)
591             threashold = datetime.timedelta(seconds=1) 
592             self.assertTrue(delta < threashold)
593             self.assertTrue(headers['x-container-object-meta'])
594             self.assertTrue('Trash' in headers['x-container-object-meta'])
595
596 class ContainerGet(BaseTestCase):
597     def setUp(self):
598         BaseTestCase.setUp(self)
599         self.container = ['pears', 'apples']
600         for c in self.container:
601             self.client.create_container(c)
602         self.obj = []
603         for o in o_names[:8]:
604             self.obj.append(self.upload_random_data(self.container[0], o))
605         for o in o_names[8:]:
606             self.obj.append(self.upload_random_data(self.container[1], o))
607     
608     def test_list_objects(self):
609         objects = self.client.list_objects(self.container[0])
610         l = [elem['name'] for elem in self.obj[:8]]
611         l.sort()
612         self.assertEqual(objects, l)
613     
614     def test_list_objects_containing_slash(self):
615         self.client.create_container('test')
616         self.upload_random_data('test', '/objectname')
617         
618         objects = self.client.list_objects('test')
619         self.assertEqual(objects, ['/objectname'])
620         
621         objects = self.client.list_objects('test', format='json')
622         self.assertEqual(objects[0]['name'], '/objectname')
623         
624         objects = self.client.list_objects('test', format='xml')
625         self.assert_extended(objects, 'xml', 'object')
626         node_name = objects.getElementsByTagName('name')[0]
627         self.assertEqual(node_name.firstChild.data, '/objectname')
628         
629         #objects = self.client.list_objects('test', prefix='/')
630         #self.assertEqual(objects, ['/objectname'])
631         #
632         #objects = self.client.list_objects('test', path='/')
633         #self.assertEqual(objects, ['/objectname'])
634         #
635         #objects = self.client.list_objects('test', prefix='/', delimiter='n')
636         #self.assertEqual(objects, ['/object'])
637
638     def test_list_objects_with_limit_marker(self):
639         objects = self.client.list_objects(self.container[0], limit=2)
640         l = [elem['name'] for elem in self.obj[:8]]
641         l.sort()
642         self.assertEqual(objects, l[:2])
643         
644         markers = ['How To Win Friends And Influence People.pdf',
645                    'moms_birthday.jpg']
646         limit = 4
647         for m in markers:
648             objects = self.client.list_objects(self.container[0], limit=limit,
649                                                marker=m)
650             l = [elem['name'] for elem in self.obj[:8]]
651             l.sort()
652             start = l.index(m) + 1
653             end = start + limit
654             end = len(l) >= end and end or len(l)
655             self.assertEqual(objects, l[start:end])
656     
657     #takes too long
658     def _test_list_limit_exceeds(self):
659         self.client.create_container('pithos')
660         
661         for i in range(10001):
662             self.client.create_zero_length_object('pithos', i)
663         
664         self.assertEqual(10000, len(self.client.list_objects('pithos')))
665     
666     def test_list_empty_params(self):
667         objects = self.client.get('/%s/%s' % (get_user(), self.container[0]))[2]
668         if objects:
669             objects = objects.strip().split('\n')
670         self.assertEqual(objects,
671                          self.client.list_objects(self.container[0]))
672     
673     def test_list_pseudo_hierarchical_folders(self):
674         objects = self.client.list_objects(self.container[1], prefix='photos',
675                                            delimiter='/')
676         self.assertEquals(['photos/animals/', 'photos/me.jpg',
677                            'photos/plants/'], objects)
678         
679         objects = self.client.list_objects(self.container[1],
680                                            prefix='photos/animals',
681                                            delimiter='/')
682         l = ['photos/animals/cats/', 'photos/animals/dogs/']
683         self.assertEquals(l, objects)
684         
685         objects = self.client.list_objects(self.container[1], path='photos')
686         self.assertEquals(['photos/me.jpg'], objects)
687     
688     def test_extended_list_json(self):
689         objects = self.client.list_objects(self.container[1], format='json',
690                                            limit=2, prefix='photos/animals',
691                                            delimiter='/')
692         self.assertEqual(objects[0]['subdir'], 'photos/animals/cats/')
693         self.assertEqual(objects[1]['subdir'], 'photos/animals/dogs/')
694     
695     def test_extended_list_xml(self):
696         xml = self.client.list_objects(self.container[1], format='xml', limit=4,
697                                        prefix='photos', delimiter='/')
698         self.assert_extended(xml, 'xml', 'object', size=4)
699         dirs = xml.getElementsByTagName('subdir')
700         self.assertEqual(len(dirs), 2)
701         self.assertEqual(dirs[0].attributes['name'].value, 'photos/animals/')
702         self.assertEqual(dirs[1].attributes['name'].value, 'photos/plants/')
703         
704         objects = xml.getElementsByTagName('name')
705         self.assertEqual(len(objects), 1)
706         self.assertEqual(objects[0].childNodes[0].data, 'photos/me.jpg')
707     
708     def test_list_meta_double_matching(self):
709         meta = {'quality':'aaa', 'stock':'true'}
710         self.client.update_object_metadata(self.container[0],
711                                            self.obj[0]['name'], **meta)
712         obj = self.client.list_objects(self.container[0], meta='Quality,Stock')
713         self.assertEqual(len(obj), 1)
714         self.assertTrue(obj, self.obj[0]['name'])
715     
716     def test_list_using_meta(self):
717         meta = {'quality':'aaa'}
718         for o in self.obj[:2]:
719             self.client.update_object_metadata(self.container[0], o['name'],
720                                                **meta)
721         meta = {'stock':'true'}
722         for o in self.obj[3:5]:
723             self.client.update_object_metadata(self.container[0], o['name'],
724                                                **meta)
725         
726         obj = self.client.list_objects(self.container[0], meta='Quality')
727         self.assertEqual(len(obj), 2)
728         self.assertTrue(obj, [o['name'] for o in self.obj[:2]])
729         
730         # test case insensitive
731         obj = self.client.list_objects(self.container[0], meta='quality')
732         self.assertEqual(len(obj), 2)
733         self.assertTrue(obj, [o['name'] for o in self.obj[:2]])
734         
735         # test multiple matches
736         obj = self.client.list_objects(self.container[0], meta='Quality,Stock')
737         self.assertEqual(len(obj), 4)
738         self.assertTrue(obj, [o['name'] for o in self.obj[:4]])
739         
740         # test non 1-1 multiple match
741         obj = self.client.list_objects(self.container[0], meta='Quality,aaaa')
742         self.assertEqual(len(obj), 2)
743         self.assertTrue(obj, [o['name'] for o in self.obj[:2]])
744     
745     def test_if_modified_since(self):
746         t = datetime.datetime.utcnow()
747         t2 = t - datetime.timedelta(minutes=10)
748         
749         #add a new object
750         self.upload_random_data(self.container[0], o_names[0])
751         
752         for f in DATE_FORMATS:
753             past = t2.strftime(f)
754             try:
755                 o = self.client.list_objects(self.container[0],
756                                             if_modified_since=past)
757                 self.assertEqual(o,
758                                  self.client.list_objects(self.container[0]))
759             except Fault, f:
760                 self.failIf(f.status == 304) #fail if not modified
761     
762     def test_if_modified_since_invalid_date(self):
763         headers = {'if-modified-since':''}
764         o = self.client.list_objects(self.container[0], if_modified_since='')
765         self.assertEqual(o, self.client.list_objects(self.container[0]))
766     
767     def test_if_not_modified_since(self):
768         now = datetime.datetime.utcnow()
769         since = now + datetime.timedelta(1)
770         
771         for f in DATE_FORMATS:
772             args = {'if_modified_since':'%s' %since.strftime(f)}
773             
774             #assert not modified
775             self.assert_raises_fault(304, self.client.list_objects,
776                                      self.container[0], **args)
777     
778     def test_if_unmodified_since(self):
779         now = datetime.datetime.utcnow()
780         since = now + datetime.timedelta(1)
781         
782         for f in DATE_FORMATS:
783             obj = self.client.list_objects(self.container[0],
784                                            if_unmodified_since=since.strftime(f))
785             
786             #assert unmodified
787             self.assertEqual(obj, self.client.list_objects(self.container[0]))
788     
789     def test_if_unmodified_since_precondition_failed(self):
790         t = datetime.datetime.utcnow()
791         t2 = t - datetime.timedelta(minutes=10)
792         
793         #add a new container
794         self.client.create_container('dummy')
795         
796         for f in DATE_FORMATS:
797             past = t2.strftime(f)
798             
799             args = {'if_unmodified_since':'%s' %past}
800             
801             #assert precondition failed
802             self.assert_raises_fault(412, self.client.list_objects,
803                                      self.container[0], **args)
804
805 class ContainerPut(BaseTestCase):
806     def setUp(self):
807         BaseTestCase.setUp(self)
808         self.containers = ['c1', 'c2']
809     
810     def test_create(self):
811         self.client.create_container(self.containers[0])
812         containers = self.client.list_containers()
813         self.assertTrue(self.containers[0] in containers)
814         self.assert_container_exists(self.containers[0])
815     
816     def test_create_twice(self):
817         self.client.create_container(self.containers[0])
818         self.assertTrue(not self.client.create_container(self.containers[0]))
819     
820 class ContainerPost(BaseTestCase):
821     def setUp(self):
822         BaseTestCase.setUp(self)
823         self.container = 'apples'
824         self.client.create_container(self.container)
825     
826     def test_update_meta(self):
827         meta = {'test':'test33',
828                 'tost':'tost22'}
829         self.client.update_container_metadata(self.container, **meta)
830         headers = self.client.retrieve_container_metadata(self.container)
831         for k,v in meta.items():
832             k = 'x-container-meta-%s' % k
833             self.assertTrue(headers[k])
834             self.assertEqual(headers[k], v)
835
836 class ContainerDelete(BaseTestCase):
837     def setUp(self):
838         BaseTestCase.setUp(self)
839         self.containers = ['c1', 'c2']
840         for c in self.containers:
841             self.client.create_container(c)
842         self.upload_random_data(self.containers[1], o_names[0])
843     
844     def test_delete(self):
845         status = self.client.delete_container(self.containers[0])[0]
846         self.assertEqual(status, 204)
847     
848     def test_delete_non_empty(self):
849         self.assert_raises_fault(409, self.client.delete_container,
850                                  self.containers[1])
851     
852     def test_delete_invalid(self):
853         self.assert_raises_fault(404, self.client.delete_container, 'c3')
854
855 class ObjectHead(BaseTestCase):
856     pass
857
858 class ObjectGet(BaseTestCase):
859     def setUp(self):
860         BaseTestCase.setUp(self)
861         self.containers = ['c1', 'c2']
862         #create some containers
863         for c in self.containers:
864             self.client.create_container(c)
865         
866         #upload a file
867         names = ('obj1', 'obj2')
868         self.objects = []
869         for n in names:
870             self.objects.append(self.upload_random_data(self.containers[1], n))
871     
872     def test_versions(self):
873         c = self.containers[1]
874         o = self.objects[0]
875         b = self.client.retrieve_object_versionlist(c, o['name'])['versions']
876         self.assert_versionlist_structure(b)
877         
878         #update meta
879         meta = {'quality':'AAA', 'stock':True}
880         self.client.update_object_metadata(c, o['name'], **meta)
881         
882         a = self.client.retrieve_object_versionlist(c, o['name'])['versions']
883         self.assert_versionlist_structure(a)
884         self.assertEqual(len(b)+1, len(a))
885         self.assertEqual(b, a[:-1])
886         
887         #get exact previous version metadata
888         v = a[-2][0]
889         v_meta = self.client.retrieve_object_metadata(c, o['name'],
890                                                       restricted=True,
891                                                       version=v)
892         for k in meta.keys():
893             self.assertTrue(k not in v_meta)
894         
895         #update obejct
896         data = get_random_data()
897         self.client.update_object(c, o['name'], StringIO(data))
898         
899         aa = self.client.retrieve_object_versionlist(c, o['name'])['versions']
900         self.assert_versionlist_structure(aa)
901         self.assertEqual(len(a)+1, len(aa))
902         self.assertEqual(a, aa[:-1])
903         
904         #get exact previous version
905         v = aa[-3][0]
906         v_data = self.client.retrieve_object_version(c, o['name'], version=v)
907         self.assertEqual(o['data'], v_data)
908         self.assertEqual(self.client.retrieve_object(c, o['name']),
909                          '%s%s' %(v_data, data))
910     
911     def test_get(self):
912         #perform get
913         o = self.client.retrieve_object(self.containers[1],
914                                         self.objects[0]['name'],
915                                         self.objects[0]['meta'])
916         self.assertEqual(o, self.objects[0]['data'])
917     
918     def test_get_invalid(self):
919         self.assert_raises_fault(404, self.client.retrieve_object,
920                                  self.containers[0], self.objects[0]['name'])
921     
922     def test_get_partial(self):
923         #perform get with range
924         status, headers, data = self.client.request_object(self.containers[1],
925                                                             self.objects[0]['name'],
926                                                             range='bytes=0-499')
927         
928         #assert successful partial content
929         self.assertEqual(status, 206)
930         
931         #assert content-type
932         self.assertEqual(headers['content-type'],
933                          self.objects[0]['meta']['content_type'])
934         
935         #assert content length
936         self.assertEqual(int(headers['content-length']), 500)
937         
938         #assert content
939         self.assertEqual(self.objects[0]['data'][:500], data)
940     
941     def test_get_final_500(self):
942         #perform get with range
943         headers = {'range':'bytes=-500'}
944         status, headers, data = self.client.request_object(self.containers[1],
945                                                             self.objects[0]['name'],
946                                                             range='bytes=-500')
947         
948         #assert successful partial content
949         self.assertEqual(status, 206)
950         
951         #assert content-type
952         self.assertEqual(headers['content-type'],
953                          self.objects[0]['meta']['content_type'])
954         
955         #assert content length
956         self.assertEqual(int(headers['content-length']), 500)
957         
958         #assert content
959         self.assertTrue(self.objects[0]['data'][-500:], data)
960     
961     def test_get_rest(self):
962         #perform get with range
963         offset = len(self.objects[0]['data']) - 500
964         status, headers, data = self.client.request_object(self.containers[1],
965                                                 self.objects[0]['name'],
966                                                 range='bytes=%s-' %offset)
967         
968         #assert successful partial content
969         self.assertEqual(status, 206)
970         
971         #assert content-type
972         self.assertEqual(headers['content-type'],
973                          self.objects[0]['meta']['content_type'])
974         
975         #assert content length
976         self.assertEqual(int(headers['content-length']), 500)
977         
978         #assert content
979         self.assertTrue(self.objects[0]['data'][-500:], data)
980     
981     def test_get_range_not_satisfiable(self):
982         #perform get with range
983         offset = len(self.objects[0]['data']) + 1
984         
985         #assert range not satisfiable
986         self.assert_raises_fault(416, self.client.retrieve_object,
987                                  self.containers[1], self.objects[0]['name'],
988                                  range='bytes=0-%s' %offset)
989     
990     def test_multiple_range(self):
991         #perform get with multiple range
992         ranges = ['0-499', '-500', '1000-']
993         bytes = 'bytes=%s' % ','.join(ranges)
994         status, headers, data = self.client.request_object(self.containers[1],
995                                                            self.objects[0]['name'],
996                                                            range=bytes)
997         
998         # assert partial content
999         self.assertEqual(status, 206)
1000         
1001         # assert Content-Type of the reply will be multipart/byteranges
1002         self.assertTrue(headers['content-type'])
1003         content_type_parts = headers['content-type'].split()
1004         self.assertEqual(content_type_parts[0], ('multipart/byteranges;'))
1005         
1006         boundary = '--%s' %content_type_parts[1].split('=')[-1:][0]
1007         cparts = data.split(boundary)[1:-1]
1008         
1009         # assert content parts are exactly 2
1010         self.assertEqual(len(cparts), len(ranges))
1011         
1012         # for each content part assert headers
1013         i = 0
1014         for cpart in cparts:
1015             content = cpart.split('\r\n')
1016             headers = content[1:3]
1017             content_range = headers[0].split(': ')
1018             self.assertEqual(content_range[0], 'Content-Range')
1019             
1020             r = ranges[i].split('-')
1021             if not r[0] and not r[1]:
1022                 pass
1023             elif not r[0]:
1024                 start = len(self.objects[0]['data']) - int(r[1])
1025                 end = len(self.objects[0]['data'])
1026             elif not r[1]:
1027                 start = int(r[0])
1028                 end = len(self.objects[0]['data'])
1029             else:
1030                 start = int(r[0])
1031                 end = int(r[1]) + 1
1032             fdata = self.objects[0]['data'][start:end]
1033             sdata = '\r\n'.join(content[4:-1])
1034             self.assertEqual(len(fdata), len(sdata))
1035             self.assertEquals(fdata, sdata)
1036             i+=1
1037     
1038     def test_multiple_range_not_satisfiable(self):
1039         #perform get with multiple range
1040         out_of_range = len(self.objects[0]['data']) + 1
1041         ranges = ['0-499', '-500', '%d-' %out_of_range]
1042         bytes = 'bytes=%s' % ','.join(ranges)
1043         
1044         # assert partial content
1045         self.assert_raises_fault(416, self.client.retrieve_object,
1046                                  self.containers[1],
1047                                  self.objects[0]['name'], range=bytes)
1048     
1049     def test_get_with_if_match(self):
1050         #perform get with If-Match
1051         etag = self.objects[0]['hash']
1052         status, headers, data = self.client.request_object(self.containers[1],
1053                                                            self.objects[0]['name'],
1054                                                            if_match=etag)
1055         #assert get success
1056         self.assertEqual(status, 200)
1057         
1058         #assert content-type
1059         self.assertEqual(headers['content-type'],
1060                          self.objects[0]['meta']['content_type'])
1061         
1062         #assert response content
1063         self.assertEqual(self.objects[0]['data'], data)
1064     
1065     def test_get_with_if_match_star(self):
1066         #perform get with If-Match *
1067         headers = {'if-match':'*'}
1068         status, headers, data = self.client.request_object(self.containers[1],
1069                                                 self.objects[0]['name'],
1070                                                 **headers)
1071         #assert get success
1072         self.assertEqual(status, 200)
1073         
1074         #assert content-type
1075         self.assertEqual(headers['content-type'],
1076                          self.objects[0]['meta']['content_type'])
1077         
1078         #assert response content
1079         self.assertEqual(self.objects[0]['data'], data)
1080     
1081     def test_get_with_multiple_if_match(self):
1082         #perform get with If-Match
1083         etags = [i['hash'] for i in self.objects if i]
1084         etags = ','.join('"%s"' % etag for etag in etags)
1085         status, headers, data = self.client.request_object(self.containers[1],
1086                                                            self.objects[0]['name'],
1087                                                            if_match=etags)
1088         #assert get success
1089         self.assertEqual(status, 200)
1090         
1091         #assert content-type
1092         self.assertEqual(headers['content-type'],
1093                          self.objects[0]['meta']['content_type'])
1094         
1095         #assert content-type
1096         self.assertEqual(headers['content-type'],
1097                          self.objects[0]['meta']['content_type'])
1098         
1099         #assert response content
1100         self.assertEqual(self.objects[0]['data'], data)
1101     
1102     def test_if_match_precondition_failed(self):
1103         #assert precondition failed
1104         self.assert_raises_fault(412, self.client.retrieve_object,
1105                                  self.containers[1],
1106                                  self.objects[0]['name'], if_match='123')
1107     
1108     def test_if_none_match(self):
1109         #perform get with If-None-Match
1110         status, headers, data = self.client.request_object(self.containers[1],
1111                                                            self.objects[0]['name'],
1112                                                            if_none_match='123')
1113         
1114         #assert get success
1115         self.assertEqual(status, 200)
1116         
1117         #assert content-type
1118         self.assertEqual(headers['content_type'],
1119                          self.objects[0]['meta']['content_type'])
1120     
1121     def test_if_none_match(self):
1122         #perform get with If-None-Match * and assert not modified
1123         self.assert_raises_fault(304, self.client.retrieve_object,
1124                                  self.containers[1],
1125                                  self.objects[0]['name'],
1126                                  if_none_match='*')
1127     
1128     def test_if_none_match_not_modified(self):
1129         #perform get with If-None-Match and assert not modified
1130         self.assert_raises_fault(304, self.client.retrieve_object,
1131                                  self.containers[1],
1132                                  self.objects[0]['name'],
1133                                  if_none_match=self.objects[0]['hash'])
1134         
1135         meta = self.client.retrieve_object_metadata(self.containers[1],
1136                                                     self.objects[0]['name'])
1137         self.assertEqual(meta['etag'], self.objects[0]['hash'])
1138     
1139     def test_if_modified_since(self):
1140         t = datetime.datetime.utcnow()
1141         t2 = t - datetime.timedelta(minutes=10)
1142         
1143         #modify the object
1144         self.upload_data(self.containers[1],
1145                            self.objects[0]['name'],
1146                            self.objects[0]['data'][:200])
1147         
1148         for f in DATE_FORMATS:
1149             past = t2.strftime(f)
1150             
1151             headers = {'if-modified-since':'%s' %past}
1152             try:
1153                 o = self.client.retrieve_object(self.containers[1],
1154                                                 self.objects[0]['name'],
1155                                                 if_modified_since=past)
1156                 self.assertEqual(o,
1157                                  self.client.retrieve_object(self.containers[1],
1158                                                              self.objects[0]['name']))
1159             except Fault, f:
1160                 self.failIf(f.status == 304)
1161     
1162     def test_if_modified_since_invalid_date(self):
1163         o = self.client.retrieve_object(self.containers[1],
1164                                         self.objects[0]['name'],
1165                                         if_modified_since='')
1166         self.assertEqual(o, self.client.retrieve_object(self.containers[1],
1167                                                         self.objects[0]['name']))
1168             
1169     def test_if_not_modified_since(self):
1170         now = datetime.datetime.utcnow()
1171         since = now + datetime.timedelta(1)
1172         
1173         for f in DATE_FORMATS:
1174             #assert not modified
1175             self.assert_raises_fault(304, self.client.retrieve_object,
1176                                      self.containers[1], self.objects[0]['name'],
1177                                      if_modified_since=since.strftime(f))
1178     
1179     def test_if_unmodified_since(self):
1180         now = datetime.datetime.utcnow()
1181         since = now + datetime.timedelta(1)
1182         
1183         for f in DATE_FORMATS:
1184             t = since.strftime(f)
1185             status, headers, data = self.client.request_object(self.containers[1],
1186                                                                self.objects[0]['name'],
1187                                                                if_unmodified_since=t)
1188             #assert success
1189             self.assertEqual(status, 200)
1190             self.assertEqual(self.objects[0]['data'], data)
1191             
1192             #assert content-type
1193             self.assertEqual(headers['content-type'],
1194                              self.objects[0]['meta']['content_type'])
1195     
1196     def test_if_unmodified_since_precondition_failed(self):
1197         t = datetime.datetime.utcnow()
1198         t2 = t - datetime.timedelta(minutes=10)
1199         
1200         #modify the object
1201         self.upload_data(self.containers[1],
1202                            self.objects[0]['name'],
1203                            self.objects[0]['data'][:200])
1204         
1205         for f in DATE_FORMATS:
1206             past = t2.strftime(f)
1207             #assert precondition failed
1208             self.assert_raises_fault(412, self.client.retrieve_object,
1209                                      self.containers[1], self.objects[0]['name'],
1210                                      if_unmodified_since=past)
1211     
1212     def test_hashes(self):
1213         l = 8388609
1214         fname = 'largefile'
1215         o = self.upload_random_data(self.containers[1], fname, l)
1216         if o:
1217             body = self.client.retrieve_object(self.containers[1], fname,
1218                                                format='json')
1219             hashes = body['hashes']
1220             block_size = body['block_size']
1221             block_hash = body['block_hash']
1222             block_num = l/block_size == 0 and l/block_size or l/block_size + 1
1223             self.assertTrue(len(hashes), block_num)
1224             i = 0
1225             for h in hashes:
1226                 start = i * block_size
1227                 end = (i + 1) * block_size
1228                 hash = compute_block_hash(o['data'][start:end], block_hash)
1229                 self.assertEqual(h, hash)
1230                 i += 1
1231
1232 class ObjectPut(BaseTestCase):
1233     def setUp(self):
1234         BaseTestCase.setUp(self)
1235         self.container = 'c1'
1236         self.client.create_container(self.container)
1237     
1238     def test_upload(self):
1239         name = o_names[0]
1240         meta = {'test':'test1'}
1241         o = self.upload_random_data(self.container, name, **meta)
1242         
1243         headers = self.client.retrieve_object_metadata(self.container,
1244                                                        name,
1245                                                        restricted=True)
1246         self.assertTrue('test' in headers.keys())
1247         self.assertEqual(headers['test'], meta['test'])
1248         
1249         #assert uploaded content
1250         status, h, data = self.client.request_object(self.container, name)
1251         self.assertEqual(len(o['data']), int(h['content-length']))
1252         self.assertEqual(o['data'], data)
1253         
1254         #assert content-type
1255         self.assertEqual(h['content-type'], o['meta']['content_type'])
1256     
1257     def _test_maximum_upload_size_exceeds(self):
1258         name = o_names[0]
1259         meta = {'test':'test1'}
1260         #upload 100MB
1261         length=1024*1024*100
1262         self.assert_raises_fault(400, self.upload_random_data, self.container,
1263                                  name, length, **meta)
1264     
1265     def test_upload_with_name_containing_slash(self):
1266         name = '/%s' % o_names[0]
1267         meta = {'test':'test1'}
1268         o = self.upload_random_data(self.container, name, **meta)
1269         
1270         self.assertEqual(o['data'],
1271                          self.client.retrieve_object(self.container, name))
1272         
1273         self.assertTrue(name in self.client.list_objects(self.container))
1274     
1275     def test_create_directory_marker(self):
1276         self.client.create_directory_marker(self.container, 'foo')
1277         meta = self.client.retrieve_object_metadata(self.container, 'foo')
1278         self.assertEqual(meta['content-length'], '0')
1279         self.assertEqual(meta['content-type'], 'application/directory')
1280
1281     def test_upload_unprocessable_entity(self):
1282         meta={'etag':'123', 'test':'test1'}
1283         
1284         #assert unprocessable entity
1285         self.assert_raises_fault(422, self.upload_random_data, self.container,
1286                                  o_names[0], **meta)
1287     
1288     def test_chunked_transfer(self):
1289         data = get_random_data()
1290         objname = 'object'
1291         self.client.create_object_using_chunks(self.container, objname,
1292                                                StringIO(data))
1293         
1294         uploaded_data = self.client.retrieve_object(self.container, objname)
1295         self.assertEqual(data, uploaded_data)
1296     
1297     def test_manifestation(self):
1298         prefix = 'myobject/'
1299         data = ''
1300         for i in range(5):
1301             part = '%s%d' %(prefix, i)
1302             o = self.upload_random_data(self.container, part)
1303             data += o['data']
1304         
1305         manifest = '%s/%s' %(self.container, prefix)
1306         self.client.create_manifestation(self.container, 'large-object', manifest)
1307         
1308         self.assert_object_exists(self.container, 'large-object')
1309         self.assertEqual(data, self.client.retrieve_object(self.container,
1310                                                            'large-object'))
1311         
1312         #wrong manifestation
1313         self.client.create_manifestation(self.container, 'large-object',
1314                                          '%s/invalid' % self.container)
1315         self.assertEqual('', self.client.retrieve_object(self.container,
1316                                                          'large-object'))
1317     
1318     def test_create_zero_length_object(self):
1319         c = self.container
1320         o = 'object'
1321         zero = self.client.create_zero_length_object(c, o)
1322         zero_meta = self.client.retrieve_object_metadata(c, o)
1323         zero_hash = self.client.retrieve_object_hashmap(c, o)
1324         zero_data = self.client.retrieve_object(c, o)
1325         
1326         self.assertEqual(int(zero_meta['content-length']), 0)
1327         self.assertEqual(zero_hash, [])
1328         self.assertEqual(zero_data, '')
1329     
1330     def test_create_object_by_hashmap(self):
1331         c = self.container
1332         o = 'object'
1333         self.upload_random_data(c, o)
1334         hashmap = self.client.retrieve_object(c, o, format='json')
1335         o2 = 'object-copy'
1336         self.client.create_object_by_hashmap(c, o2, hashmap)
1337         self.assertEqual(self.client.retrieve_object(c, o),
1338                          self.client.retrieve_object(c, o))
1339
1340 class ObjectCopy(BaseTestCase):
1341     def setUp(self):
1342         BaseTestCase.setUp(self)
1343         self.containers = ['c1', 'c2']
1344         for c in self.containers:
1345             self.client.create_container(c)
1346         self.obj = self.upload_random_data(self.containers[0], o_names[0])
1347         
1348     def test_copy(self):
1349         with AssertMappingInvariant(self.client.retrieve_object_metadata,
1350                              self.containers[0], self.obj['name']):
1351             #perform copy
1352             meta = {'test':'testcopy'}
1353             status = self.client.copy_object(self.containers[0],
1354                                               self.obj['name'],
1355                                               self.containers[0],
1356                                               'testcopy',
1357                                               meta)[0]
1358             
1359             #assert copy success
1360             self.assertEqual(status, 201)
1361             
1362             #assert access the new object
1363             headers = self.client.retrieve_object_metadata(self.containers[0],
1364                                                            'testcopy')
1365             self.assertTrue('x-object-meta-test' in headers.keys())
1366             self.assertTrue(headers['x-object-meta-test'], 'testcopy')
1367             
1368             #assert etag is the same
1369             self.assertEqual(headers['etag'], self.obj['hash'])
1370             
1371             #assert src object still exists
1372             self.assert_object_exists(self.containers[0], self.obj['name'])
1373     
1374     def test_copy_from_different_container(self):
1375         with AssertMappingInvariant(self.client.retrieve_object_metadata,
1376                              self.containers[0], self.obj['name']):
1377             meta = {'test':'testcopy'}
1378             status = self.client.copy_object(self.containers[0],
1379                                              self.obj['name'],
1380                                              self.containers[1],
1381                                              'testcopy',
1382                                              meta)[0]
1383             self.assertEqual(status, 201)
1384             
1385             # assert updated metadata
1386             meta = self.client.retrieve_object_metadata(self.containers[1],
1387                                                            'testcopy',
1388                                                            restricted=True)
1389             self.assertTrue('test' in meta.keys())
1390             self.assertTrue(meta['test'], 'testcopy')
1391             
1392             #assert src object still exists
1393             self.assert_object_exists(self.containers[0], self.obj['name'])
1394     
1395     def test_copy_invalid(self):
1396         #copy from invalid object
1397         meta = {'test':'testcopy'}
1398         self.assert_raises_fault(404, self.client.copy_object, self.containers[0],
1399                                  'test.py', self.containers[1], 'testcopy', meta)
1400         
1401         #copy from invalid container
1402         meta = {'test':'testcopy'}
1403         self.assert_raises_fault(404, self.client.copy_object, self.containers[1],
1404                                  self.obj['name'], self.containers[1],
1405                                  'testcopy', meta)
1406
1407 class ObjectMove(BaseTestCase):
1408     def setUp(self):
1409         BaseTestCase.setUp(self)
1410         self.containers = ['c1', 'c2']
1411         for c in self.containers:
1412             self.client.create_container(c)
1413         self.obj = self.upload_random_data(self.containers[0], o_names[0])
1414     
1415     def test_move(self):
1416         #perform move
1417         meta = {'test':'testcopy'}
1418         src_path = '/'.join(('/', self.containers[0], self.obj['name']))
1419         status = self.client.move_object(self.containers[0], self.obj['name'],
1420                                          self.containers[0], 'testcopy',
1421                                          meta)[0]
1422         
1423         #assert successful move
1424         self.assertEqual(status, 201)
1425         
1426         #assert updated metadata
1427         meta = self.client.retrieve_object_metadata(self.containers[0],
1428                                                     'testcopy',
1429                                                     restricted=True)
1430         self.assertTrue('test' in meta.keys())
1431         self.assertTrue(meta['test'], 'testcopy')
1432         
1433         #assert src object no more exists
1434         self.assert_object_not_exists(self.containers[0], self.obj['name'])
1435
1436 class ObjectPost(BaseTestCase):
1437     def setUp(self):
1438         BaseTestCase.setUp(self)
1439         self.containers = ['c1', 'c2']
1440         for c in self.containers:
1441             self.client.create_container(c)
1442         self.obj = []
1443         for i in range(2):
1444             self.obj.append(self.upload_random_data(self.containers[0], o_names[i]))
1445     
1446     def test_update_meta(self):
1447         #perform update metadata
1448         more = {'foo':'foo', 'bar':'bar'}
1449         status = self.client.update_object_metadata(self.containers[0],
1450                                                     self.obj[0]['name'],
1451                                                     **more)[0]
1452         #assert request accepted
1453         self.assertEqual(status, 202)
1454         
1455         #assert old metadata are still there
1456         headers = self.client.retrieve_object_metadata(self.containers[0],
1457                                                        self.obj[0]['name'],
1458                                                        restricted=True)
1459         #assert new metadata have been updated
1460         for k,v in more.items():
1461             self.assertTrue(k in headers.keys())
1462             self.assertTrue(headers[k], v)
1463     
1464     def test_update_object(self,
1465                            first_byte_pos=0,
1466                            last_byte_pos=499,
1467                            instance_length = True,
1468                            content_length = 500):
1469         l = len(self.obj[0]['data'])
1470         range = 'bytes %d-%d/%s' %(first_byte_pos,
1471                                        last_byte_pos,
1472                                         l if instance_length else '*')
1473         partial = last_byte_pos - first_byte_pos + 1
1474         length = first_byte_pos + partial
1475         data = get_random_data(partial)
1476         args = {'content_type':'application/octet-stream',
1477                 'content_range':'%s' %range}
1478         if content_length:
1479             args['content_length'] = content_length
1480         
1481         status = self.client.update_object(self.containers[0], self.obj[0]['name'],
1482                                   StringIO(data), **args)[0]
1483         
1484         if partial < 0 or (instance_length and l <= last_byte_pos):
1485             self.assertEqual(status, 202)    
1486         else:
1487             self.assertEqual(status, 204)           
1488             #check modified object
1489             content = self.client.retrieve_object(self.containers[0],
1490                                               self.obj[0]['name'])
1491             self.assertEqual(content[:first_byte_pos], self.obj[0]['data'][:first_byte_pos])
1492             self.assertEqual(content[first_byte_pos:last_byte_pos+1], data)
1493             self.assertEqual(content[last_byte_pos+1:], self.obj[0]['data'][last_byte_pos+1:])
1494     
1495     def test_update_object_lt_blocksize(self):
1496         self.test_update_object(10, 20, content_length=None)
1497     
1498     def test_update_object_gt_blocksize(self):
1499         o = self.upload_random_data(self.containers[0], o_names[1],
1500                                 length=4*1024*1024+5)
1501         c = self.containers[0]
1502         o_name = o['name']
1503         o_data = o['data']
1504         first_byte_pos = 4*1024*1024+1
1505         last_byte_pos = 4*1024*1024+4
1506         l = last_byte_pos - first_byte_pos + 1
1507         data = get_random_data(l)
1508         range = 'bytes %d-%d/*' %(first_byte_pos, last_byte_pos)
1509         self.client.update_object(c, o_name, StringIO(data), content_range=range)
1510         content = self.client.retrieve_object(c, o_name)
1511         self.assertEqual(content[:first_byte_pos], o_data[:first_byte_pos])
1512         self.assertEqual(content[first_byte_pos:last_byte_pos+1], data)
1513         self.assertEqual(content[last_byte_pos+1:], o_data[last_byte_pos+1:])    
1514     
1515     def test_update_object_divided_by_blocksize(self):
1516         o = self.upload_random_data(self.containers[0], o_names[1],
1517                                 length=4*1024*1024+5)
1518         c = self.containers[0]
1519         o_name = o['name']
1520         o_data = o['data']
1521         first_byte_pos = 4*1024*1024
1522         last_byte_pos = 5*1024*1024
1523         l = last_byte_pos - first_byte_pos + 1
1524         data = get_random_data(l)
1525         range = 'bytes %d-%d/*' %(first_byte_pos, last_byte_pos)
1526         self.client.update_object(c, o_name, StringIO(data), content_range=range)
1527         content = self.client.retrieve_object(c, o_name)
1528         self.assertEqual(content[:first_byte_pos], o_data[:first_byte_pos])
1529         self.assertEqual(content[first_byte_pos:last_byte_pos+1], data)
1530         self.assertEqual(content[last_byte_pos+1:], o_data[last_byte_pos+1:])    
1531     
1532     def test_update_object_no_content_length(self):
1533         self.test_update_object(content_length = None)
1534     
1535     def test_update_object_invalid_content_length(self):
1536         with AssertContentInvariant(self.client.retrieve_object,
1537                                     self.containers[0], self.obj[0]['name']):
1538             self.assert_raises_fault(400, self.test_update_object,
1539                                      content_length = 1000)
1540     
1541     def test_update_object_invalid_range(self):
1542         with AssertContentInvariant(self.client.retrieve_object,
1543                                     self.containers[0], self.obj[0]['name']):
1544             self.assert_raises_fault(416, self.test_update_object, 499, 0, True)
1545     
1546     def test_update_object_invalid_range_and_length(self):
1547         with AssertContentInvariant(self.client.retrieve_object,
1548                                     self.containers[0], self.obj[0]['name']):
1549             self.assert_raises_fault([400, 416], self.test_update_object, 499, 0, True,
1550                                      -1)
1551     
1552     def test_update_object_invalid_range_with_no_content_length(self):
1553         with AssertContentInvariant(self.client.retrieve_object,
1554                                     self.containers[0], self.obj[0]['name']):
1555             self.assert_raises_fault(416, self.test_update_object, 499, 0, True,
1556                                      content_length = None)
1557     
1558     def test_update_object_out_of_limits(self):    
1559         with AssertContentInvariant(self.client.retrieve_object,
1560                                     self.containers[0], self.obj[0]['name']):
1561             l = len(self.obj[0]['data'])
1562             self.assert_raises_fault(416, self.test_update_object, 0, l+1, True)
1563     
1564     def test_append(self):
1565         data = get_random_data(500)
1566         headers = {}
1567         self.client.update_object(self.containers[0], self.obj[0]['name'],
1568                                   StringIO(data), content_length=500,
1569                                   content_type='application/octet-stream')
1570         
1571         content = self.client.retrieve_object(self.containers[0],
1572                                               self.obj[0]['name'])
1573         self.assertEqual(len(content), len(self.obj[0]['data']) + 500)
1574         self.assertEqual(content[:-500], self.obj[0]['data'])
1575     
1576     def test_update_with_chunked_transfer(self):
1577         data = get_random_data(500)
1578         dl = len(data)
1579         fl = len(self.obj[0]['data'])
1580         
1581         self.client.update_object_using_chunks(self.containers[0],
1582                                                self.obj[0]['name'],
1583                                                StringIO(data),
1584                                                offset=0,
1585                                                content_type='application/octet-stream')
1586         
1587         #check modified object
1588         content = self.client.retrieve_object(self.containers[0],
1589                                               self.obj[0]['name'])
1590         self.assertEqual(content[0:dl], data)
1591         self.assertEqual(content[dl:fl], self.obj[0]['data'][dl:fl])
1592     
1593     def test_update_from_other_object(self):
1594         c = self.containers[0]
1595         src = o_names[0]
1596         dest = 'object'
1597         
1598         source_data = self.client.retrieve_object(c, src)
1599         source_meta = self.client.retrieve_object_metadata(c, src)
1600         source_hash = self.client.retrieve_object_hashmap(c, src)
1601         
1602         #update zero length object
1603         self.client.create_zero_length_object(c, dest)
1604         source_object = '/%s/%s' % (c, src)
1605         self.client.update_from_other_source(c, dest, source_object)
1606         dest_data = self.client.retrieve_object(c, src)
1607         dest_meta = self.client.retrieve_object_metadata(c, dest)
1608         dest_hash = self.client.retrieve_object_hashmap(c, src)
1609         self.assertEqual(source_data, dest_data)
1610         self.assertEqual(source_hash, dest_hash)
1611         
1612         #test append
1613         self.client.update_from_other_source(c, dest, source_object)
1614         content = self.client.retrieve_object(c, dest)
1615         self.assertEqual(source_data * 2, content)
1616     
1617     def test_update_range_from_other_object(self):
1618         c = self.containers[0]
1619         dest = 'object'
1620         
1621         #test update range
1622         src = self.obj[1]['name']
1623         src_data = self.client.retrieve_object(c, src)
1624         
1625         #update zero length object
1626         prev_data = self.upload_random_data(c, dest, length=4*1024*1024+10)['data']
1627         source_object = '/%s/%s' % (c, src)
1628         first_byte_pos = 4*1024*1024+1
1629         last_byte_pos = 4*1024*1024+4
1630         range = 'bytes %d-%d/*' %(first_byte_pos, last_byte_pos)
1631         self.client.update_from_other_source(c, dest, source_object,
1632                                              content_range=range)
1633         content = self.client.retrieve_object(c, dest)
1634         self.assertEqual(content[:first_byte_pos], prev_data[:first_byte_pos])
1635         self.assertEqual(content[first_byte_pos:last_byte_pos+1], src_data[:last_byte_pos - first_byte_pos + 1])
1636         self.assertEqual(content[last_byte_pos+1:], prev_data[last_byte_pos+1:])
1637     
1638     def test_update_hashes_from_other_object(self):
1639         c = self.containers[0]
1640         dest = 'object'
1641         
1642         #test update range
1643         src_data = self.upload_random_data(c, o_names[0], length=1024*1024+10)['data']
1644         
1645         #update zero length object
1646         prev_data = self.upload_random_data(c, dest, length=5*1024*1024+10)['data']
1647         source_object = '/%s/%s' % (c, o_names[0])
1648         first_byte_pos = 4*1024*1024
1649         last_byte_pos = 5*1024*1024
1650         range = 'bytes %d-%d/*' %(first_byte_pos, last_byte_pos)
1651         self.client.update_from_other_source(c, dest, source_object,
1652                                              content_range=range)
1653         content = self.client.retrieve_object(c, dest)
1654         self.assertEqual(content[:first_byte_pos], prev_data[:first_byte_pos])
1655         self.assertEqual(content[first_byte_pos:last_byte_pos+1], src_data[:last_byte_pos - first_byte_pos + 1])
1656         self.assertEqual(content[last_byte_pos+1:], prev_data[last_byte_pos+1:])
1657     
1658     
1659     def test_update_zero_length_object(self):
1660         c = self.containers[0]
1661         o = 'object'
1662         other = 'other'
1663         zero = self.client.create_zero_length_object(c, o)
1664         
1665         data = get_random_data()
1666         self.client.update_object(c, o, StringIO(data))
1667         self.client.create_object(c, other, StringIO(data))
1668         
1669         self.assertEqual(self.client.retrieve_object(c, o),
1670                          self.client.retrieve_object(c, other))
1671         
1672         self.assertEqual(self.client.retrieve_object_hashmap(c, o),
1673                          self.client.retrieve_object_hashmap(c, other))
1674     
1675 class ObjectDelete(BaseTestCase):
1676     def setUp(self):
1677         BaseTestCase.setUp(self)
1678         self.containers = ['c1', 'c2']
1679         for c in self.containers:
1680             self.client.create_container(c)
1681         self.obj = self.upload_random_data(self.containers[0], o_names[0])
1682     
1683     def test_delete(self):
1684         #perform delete object
1685         self.client.delete_object(self.containers[0], self.obj['name'])[0]
1686     
1687     def test_delete_invalid(self):
1688         #assert item not found
1689         self.assert_raises_fault(404, self.client.delete_object, self.containers[1],
1690                                  self.obj['name'])
1691
1692 class ListSharing(BaseTestCase):
1693     def setUp(self):
1694         BaseTestCase.setUp(self)
1695         for i in range(2):
1696             self.client.create_container('c%s' %i)
1697         self.client.create_container('c')
1698         for i in range(2):
1699             self.upload_random_data('c1', 'o%s' %i)
1700         accounts = OTHER_ACCOUNTS.copy()
1701         self.o1_sharing_with = accounts.popitem()
1702         self.o1_sharing = [self.o1_sharing_with[1]]
1703         self.client.share_object('c1', 'o1', self.o1_sharing, read=True)
1704         
1705         l = []
1706         for i in range(2):
1707             l.append(accounts.popitem())
1708     
1709     def test_list_other_shared(self):
1710         self.other = Pithos_Client(get_server(),
1711                               self.o1_sharing_with[0],
1712                               self.o1_sharing_with[1],
1713                               get_api())
1714         self.assertTrue(get_user() in self.other.list_shared_by_others())
1715     
1716     def test_list_my_shared(self):
1717         my_shared_containers = self.client.list_containers(shared=True)
1718         self.assertTrue('c1' in my_shared_containers)
1719         self.assertTrue('c2' not in my_shared_containers)
1720         
1721         my_shared_objects = self.client.list_objects('c1', shared=True)
1722         self.assertTrue('o1' in my_shared_objects)
1723         self.assertTrue('o2' not in my_shared_objects)
1724     
1725 class TestGreek(BaseTestCase):
1726     def setUp(self):
1727         BaseTestCase.setUp(self)
1728         #keep track of initial account groups
1729         self.initial_groups = self.client.retrieve_account_groups()
1730         
1731         #keep track of initial account meta
1732         self.initial_meta = self.client.retrieve_account_metadata(restricted=True)
1733     
1734     def tearDown(self):
1735         #delete additionally created meta
1736         l = []
1737         for m in self.client.retrieve_account_metadata(restricted=True):
1738             if m not in self.initial_meta:
1739                 l.append(m)
1740         self.client.delete_account_metadata(l)
1741         
1742         #delete additionally created groups
1743         l = []
1744         for g in self.client.retrieve_account_groups():
1745             if g not in self.initial_groups:
1746                 l.append(g)
1747         self.client.unset_account_groups(l)
1748         
1749         BaseTestCase.tearDown(self)
1750     
1751     def test_create_container(self):
1752         self.client.create_container('φάκελος')
1753         self.assert_container_exists('φάκελος')
1754         
1755         self.assertTrue('φάκελος' in self.client.list_containers())
1756     
1757     def test_create_object(self):
1758         self.client.create_container('φάκελος')
1759         self.upload_random_data('φάκελος', 'αντικείμενο')
1760         
1761         self.assert_object_exists('φάκελος', 'αντικείμενο')
1762         self.assertTrue('αντικείμενο' in self.client.list_objects('φάκελος'))
1763     
1764     def test_copy_object(self):
1765         src_container = 'φάκελος'
1766         src_object = 'αντικείμενο'
1767         dest_container = 'αντίγραφα'
1768         dest_object = 'ασφαλές-αντίγραφο'
1769         
1770         self.client.create_container(src_container)
1771         self.upload_random_data(src_container, src_object)
1772         
1773         self.client.create_container(dest_container)
1774         self.client.copy_object(src_container, src_object, dest_container,
1775                                 dest_object)
1776         
1777         self.assert_object_exists(src_container, src_object)
1778         self.assert_object_exists(dest_container, dest_object)
1779         self.assertTrue(dest_object in self.client.list_objects(dest_container))
1780     
1781     def test_move_object(self):
1782         src_container = 'φάκελος'
1783         src_object = 'αντικείμενο'
1784         dest_container = 'αντίγραφα'
1785         dest_object = 'ασφαλές-αντίγραφο'
1786         
1787         self.client.create_container(src_container)
1788         self.upload_random_data(src_container, src_object)
1789         
1790         self.client.create_container(dest_container)
1791         self.client.move_object(src_container, src_object, dest_container,
1792                                 dest_object)
1793         
1794         self.assert_object_not_exists(src_container, src_object)
1795         self.assert_object_exists(dest_container, dest_object)
1796         self.assertTrue(dest_object in self.client.list_objects(dest_container))
1797     
1798     def test_delete_object(self):
1799         self.client.create_container('φάκελος')
1800         self.upload_random_data('φάκελος', 'αντικείμενο')
1801         self.assert_object_exists('φάκελος', 'αντικείμενο')
1802     
1803         self.client.delete_object('φάκελος', 'αντικείμενο')
1804         self.assert_object_not_exists('φάκελος', 'αντικείμενο')
1805         self.assertTrue('αντικείμενο' not in self.client.list_objects('φάκελος'))
1806     
1807     def test_delete_container(self):
1808         self.client.create_container('φάκελος')
1809         self.assert_container_exists('φάκελος')
1810         
1811         self.client.delete_container('φάκελος')
1812         self.assert_container_not_exists('φάκελος')
1813         self.assertTrue('φάκελος' not in self.client.list_containers())
1814
1815     def test_account_meta(self):
1816         meta = {'ποιότητα':'ΑΑΑ'}
1817         self.client.update_account_metadata(**meta)
1818         meta = self.client.retrieve_account_metadata(restricted=True)
1819         self.assertTrue('ποιότητα' in meta.keys())
1820         self.assertEqual(meta['ποιότητα'], 'ΑΑΑ')
1821     
1822     def test_container_meta(self):
1823         meta = {'ποιότητα':'ΑΑΑ'}
1824         self.client.create_container('φάκελος', **meta)
1825         
1826         meta = self.client.retrieve_container_metadata('φάκελος', restricted=True)
1827         self.assertTrue('ποιότητα' in meta.keys())
1828         self.assertEqual(meta['ποιότητα'], 'ΑΑΑ')
1829     
1830     def test_object_meta(self):
1831         self.client.create_container('φάκελος')
1832         meta = {'ποιότητα':'ΑΑΑ'}
1833         self.upload_random_data('φάκελος', 'αντικείμενο', **meta)
1834         
1835         meta = self.client.retrieve_object_metadata('φάκελος', 'αντικείμενο',
1836                                                     restricted=True)
1837         self.assertTrue('ποιότητα' in meta.keys())
1838         self.assertEqual(meta['ποιότητα'], 'ΑΑΑ')    
1839     
1840     def test_list_meta_filtering(self):
1841         self.client.create_container('φάκελος')
1842         meta = {'ποιότητα':'ΑΑΑ'}
1843         self.upload_random_data('φάκελος', 'ο1', **meta)
1844         self.upload_random_data('φάκελος', 'ο2')
1845         self.upload_random_data('φάκελος', 'ο3')
1846         
1847         meta = {'ποσότητα':'μεγάλη'}
1848         self.client.update_object_metadata('φάκελος', 'ο2', **meta)
1849         objects = self.client.list_objects('φάκελος', meta='ποιότητα, ποσότητα')
1850         self.assertTrue('ο1' in objects)
1851         self.assertTrue('ο2' in objects)
1852         self.assertTrue('ο3' not in objects)
1853     
1854     def test_groups(self):
1855         #create a group
1856         groups = {'σεφς':'chazapis,διογένης'}
1857         self.client.set_account_groups(**groups)
1858         groups.update(self.initial_groups)
1859         self.assertEqual(groups['σεφς'],
1860                          self.client.retrieve_account_groups()['σεφς'])
1861         
1862         #check read access
1863         self.client.create_container('φάκελος')
1864         o = self.upload_random_data('φάκελος', 'ο1')
1865         self.client.share_object('φάκελος', 'ο1', ['%s:σεφς' % get_user()])
1866         chef = Pithos_Client(get_server(),
1867                             '0009',
1868                             'διογένης',
1869                             get_api())
1870         self.assert_not_raises_fault(401, chef.retrieve_object_metadata,
1871                                      'φάκελος', 'ο1', account=get_user())
1872         
1873         #check write access
1874         self.client.share_object('φάκελος', 'ο1', ['διογένης'], read=False)
1875         new_data = get_random_data()
1876         self.assert_not_raises_fault(401, chef.update_object,
1877                                      'φάκελος', 'ο1', StringIO(new_data),
1878                                      account=get_user())
1879         
1880         server_data = self.client.retrieve_object('φάκελος', 'ο1')
1881         self.assertEqual(server_data[:len(o['data'])], o['data'])
1882         self.assertEqual(server_data[len(o['data']):], new_data)
1883     
1884     def test_manifestation(self):
1885         self.client.create_container('κουβάς')
1886         prefix = 'μέρη/'
1887         data = ''
1888         for i in range(5):
1889             part = '%s%d' %(prefix, i)
1890             o = self.upload_random_data('κουβάς', part)
1891             data += o['data']
1892         
1893         self.client.create_container('φάκελος')
1894         manifest = '%s/%s' %('κουβάς', prefix)
1895         self.client.create_manifestation('φάκελος', 'άπαντα', manifest)
1896         
1897         self.assert_object_exists('φάκελος', 'άπαντα')
1898         self.assertEqual(data, self.client.retrieve_object('φάκελος',
1899                                                            'άπαντα'))
1900         
1901         #wrong manifestation
1902         self.client.create_manifestation('φάκελος', 'άπαντα', 'κουβάς/άκυρο')
1903         self.assertEqual('', self.client.retrieve_object('φάκελος', 'άπαντα'))
1904     
1905     def test_update_from_another_object(self):
1906         self.client.create_container('κουβάς')
1907         src_data = self.upload_random_data('κουβάς', 'πηγή')['data']
1908         initial_data = self.upload_random_data('κουβάς', 'νέο')['data']
1909         source_object = '/%s/%s' % ('κουβάς', 'πηγή')
1910         self.client.update_from_other_source('κουβάς', 'νέο', source_object)
1911         
1912         self.assertEqual(
1913             self.client.retrieve_object('κουβάς', 'νέο'),
1914             '%s%s' % (initial_data, self.client.retrieve_object('κουβάς', 'πηγή')))
1915
1916 class TestPermissions(BaseTestCase):
1917     def setUp(self):
1918         BaseTestCase.setUp(self)
1919         #keep track of initial account groups
1920         self.initial_groups = self.client.retrieve_account_groups()
1921         #keep track of initial account meta
1922         self.initial_meta = self.client.retrieve_account_metadata(restricted=True)
1923         
1924         #create a group
1925         self.authorized = ['chazapis', 'verigak', 'gtsouk']
1926         groups = {'pithosdev':','.join(self.authorized)}
1927         self.client.set_account_groups(**groups)
1928     
1929     def tearDown(self):
1930         #delete additionally created meta
1931         l = []
1932         for m in self.client.retrieve_account_metadata(restricted=True):
1933             if m not in self.initial_meta:
1934                 l.append(m)
1935         self.client.delete_account_metadata(l)
1936         
1937         #delete additionally created groups
1938         l = []
1939         for g in self.client.retrieve_account_groups():
1940             if g not in self.initial_groups:
1941                 l.append(g)
1942         self.client.unset_account_groups(l)
1943         
1944         BaseTestCase.tearDown(self)
1945     
1946     def assert_read(self, authorized=[], any=False):
1947         for token, account in OTHER_ACCOUNTS.items():
1948             cl = Pithos_Client(get_server(), token, account, get_api()) 
1949             if account in authorized or any:
1950                 self.assert_not_raises_fault(401, cl.retrieve_object_metadata,
1951                                              'c', 'o', account=get_user())
1952             else:
1953                 self.assert_raises_fault(401, cl.retrieve_object_metadata,
1954                                          'c', 'o', account=get_user())
1955         
1956         #check inheritance
1957         o = self.upload_random_data('c', 'o/also-shared')
1958         for token, account in OTHER_ACCOUNTS.items():
1959             cl = Pithos_Client(get_server(), token, account, get_api()) 
1960             if account in authorized or any:
1961                 self.assert_not_raises_fault(401, cl.retrieve_object_metadata,
1962                                              'c', 'o/also-shared', account=get_user())
1963             else:
1964                 self.assert_raises_fault(401, cl.retrieve_object_metadata,
1965                                          'c', 'o/also-shared', account=get_user())
1966     
1967     def assert_write(self, o_data, authorized=[], any=False):
1968         for token, account in OTHER_ACCOUNTS.items():
1969             cl = Pithos_Client(get_server(), token, account, get_api()) 
1970             new_data = get_random_data()
1971             if account in authorized or any:
1972                 # test write access
1973                 self.assert_not_raises_fault(401, cl.update_object,
1974                                              'c', 'o', StringIO(new_data),
1975                                              account=get_user())
1976                 try:
1977                     # test read access
1978                     server_data = cl.retrieve_object('c', 'o', account=get_user())
1979                     self.assertEqual(o_data, server_data[:len(o_data)])
1980                     self.assertEqual(new_data, server_data[len(o_data):])
1981                     o_data = server_data
1982                 except Fault, f:
1983                     self.failIf(f.status == 401)
1984             else:
1985                 self.assert_raises_fault(401, cl.update_object,
1986                                              'c', 'o', StringIO(new_data),
1987                                              account=get_user())
1988         
1989         #check inheritance
1990         o = self.upload_random_data('c', 'o/also-shared')
1991         o_data = o['data']
1992         for token, account in OTHER_ACCOUNTS.items():
1993             cl = Pithos_Client(get_server(), token, account, get_api()) 
1994             new_data = get_random_data()
1995             if account in authorized or any:
1996                 # test write access
1997                 self.assert_not_raises_fault(401, cl.update_object,
1998                                              'c', o['name'],
1999                                              StringIO(new_data),
2000                                              account=get_user())
2001                 try:
2002                     server_data = cl.retrieve_object('c', o['name'], account=get_user())
2003                     self.assertEqual(o_data, server_data[:len(o_data)])
2004                     self.assertEqual(new_data, server_data[len(o_data):])
2005                     o_data = server_data
2006                 except Fault, f:
2007                     self.failIf(f.status == 401)
2008             else:
2009                 self.assert_raises_fault(401, cl.update_object,
2010                                              'c', o['name'],
2011                                              StringIO(new_data),
2012                                              account=get_user())
2013     
2014     def test_group_read(self):
2015         self.client.create_container('c')
2016         o = self.upload_random_data('c', 'o')
2017         self.client.share_object('c', 'o', ['%s:pithosdev' % get_user()])
2018         self.assert_read(authorized=self.authorized)
2019     
2020     def test_read_many(self):
2021         #test read access
2022         self.client.create_container('c')
2023         o = self.upload_random_data('c', 'o')
2024         self.client.share_object('c', 'o', self.authorized)
2025         self.assert_read(authorized=self.authorized)
2026     
2027     def test_read_by_everyone(self):
2028         self.client.create_container('c')
2029         o = self.upload_random_data('c', 'o')
2030         self.client.share_object('c', 'o', ['*'])
2031         self.assert_read(any=True)
2032     
2033     def test_group_write(self):
2034         self.client.create_container('c')
2035         o = self.upload_random_data('c', 'o')
2036         self.client.share_object('c', 'o', ['%s:pithosdev' % get_user()], read=False)
2037         self.assert_write(o['data'], authorized=self.authorized)
2038     
2039     def test_write_many(self):
2040         self.client.create_container('c')
2041         o = self.upload_random_data('c', 'o')
2042         self.client.share_object('c', 'o', self.authorized, read=False)
2043         self.assert_write(o['data'], authorized=self.authorized)
2044     
2045     def test_write_by_everyone(self):
2046         self.client.create_container('c')
2047         o = self.upload_random_data('c', 'o')
2048         self.client.share_object('c', 'o', ['*'], read=False)
2049         o_data = o['data']
2050         self.assert_write(o['data'], any=True)
2051
2052 class TestPublish(BaseTestCase):
2053     def test_publish(self):
2054         self.client.create_container('c')
2055         o_data = self.upload_random_data('c', 'o')['data']
2056         self.client.publish_object('c', 'o')
2057         meta = self.client.retrieve_object_metadata('c', 'o')
2058         self.assertTrue('x-object-public' in meta)
2059         url = '/public/%s/c/o' % get_user()
2060         self.assertEqual(meta['x-object-public'], url)
2061         public_client = Pithos_Client(get_server(), get_auth(), get_user(), api='')
2062         data = public_client.get(url)[2]
2063         self.assertEqual(o_data, data)
2064
2065 class AssertMappingInvariant(object):
2066     def __init__(self, callable, *args, **kwargs):
2067         self.callable = callable
2068         self.args = args
2069         self.kwargs = kwargs
2070     
2071     def __enter__(self):
2072         self.map = self.callable(*self.args, **self.kwargs)
2073         return self.map
2074     
2075     def __exit__(self, type, value, tb):
2076         map = self.callable(*self.args, **self.kwargs)
2077         for k in self.map.keys():
2078             if is_date(self.map[k]):
2079                 continue
2080             assert map[k] == self.map[k]
2081
2082 class AssertContentInvariant(object):
2083     def __init__(self, callable, *args, **kwargs):
2084         self.callable = callable
2085         self.args = args
2086         self.kwargs = kwargs
2087     
2088     def __enter__(self):
2089         self.content = self.callable(*self.args, **self.kwargs)[2]
2090         return self.content
2091     
2092     def __exit__(self, type, value, tb):
2093         content = self.callable(*self.args, **self.kwargs)[2]
2094         assert self.content == content
2095
2096 def get_content_splitted(response):
2097     if response:
2098         return response.content.split('\n')
2099
2100 def compute_md5_hash(data):
2101     md5 = hashlib.md5()
2102     offset = 0
2103     md5.update(data)
2104     return md5.hexdigest().lower()
2105
2106 def compute_block_hash(data, algorithm):
2107     h = hashlib.new(algorithm)
2108     h.update(data.rstrip('\x00'))
2109     return h.hexdigest()
2110
2111 def get_random_data(length=500):
2112     char_set = string.ascii_uppercase + string.digits
2113     return ''.join(random.choice(char_set) for x in range(length))
2114
2115 def is_date(date):
2116     MONTHS = 'jan feb mar apr may jun jul aug sep oct nov dec'.split()
2117     __D = r'(?P<day>\d{2})'
2118     __D2 = r'(?P<day>[ \d]\d)'
2119     __M = r'(?P<mon>\w{3})'
2120     __Y = r'(?P<year>\d{4})'
2121     __Y2 = r'(?P<year>\d{2})'
2122     __T = r'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})'
2123     RFC1123_DATE = re.compile(r'^\w{3}, %s %s %s %s GMT$' % (__D, __M, __Y, __T))
2124     RFC850_DATE = re.compile(r'^\w{6,9}, %s-%s-%s %s GMT$' % (__D, __M, __Y2, __T))
2125     ASCTIME_DATE = re.compile(r'^\w{3} %s %s %s %s$' % (__M, __D2, __T, __Y))
2126     for regex in RFC1123_DATE, RFC850_DATE, ASCTIME_DATE:
2127         m = regex.match(date)
2128         if m is not None:
2129             return True
2130     return False
2131
2132 o_names = ['kate.jpg',
2133            'kate_beckinsale.jpg',
2134            'How To Win Friends And Influence People.pdf',
2135            'moms_birthday.jpg',
2136            'poodle_strut.mov',
2137            'Disturbed - Down With The Sickness.mp3',
2138            'army_of_darkness.avi',
2139            'the_mad.avi',
2140            'photos/animals/dogs/poodle.jpg',
2141            'photos/animals/dogs/terrier.jpg',
2142            'photos/animals/cats/persian.jpg',
2143            'photos/animals/cats/siamese.jpg',
2144            'photos/plants/fern.jpg',
2145            'photos/plants/rose.jpg',
2146            'photos/me.jpg']
2147
2148 if __name__ == "__main__":
2149     if get_user() == 'test':
2150         unittest.main()
2151     else:
2152         print 'Will not run tests as any other user except \'test\' (current user: %s).' % get_user()