Statistics
| Branch: | Tag: | Revision:

root / pithos / api / tests.py @ ae75584f

History | View | Annotate | Download (65.9 kB)

1
#coding=utf8
2

    
3
# Copyright 2011 GRNET S.A. All rights reserved.
4
# 
5
# Redistribution and use in source and binary forms, with or
6
# without modification, are permitted provided that the following
7
# conditions are met:
8
# 
9
#   1. Redistributions of source code must retain the above
10
#      copyright notice, this list of conditions and the following
11
#      disclaimer.
12
# 
13
#   2. Redistributions in binary form must reproduce the above
14
#      copyright notice, this list of conditions and the following
15
#      disclaimer in the documentation and/or other materials
16
#      provided with the distribution.
17
# 
18
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
# POSSIBILITY OF SUCH DAMAGE.
30
# 
31
# The views and conclusions contained in the software and
32
# documentation are those of the authors and should not be
33
# interpreted as representing official policies, either expressed
34
# or implied, of GRNET S.A.
35

    
36
from pithos.lib.client import Pithos_Client, Fault
37
from django.utils import simplejson as json
38
from xml.dom import minidom
39
from StringIO import StringIO
40
import unittest
41
import time as _time
42
import types
43
import hashlib
44
import os
45
import mimetypes
46
import random
47
import datetime
48
import string
49
import re
50

    
51
DATE_FORMATS = ["%a %b %d %H:%M:%S %Y",
52
                "%A, %d-%b-%y %H:%M:%S GMT",
53
                "%a, %d %b %Y %H:%M:%S GMT"]
54

    
55
DEFAULT_HOST = 'pithos.dev.grnet.gr'
56
#DEFAULT_HOST = '127.0.0.1:8000'
57
DEFAULT_API = 'v1'
58
DEFAULT_USER = 'test'
59
DEFAULT_AUTH = '0000'
60

    
61
OTHER_ACCOUNTS = {
62
    '0001': 'verigak',
63
    '0002': 'chazapis',
64
    '0003': 'gtsouk',
65
    '0004': 'papagian',
66
    '0005': 'louridas',
67
    '0006': 'chstath',
68
    '0007': 'pkanavos',
69
    '0008': 'mvasilak'}
70

    
71
class BaseTestCase(unittest.TestCase):
72
    #TODO unauthorized request
73
    def setUp(self):
74
        self.client = Pithos_Client(DEFAULT_HOST,
75
                                    DEFAULT_AUTH,
76
                                    DEFAULT_USER,
77
                                    DEFAULT_API)
78
        self.invalid_client = Pithos_Client(DEFAULT_HOST,
79
                                                  DEFAULT_AUTH,
80
                                                  'invalid',
81
                                                  DEFAULT_API)
82
        #self.headers = {
83
        #    'account': ('x-account-container-count',
84
        #                'x-account-bytes-used',
85
        #                'last-modified',
86
        #                'content-length',
87
        #                'date',
88
        #                'content_type',
89
        #                'server',),
90
        #    'object': ('etag',
91
        #               'content-length',
92
        #               'content_type',
93
        #               'content-encoding',
94
        #               'last-modified',
95
        #               'date',
96
        #               'x-object-manifest',
97
        #               'content-range',
98
        #               'x-object-modified-by',
99
        #               'x-object-version',
100
        #               'x-object-version-timestamp',
101
        #               'server',),
102
        #    'container': ('x-container-object-count',
103
        #                  'x-container-bytes-used',
104
        #                  'content_type',
105
        #                  'last-modified',
106
        #                  'content-length',
107
        #                  'date',
108
        #                  'x-container-block-size',
109
        #                  'x-container-block-hash',
110
        #                  'x-container-policy-quota',
111
        #                  'x-container-policy-versioning',
112
        #                  'server',
113
        #                  'x-container-object-meta',
114
        #                  'x-container-policy-versioning',
115
        #                  'server',)}
116
        #
117
        #self.contentTypes = {'xml':'application/xml',
118
        #                     'json':'application/json',
119
        #                     '':'text/plain'}
120
        self.extended = {
121
            'container':(
122
                'name',
123
                'count',
124
                'bytes',
125
                'last_modified',
126
                'x_container_policy_quota',
127
                'x_container_policy_versioning',),
128
            'object':(
129
                'name',
130
                'hash',
131
                'bytes',
132
                'content_type',
133
                'content_encoding',
134
                'last_modified',)}
135
        self.return_codes = (400, 401, 404, 503,)
136
    
137
    def tearDown(self):
138
        for c in self.client.list_containers():
139
            while True:
140
                #list objects returns at most 10000 objects
141
                #so repeat until there are no more objects
142
                objects = self.client.list_objects(c)
143
                if not objects:
144
                    break
145
                for o in objects:
146
                    self.client.delete_object(c, o)
147
            self.client.delete_container(c)
148
    
149
    def assert_status(self, status, codes):
150
        l = [elem for elem in self.return_codes]
151
        if type(codes) == types.ListType:
152
            l.extend(codes)
153
        else:
154
            l.append(codes)
155
        self.assertTrue(status in l)
156
    
157
    #def assert_headers(self, headers, type, **exp_meta):
158
    #    prefix = 'x-%s-meta-' %type
159
    #    system_headers = [h for h in headers if not h.startswith(prefix)]
160
    #    for k,v in headers.items():
161
    #        if k in system_headers:
162
    #            self.assertTrue(k in headers[type])
163
    #        elif exp_meta:
164
    #            k = k.split(prefix)[-1]
165
    #            self.assertEqual(v, exp_meta[k])
166
    
167
    def assert_extended(self, data, format, type, size=10000):
168
        if format == 'xml':
169
            self._assert_xml(data, type, size)
170
        elif format == 'json':
171
            self._assert_json(data, type, size)
172
    
173
    def _assert_json(self, data, type, size):
174
        convert = lambda s: s.lower()
175
        info = [convert(elem) for elem in self.extended[type]]
176
        self.assertTrue(len(data) <= size)
177
        for item in info:
178
            for i in data:
179
                if 'subdir' in i.keys():
180
                    continue
181
                self.assertTrue(item in i.keys())
182
    
183
    def _assert_xml(self, data, type, size):
184
        convert = lambda s: s.lower()
185
        info = [convert(elem) for elem in self.extended[type]]
186
        try:
187
            info.remove('content_encoding')
188
        except ValueError:
189
            pass
190
        xml = data
191
        entities = xml.getElementsByTagName(type)
192
        self.assertTrue(len(entities) <= size)
193
        for e in entities:
194
            for item in info:
195
                self.assertTrue(e.getElementsByTagName(item))
196
    
197
    def assert_raises_fault(self, status, callableObj, *args, **kwargs):
198
        """
199
        asserts that a Fault with a specific status is raised
200
        when callableObj is called with the specific arguments
201
        """
202
        try:
203
            r = callableObj(*args, **kwargs)
204
            self.fail('Should never reach here')
205
        except Fault, f:
206
            self.failUnless(f.status == status)
207
    
208
    def assert_container_exists(self, container):
209
        """
210
        asserts the existence of a container
211
        """
212
        try:
213
            self.client.retrieve_container_metadata(container)
214
        except Fault, f:
215
            self.failIf(f.status == 404)
216
    
217
    def assert_object_exists(self, container, object):
218
        """
219
        asserts the existence of an object
220
        """
221
        try:
222
            self.client.retrieve_object_metadata(container, object)
223
        except Fault, f:
224
            self.failIf(f.status == 404)
225
    
226
    def assert_object_not_exists(self, container, object):
227
        """
228
        asserts there is no such an object
229
        """
230
        self.assert_raises_fault(404, self.client.retrieve_object_metadata,
231
                                 container, object)
232
    
233
    def upload_random_data(self, container, name, length=1024, type=None,
234
                           enc=None, **meta):
235
        data = get_random_data(length)
236
        return self.upload_data(container, name, data, type, enc, **meta)
237
    
238
    def upload_data(self, container, name, data, type=None, enc=None, etag=None,
239
                    **meta):
240
        obj = {}
241
        obj['name'] = name
242
        try:
243
            obj['data'] = data
244
            obj['hash'] = compute_md5_hash(obj['data'])
245
            
246
            args = {}
247
            args['etag'] = etag if etag else obj['hash']
248
            
249
            try:
250
                guess = mimetypes.guess_type(name)
251
                type = type if type else guess[0]
252
                enc = enc if enc else guess[1]
253
            except:
254
                pass
255
            args['content_type'] = type if type else 'plain/text'
256
            args['content_encoding'] = enc if enc else None
257
            
258
            obj['meta'] = args
259
            
260
            path = '/%s/%s' % (container, name)
261
            self.client.create_object(container, name, StringIO(obj['data']),
262
                                      meta, **args)
263
            
264
            return obj
265
        except IOError:
266
            return
267
class TopLevel(BaseTestCase):
268
    def test_list_shared_by_other(self):
269
        pass
270

    
271
class AccountHead(BaseTestCase):
272
    def setUp(self):
273
        BaseTestCase.setUp(self)
274
        self.account = 'test'
275
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
276
        for item in self.containers:
277
            self.client.create_container(item)
278
        
279
                #keep track of initial account groups
280
        self.initial_groups = self.client.retrieve_account_groups()
281
        
282
        #keep track of initial account meta
283
        self.initial_meta = self.client.retrieve_account_metadata(restricted=True)
284
        
285
        meta = {'foo':'bar'}
286
        self.client.update_account_metadata(**meta)
287
        self.updated_meta = self.initial_meta.update(meta)
288
    
289
    def tearDown(self):
290
        #delete additionally created meta
291
        l = []
292
        for m in self.client.retrieve_account_metadata(restricted=True):
293
            if m not in self.initial_meta:
294
                l.append(m)
295
        self.client.delete_account_metadata(l)
296
        
297
        #delete additionally created groups
298
        l = []
299
        for g in self.client.retrieve_account_groups():
300
            if g not in self.initial_groups:
301
                l.append(g)
302
        self.client.unset_account_groups(l)
303
        
304
        #print '#', self.client.retrieve_account_groups()
305
        #print '#', self.client.retrieve_account_metadata(restricted=True)
306
        BaseTestCase.tearDown(self)
307
    
308
    def test_get_account_meta(self):
309
        meta = self.client.retrieve_account_metadata()
310
        
311
        containers = self.client.list_containers()
312
        l = str(len(containers))
313
        self.assertEqual(meta['x-account-container-count'], l)
314
        size = 0
315
        for c in containers:
316
            m = self.client.retrieve_container_metadata(c)
317
            size = size + int(m['x-container-bytes-used'])
318
        self.assertEqual(meta['x-account-bytes-used'], str(size))
319
    
320
    def test_get_account_401(self):
321
        self.assert_raises_fault(401,
322
                                 self.invalid_client.retrieve_account_metadata)
323
    
324
    def test_get_account_meta_until(self):
325
        t = datetime.datetime.utcnow()
326
        past = t - datetime.timedelta(minutes=-15)
327
        past = int(_time.mktime(past.timetuple()))
328
        
329
        meta = {'premium':True}
330
        self.client.update_account_metadata(**meta)
331
        meta = self.client.retrieve_account_metadata(restricted=True,
332
                                                     until=past)
333
        self.assertTrue('premium' not in meta)
334
        
335
        meta = self.client.retrieve_account_metadata(restricted=True)
336
        self.assertTrue('premium' in meta)
337
    
338
    def test_get_account_meta_until_invalid_date(self):
339
        meta = {'premium':True}
340
        self.client.update_account_metadata(**meta)
341
        meta = self.client.retrieve_account_metadata(restricted=True,
342
                                                     until='kshfksfh')
343
        self.assertTrue('premium' in meta)
344
    
345
class AccountGet(BaseTestCase):
346
    def setUp(self):
347
        BaseTestCase.setUp(self)
348
        self.account = 'test'
349
        #create some containers
350
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
351
        for item in self.containers:
352
            self.client.create_container(item)
353
    
354
    def test_list(self):
355
        #list containers
356
        containers = self.client.list_containers()
357
        self.assertEquals(self.containers, containers)
358
    
359
    def test_list_401(self):
360
        self.assert_raises_fault(401, self.invalid_client.list_containers)
361
    
362
    def test_list_with_limit(self):
363
        limit = 2
364
        containers = self.client.list_containers(limit=limit)
365
        self.assertEquals(len(containers), limit)
366
        self.assertEquals(self.containers[:2], containers)
367
    
368
    def test_list_with_marker(self):
369
        l = 2
370
        m = 'bananas'
371
        containers = self.client.list_containers(limit=l, marker=m)
372
        i = self.containers.index(m) + 1
373
        self.assertEquals(self.containers[i:(i+l)], containers)
374
        
375
        m = 'oranges'
376
        containers = self.client.list_containers(limit=l, marker=m)
377
        i = self.containers.index(m) + 1
378
        self.assertEquals(self.containers[i:(i+l)], containers)
379
    
380
    def test_list_json_with_marker(self):
381
        l = 2
382
        m = 'bananas'
383
        containers = self.client.list_containers(limit=l, marker=m, format='json')
384
        self.assert_extended(containers, 'json', 'container', l)
385
        self.assertEqual(containers[0]['name'], 'kiwis')
386
        self.assertEqual(containers[1]['name'], 'oranges')
387
    
388
    def test_list_xml_with_marker(self):
389
        l = 2
390
        m = 'oranges'
391
        xml = self.client.list_containers(limit=l, marker=m, format='xml')
392
        self.assert_extended(xml, 'xml', 'container', l)
393
        nodes = xml.getElementsByTagName('name')
394
        self.assertEqual(len(nodes), 1)
395
        self.assertEqual(nodes[0].childNodes[0].data, 'pears')
396
    
397
    def test_if_modified_since(self):
398
        t = datetime.datetime.utcnow()
399
        t2 = t - datetime.timedelta(minutes=10)
400
        
401
        #add a new container
402
        self.client.create_container('dummy')
403
        
404
        for f in DATE_FORMATS:
405
            past = t2.strftime(f)
406
            try:
407
                c = self.client.list_containers(if_modified_since=past)
408
                self.assertEqual(len(c), len(self.containers) + 1)
409
            except Fault, f:
410
                self.failIf(f.status == 304) #fail if not modified
411
    
412
    def test_if_modified_since_invalid_date(self):
413
        c = self.client.list_containers(if_modified_since='')
414
        self.assertEqual(len(c), len(self.containers))
415
    
416
    def test_if_not_modified_since(self):
417
        now = datetime.datetime.utcnow()
418
        since = now + datetime.timedelta(1)
419
        
420
        for f in DATE_FORMATS:
421
            args = {'if_modified_since':'%s' %since.strftime(f)}
422
            
423
            #assert not modified
424
            self.assert_raises_fault(304, self.client.list_containers, **args)
425
    
426
    def test_if_unmodified_since(self):
427
        now = datetime.datetime.utcnow()
428
        since = now + datetime.timedelta(1)
429
        
430
        for f in DATE_FORMATS:
431
            c = self.client.list_containers(if_unmodified_since=since.strftime(f))
432
            
433
            #assert success
434
            self.assertEqual(self.containers, c)
435
    
436
    def test_if_unmodified_since_precondition_failed(self):
437
        t = datetime.datetime.utcnow()
438
        t2 = t - datetime.timedelta(minutes=10)
439
        
440
        #add a new container
441
        self.client.create_container('dummy')
442
        
443
        for f in DATE_FORMATS:
444
            past = t2.strftime(f)
445
            
446
            args = {'if_unmodified_since':'%s' %past}
447
            
448
            #assert precondition failed
449
            self.assert_raises_fault(412, self.client.list_containers, **args)
450
    
451
class AccountPost(BaseTestCase):
452
    def setUp(self):
453
        BaseTestCase.setUp(self)
454
        self.account = 'test'
455
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
456
        for item in self.containers:
457
            self.client.create_container(item)
458
        
459
        #keep track of initial account groups
460
        self.initial_groups = self.client.retrieve_account_groups()
461
        
462
        #keep track of initial account meta
463
        self.initial_meta = self.client.retrieve_account_metadata(restricted=True)
464
        
465
        meta = {'foo':'bar'}
466
        self.client.update_account_metadata(**meta)
467
        self.updated_meta = self.initial_meta.update(meta)
468
    
469
    def tearDown(self):
470
        #delete additionally created meta
471
        l = []
472
        for m in self.client.retrieve_account_metadata(restricted=True):
473
            if m not in self.initial_meta:
474
                l.append(m)
475
        self.client.delete_account_metadata(l)
476
        
477
        #delete additionally created groups
478
        l = []
479
        for g in self.client.retrieve_account_groups():
480
            if g not in self.initial_groups:
481
                l.append(g)
482
        self.client.unset_account_groups(l)
483
        
484
        #print '#', self.client.retrieve_account_groups()
485
        #print '#', self.client.retrieve_account_metadata(restricted=True)
486
        BaseTestCase.tearDown(self)
487
    
488
    def test_update_meta(self):
489
        with AssertMappingInvariant(self.client.retrieve_account_groups):
490
            meta = {'test':'test', 'tost':'tost'}
491
            self.client.update_account_metadata(**meta)
492
            
493
            meta.update(self.initial_meta)
494
            self.assertEqual(meta,
495
                             self.client.retrieve_account_metadata(
496
                                restricted=True))
497
        
498
    def test_invalid_account_update_meta(self):
499
        meta = {'test':'test', 'tost':'tost'}
500
        self.assert_raises_fault(401,
501
                                 self.invalid_client.update_account_metadata,
502
                                 **meta)
503
    
504
    def test_reset_meta(self):
505
        with AssertMappingInvariant(self.client.retrieve_account_groups):
506
            meta = {'test':'test', 'tost':'tost'}
507
            self.client.update_account_metadata(**meta)
508
            
509
            meta = {'test':'test33'}
510
            self.client.reset_account_metadata(**meta)
511
            
512
            self.assertEqual(meta, self.client.retrieve_account_metadata(restricted=True))
513
    
514
    def test_delete_meta(self):
515
        with AssertMappingInvariant(self.client.retrieve_account_groups):
516
            meta = {'test':'test', 'tost':'tost'}
517
            self.client.update_account_metadata(**meta)
518
            
519
            self.client.delete_account_metadata(meta.keys())
520
            
521
            account_meta = self.client.retrieve_account_metadata(restricted=True)
522
            for m in meta:
523
                self.assertTrue(m not in account_meta.keys())
524
    
525
    def test_set_account_groups(self):
526
        with AssertMappingInvariant(self.client.retrieve_account_metadata):
527
            groups = {'pithosdev':'verigak,gtsouk,chazapis'}
528
            self.client.set_account_groups(**groups)
529
            
530
            self.assertEqual(groups, self.client.retrieve_account_groups())
531
            
532
            more_groups = {'clientsdev':'pkanavos,mvasilak'}
533
            self.client.set_account_groups(**more_groups)
534
            
535
            groups.update(more_groups)
536
            self.assertEqual(groups, self.client.retrieve_account_groups())
537
    
538
    def test_reset_account_groups(self):
539
        with AssertMappingInvariant(self.client.retrieve_account_metadata):
540
            groups = {'pithosdev':'verigak,gtsouk,chazapis',
541
                      'clientsdev':'pkanavos,mvasilak'}
542
            self.client.set_account_groups(**groups)
543
            
544
            self.assertEqual(groups, self.client.retrieve_account_groups())
545
            
546
            groups = {'pithosdev':'verigak,gtsouk,chazapis, papagian'}
547
            self.client.reset_account_groups(**groups)
548
            
549
            self.assertTrue(groups, self.client.retrieve_account_groups())
550
    
551
    def test_delete_account_groups(self):
552
        with AssertMappingInvariant(self.client.retrieve_account_metadata):
553
            groups = {'pithosdev':'verigak,gtsouk,chazapis',
554
                      'clientsdev':'pkanavos,mvasilak'}
555
            self.client.set_account_groups(**groups)
556
            
557
            self.client.unset_account_groups(groups.keys())
558
            
559
            self.assertEqual({}, self.client.retrieve_account_groups())
560
    
561
class ContainerHead(BaseTestCase):
562
    def setUp(self):
563
        BaseTestCase.setUp(self)
564
        self.account = 'test'
565
        self.container = 'apples'
566
        self.client.create_container(self.container)
567
    
568
    def test_get_meta(self):
569
        meta = {'trash':'true'}
570
        t1 = datetime.datetime.utcnow()
571
        o = self.upload_random_data(self.container, o_names[0], **meta)
572
        if o:
573
            headers = self.client.retrieve_container_metadata(self.container)
574
            self.assertEqual(headers['x-container-object-count'], '1')
575
            self.assertEqual(headers['x-container-bytes-used'], str(len(o['data'])))
576
            t2 = datetime.datetime.strptime(headers['last-modified'], DATE_FORMATS[2])
577
            delta = (t2 - t1)
578
            threashold = datetime.timedelta(seconds=1) 
579
            self.assertTrue(delta < threashold)
580
            self.assertTrue(headers['x-container-object-meta'])
581
            self.assertTrue('Trash' in headers['x-container-object-meta'])
582

    
583
class ContainerGet(BaseTestCase):
584
    def setUp(self):
585
        BaseTestCase.setUp(self)
586
        self.account = 'test'
587
        self.container = ['pears', 'apples']
588
        for c in self.container:
589
            self.client.create_container(c)
590
        self.obj = []
591
        for o in o_names[:8]:
592
            self.obj.append(self.upload_random_data(self.container[0], o))
593
        for o in o_names[8:]:
594
            self.obj.append(self.upload_random_data(self.container[1], o))
595
    
596
    def test_list_objects(self):
597
        objects = self.client.list_objects(self.container[0])
598
        l = [elem['name'] for elem in self.obj[:8]]
599
        l.sort()
600
        self.assertEqual(objects, l)
601
    
602
    def test_list_objects_containing_slash(self):
603
        self.client.create_container('test')
604
        self.upload_random_data('test', '/objectname')
605
        
606
        objects = self.client.list_objects('test')
607
        self.assertEqual(objects, ['/objectname'])
608
        
609
        objects = self.client.list_objects('test', format='json')
610
        self.assertEqual(objects[0]['name'], '/objectname')
611
        
612
        objects = self.client.list_objects('test', format='xml')
613
        self.assert_extended(objects, 'xml', 'object')
614
        node_name = objects.getElementsByTagName('name')[0]
615
        self.assertEqual(node_name.firstChild.data, '/objectname')
616
        
617
        #objects = self.client.list_objects('test', prefix='/')
618
        #self.assertEqual(objects, ['/objectname'])
619
        #
620
        #objects = self.client.list_objects('test', path='/')
621
        #self.assertEqual(objects, ['/objectname'])
622
        #
623
        #objects = self.client.list_objects('test', prefix='/', delimiter='n')
624
        #self.assertEqual(objects, ['/object'])
625

    
626
    def test_list_objects_with_limit_marker(self):
627
        objects = self.client.list_objects(self.container[0], limit=2)
628
        l = [elem['name'] for elem in self.obj[:8]]
629
        l.sort()
630
        self.assertEqual(objects, l[:2])
631
        
632
        markers = ['How To Win Friends And Influence People.pdf',
633
                   'moms_birthday.jpg']
634
        limit = 4
635
        for m in markers:
636
            objects = self.client.list_objects(self.container[0], limit=limit,
637
                                               marker=m)
638
            l = [elem['name'] for elem in self.obj[:8]]
639
            l.sort()
640
            start = l.index(m) + 1
641
            end = start + limit
642
            end = len(l) >= end and end or len(l)
643
            self.assertEqual(objects, l[start:end])
644
    
645
    #takes too long
646
    #def test_list_limit_exceeds(self):
647
    #    self.client.create_container('pithos')
648
    #    
649
    #    for i in range(10001):
650
    #        self.client.create_zero_length_object('pithos', i)
651
    #    
652
    #    self.assertEqual(10000, len(self.client.list_objects('pithos')))
653
    
654
    def test_list_empty_params(self):
655
        objects = self.client.get('/%s' % self.container[0])[2]
656
        if objects:
657
            objects = objects.strip().split('\n')
658
        self.assertEqual(objects,
659
                         self.client.list_objects(self.container[0]))
660
    
661
    def test_list_pseudo_hierarchical_folders(self):
662
        objects = self.client.list_objects(self.container[1], prefix='photos',
663
                                           delimiter='/')
664
        self.assertEquals(['photos/animals/', 'photos/me.jpg',
665
                           'photos/plants/'], objects)
666
        
667
        objects = self.client.list_objects(self.container[1],
668
                                           prefix='photos/animals',
669
                                           delimiter='/')
670
        l = ['photos/animals/cats/', 'photos/animals/dogs/']
671
        self.assertEquals(l, objects)
672
        
673
        objects = self.client.list_objects(self.container[1], path='photos')
674
        self.assertEquals(['photos/me.jpg'], objects)
675
    
676
    def test_extended_list_json(self):
677
        objects = self.client.list_objects(self.container[1], format='json',
678
                                           limit=2, prefix='photos/animals',
679
                                           delimiter='/')
680
        self.assertEqual(objects[0]['subdir'], 'photos/animals/cats/')
681
        self.assertEqual(objects[1]['subdir'], 'photos/animals/dogs/')
682
    
683
    def test_extended_list_xml(self):
684
        xml = self.client.list_objects(self.container[1], format='xml', limit=4,
685
                                       prefix='photos', delimiter='/')
686
        self.assert_extended(xml, 'xml', 'object', size=4)
687
        dirs = xml.getElementsByTagName('subdir')
688
        self.assertEqual(len(dirs), 2)
689
        self.assertEqual(dirs[0].attributes['name'].value, 'photos/animals/')
690
        self.assertEqual(dirs[1].attributes['name'].value, 'photos/plants/')
691
        
692
        objects = xml.getElementsByTagName('name')
693
        self.assertEqual(len(objects), 1)
694
        self.assertEqual(objects[0].childNodes[0].data, 'photos/me.jpg')
695
    
696
    def test_list_meta_double_matching(self):
697
        meta = {'quality':'aaa', 'stock':'true'}
698
        self.client.update_object_metadata(self.container[0],
699
                                           self.obj[0]['name'], **meta)
700
        obj = self.client.list_objects(self.container[0], meta='Quality,Stock')
701
        self.assertEqual(len(obj), 1)
702
        self.assertTrue(obj, self.obj[0]['name'])
703
    
704
    def test_list_using_meta(self):
705
        meta = {'quality':'aaa'}
706
        for o in self.obj[:2]:
707
            self.client.update_object_metadata(self.container[0], o['name'],
708
                                               **meta)
709
        meta = {'stock':'true'}
710
        for o in self.obj[3:5]:
711
            self.client.update_object_metadata(self.container[0], o['name'],
712
                                               **meta)
713
        
714
        obj = self.client.list_objects(self.container[0], meta='Quality')
715
        self.assertEqual(len(obj), 2)
716
        self.assertTrue(obj, [o['name'] for o in self.obj[:2]])
717
        
718
        # test case insensitive
719
        obj = self.client.list_objects(self.container[0], meta='quality')
720
        self.assertEqual(len(obj), 2)
721
        self.assertTrue(obj, [o['name'] for o in self.obj[:2]])
722
        
723
        # test multiple matches
724
        obj = self.client.list_objects(self.container[0], meta='Quality,Stock')
725
        self.assertEqual(len(obj), 4)
726
        self.assertTrue(obj, [o['name'] for o in self.obj[:4]])
727
        
728
        # test non 1-1 multiple match
729
        obj = self.client.list_objects(self.container[0], meta='Quality,aaaa')
730
        self.assertEqual(len(obj), 2)
731
        self.assertTrue(obj, [o['name'] for o in self.obj[:2]])
732
    
733
    def test_if_modified_since(self):
734
        t = datetime.datetime.utcnow()
735
        t2 = t - datetime.timedelta(minutes=10)
736
        
737
        #add a new object
738
        self.upload_random_data(self.container[0], o_names[0])
739
        
740
        for f in DATE_FORMATS:
741
            past = t2.strftime(f)
742
            try:
743
                o = self.client.list_objects(self.container[0],
744
                                            if_modified_since=past)
745
                self.assertEqual(o,
746
                                 self.client.list_objects(self.container[0]))
747
            except Fault, f:
748
                self.failIf(f.status == 304) #fail if not modified
749
    
750
    def test_if_modified_since_invalid_date(self):
751
        headers = {'if-modified-since':''}
752
        o = self.client.list_objects(self.container[0], if_modified_since='')
753
        self.assertEqual(o, self.client.list_objects(self.container[0]))
754
    
755
    def test_if_not_modified_since(self):
756
        now = datetime.datetime.utcnow()
757
        since = now + datetime.timedelta(1)
758
        
759
        for f in DATE_FORMATS:
760
            args = {'if_modified_since':'%s' %since.strftime(f)}
761
            
762
            #assert not modified
763
            self.assert_raises_fault(304, self.client.list_objects,
764
                                     self.container[0], **args)
765
    
766
    def test_if_unmodified_since(self):
767
        now = datetime.datetime.utcnow()
768
        since = now + datetime.timedelta(1)
769
        
770
        for f in DATE_FORMATS:
771
            obj = self.client.list_objects(self.container[0],
772
                                           if_unmodified_since=since.strftime(f))
773
            
774
            #assert unmodified
775
            self.assertEqual(obj, self.client.list_objects(self.container[0]))
776
    
777
    def test_if_unmodified_since_precondition_failed(self):
778
        t = datetime.datetime.utcnow()
779
        t2 = t - datetime.timedelta(minutes=10)
780
        
781
        #add a new container
782
        self.client.create_container('dummy')
783
        
784
        for f in DATE_FORMATS:
785
            past = t2.strftime(f)
786
            
787
            args = {'if_unmodified_since':'%s' %past}
788
            
789
            #assert precondition failed
790
            self.assert_raises_fault(412, self.client.list_objects,
791
                                     self.container[0], **args)
792

    
793
class ContainerPut(BaseTestCase):
794
    def setUp(self):
795
        BaseTestCase.setUp(self)
796
        self.account = 'test'
797
        self.containers = ['c1', 'c2']
798
    
799
    def test_create(self):
800
        self.client.create_container(self.containers[0])
801
        containers = self.client.list_containers()
802
        self.assertTrue(self.containers[0] in containers)
803
        self.assert_container_exists(self.containers[0])
804
    
805
    def test_create_twice(self):
806
        self.client.create_container(self.containers[0])
807
        self.assertTrue(not self.client.create_container(self.containers[0]))
808
    
809
class ContainerPost(BaseTestCase):
810
    def setUp(self):
811
        BaseTestCase.setUp(self)
812
        self.account = 'test'
813
        self.container = 'apples'
814
        self.client.create_container(self.container)
815
    
816
    def test_update_meta(self):
817
        meta = {'test':'test33',
818
                'tost':'tost22'}
819
        self.client.update_container_metadata(self.container, **meta)
820
        headers = self.client.retrieve_container_metadata(self.container)
821
        for k,v in meta.items():
822
            k = 'x-container-meta-%s' % k
823
            self.assertTrue(headers[k])
824
            self.assertEqual(headers[k], v)
825

    
826
class ContainerDelete(BaseTestCase):
827
    def setUp(self):
828
        BaseTestCase.setUp(self)
829
        self.account = 'test'
830
        self.containers = ['c1', 'c2']
831
        for c in self.containers:
832
            self.client.create_container(c)
833
        self.upload_random_data(self.containers[1], o_names[0])
834
    
835
    def test_delete(self):
836
        status = self.client.delete_container(self.containers[0])[0]
837
        self.assertEqual(status, 204)
838
    
839
    def test_delete_non_empty(self):
840
        self.assert_raises_fault(409, self.client.delete_container,
841
                                 self.containers[1])
842
    
843
    def test_delete_invalid(self):
844
        self.assert_raises_fault(404, self.client.delete_container, 'c3')
845

    
846
class ObjectHead(BaseTestCase):
847
    pass
848

    
849
class ObjectGet(BaseTestCase):
850
    def setUp(self):
851
        BaseTestCase.setUp(self)
852
        self.account = 'test'
853
        self.containers = ['c1', 'c2']
854
        #create some containers
855
        for c in self.containers:
856
            self.client.create_container(c)
857
        
858
        #upload a file
859
        names = ('obj1', 'obj2')
860
        self.objects = []
861
        for n in names:
862
            self.objects.append(self.upload_random_data(self.containers[1], n))
863
    
864
    def test_get(self):
865
        #perform get
866
        o = self.client.retrieve_object(self.containers[1],
867
                                        self.objects[0]['name'],
868
                                        self.objects[0]['meta'])
869
        self.assertEqual(o, self.objects[0]['data'])
870
    
871
    def test_get_invalid(self):
872
        self.assert_raises_fault(404, self.client.retrieve_object,
873
                                 self.containers[0], self.objects[0]['name'])
874
    
875
    def test_get_partial(self):
876
        #perform get with range
877
        status, headers, data = self.client.request_object(self.containers[1],
878
                                                            self.objects[0]['name'],
879
                                                            range='bytes=0-499')
880
        
881
        #assert successful partial content
882
        self.assertEqual(status, 206)
883
        
884
        #assert content-type
885
        self.assertEqual(headers['content-type'],
886
                         self.objects[0]['meta']['content_type'])
887
        
888
        #assert content length
889
        self.assertEqual(int(headers['content-length']), 500)
890
        
891
        #assert content
892
        self.assertEqual(self.objects[0]['data'][:500], data)
893
    
894
    def test_get_final_500(self):
895
        #perform get with range
896
        headers = {'range':'bytes=-500'}
897
        status, headers, data = self.client.request_object(self.containers[1],
898
                                                            self.objects[0]['name'],
899
                                                            range='bytes=-500')
900
        
901
        #assert successful partial content
902
        self.assertEqual(status, 206)
903
        
904
        #assert content-type
905
        self.assertEqual(headers['content-type'],
906
                         self.objects[0]['meta']['content_type'])
907
        
908
        #assert content length
909
        self.assertEqual(int(headers['content-length']), 500)
910
        
911
        #assert content
912
        self.assertTrue(self.objects[0]['data'][-500:], data)
913
    
914
    def test_get_rest(self):
915
        #perform get with range
916
        offset = len(self.objects[0]['data']) - 500
917
        status, headers, data = self.client.request_object(self.containers[1],
918
                                                self.objects[0]['name'],
919
                                                range='bytes=%s-' %offset)
920
        
921
        #assert successful partial content
922
        self.assertEqual(status, 206)
923
        
924
        #assert content-type
925
        self.assertEqual(headers['content-type'],
926
                         self.objects[0]['meta']['content_type'])
927
        
928
        #assert content length
929
        self.assertEqual(int(headers['content-length']), 500)
930
        
931
        #assert content
932
        self.assertTrue(self.objects[0]['data'][-500:], data)
933
    
934
    def test_get_range_not_satisfiable(self):
935
        #perform get with range
936
        offset = len(self.objects[0]['data']) + 1
937
        
938
        #assert range not satisfiable
939
        self.assert_raises_fault(416, self.client.retrieve_object,
940
                                 self.containers[1], self.objects[0]['name'],
941
                                 range='bytes=0-%s' %offset)
942
    
943
    def test_multiple_range(self):
944
        #perform get with multiple range
945
        ranges = ['0-499', '-500', '1000-']
946
        bytes = 'bytes=%s' % ','.join(ranges)
947
        status, headers, data = self.client.request_object(self.containers[1],
948
                                                           self.objects[0]['name'],
949
                                                           range=bytes)
950
        
951
        # assert partial content
952
        self.assertEqual(status, 206)
953
        
954
        # assert Content-Type of the reply will be multipart/byteranges
955
        self.assertTrue(headers['content-type'])
956
        content_type_parts = headers['content-type'].split()
957
        self.assertEqual(content_type_parts[0], ('multipart/byteranges;'))
958
        
959
        boundary = '--%s' %content_type_parts[1].split('=')[-1:][0]
960
        cparts = data.split(boundary)[1:-1]
961
        
962
        # assert content parts are exactly 2
963
        self.assertEqual(len(cparts), len(ranges))
964
        
965
        # for each content part assert headers
966
        i = 0
967
        for cpart in cparts:
968
            content = cpart.split('\r\n')
969
            headers = content[1:3]
970
            content_range = headers[0].split(': ')
971
            self.assertEqual(content_range[0], 'Content-Range')
972
            
973
            r = ranges[i].split('-')
974
            if not r[0] and not r[1]:
975
                pass
976
            elif not r[0]:
977
                start = len(self.objects[0]['data']) - int(r[1])
978
                end = len(self.objects[0]['data'])
979
            elif not r[1]:
980
                start = int(r[0])
981
                end = len(self.objects[0]['data'])
982
            else:
983
                start = int(r[0])
984
                end = int(r[1]) + 1
985
            fdata = self.objects[0]['data'][start:end]
986
            sdata = '\r\n'.join(content[4:-1])
987
            self.assertEqual(len(fdata), len(sdata))
988
            self.assertEquals(fdata, sdata)
989
            i+=1
990
    
991
    def test_multiple_range_not_satisfiable(self):
992
        #perform get with multiple range
993
        out_of_range = len(self.objects[0]['data']) + 1
994
        ranges = ['0-499', '-500', '%d-' %out_of_range]
995
        bytes = 'bytes=%s' % ','.join(ranges)
996
        
997
        # assert partial content
998
        self.assert_raises_fault(416, self.client.retrieve_object,
999
                                 self.containers[1],
1000
                                 self.objects[0]['name'], range=bytes)
1001
    
1002
    def test_get_with_if_match(self):
1003
        #perform get with If-Match
1004
        etag = self.objects[0]['hash']
1005
        status, headers, data = self.client.request_object(self.containers[1],
1006
                                                           self.objects[0]['name'],
1007
                                                           if_match=etag)
1008
        #assert get success
1009
        self.assertEqual(status, 200)
1010
        
1011
        #assert content-type
1012
        self.assertEqual(headers['content-type'],
1013
                         self.objects[0]['meta']['content_type'])
1014
        
1015
        #assert response content
1016
        self.assertEqual(self.objects[0]['data'], data)
1017
    
1018
    def test_get_with_if_match_star(self):
1019
        #perform get with If-Match *
1020
        headers = {'if-match':'*'}
1021
        status, headers, data = self.client.request_object(self.containers[1],
1022
                                                self.objects[0]['name'],
1023
                                                **headers)
1024
        #assert get success
1025
        self.assertEqual(status, 200)
1026
        
1027
        #assert content-type
1028
        self.assertEqual(headers['content-type'],
1029
                         self.objects[0]['meta']['content_type'])
1030
        
1031
        #assert response content
1032
        self.assertEqual(self.objects[0]['data'], data)
1033
    
1034
    def test_get_with_multiple_if_match(self):
1035
        #perform get with If-Match
1036
        etags = [i['hash'] for i in self.objects if i]
1037
        etags = ','.join('"%s"' % etag for etag in etags)
1038
        status, headers, data = self.client.request_object(self.containers[1],
1039
                                                           self.objects[0]['name'],
1040
                                                           if_match=etags)
1041
        #assert get success
1042
        self.assertEqual(status, 200)
1043
        
1044
        #assert content-type
1045
        self.assertEqual(headers['content-type'],
1046
                         self.objects[0]['meta']['content_type'])
1047
        
1048
        #assert content-type
1049
        self.assertEqual(headers['content-type'],
1050
                         self.objects[0]['meta']['content_type'])
1051
        
1052
        #assert response content
1053
        self.assertEqual(self.objects[0]['data'], data)
1054
    
1055
    def test_if_match_precondition_failed(self):
1056
        #assert precondition failed
1057
        self.assert_raises_fault(412, self.client.retrieve_object,
1058
                                 self.containers[1],
1059
                                 self.objects[0]['name'], if_match='123')
1060
    
1061
    def test_if_none_match(self):
1062
        #perform get with If-None-Match
1063
        status, headers, data = self.client.request_object(self.containers[1],
1064
                                                           self.objects[0]['name'],
1065
                                                           if_none_match='123')
1066
        
1067
        #assert get success
1068
        self.assertEqual(status, 200)
1069
        
1070
        #assert content-type
1071
        self.assertEqual(headers['content_type'],
1072
                         self.objects[0]['meta']['content_type'])
1073
    
1074
    def test_if_none_match(self):
1075
        #perform get with If-None-Match * and assert not modified
1076
        self.assert_raises_fault(304, self.client.retrieve_object,
1077
                                 self.containers[1],
1078
                                 self.objects[0]['name'],
1079
                                 if_none_match='*')
1080
    
1081
    def test_if_none_match_not_modified(self):
1082
        #perform get with If-None-Match and assert not modified
1083
        self.assert_raises_fault(304, self.client.retrieve_object,
1084
                                 self.containers[1],
1085
                                 self.objects[0]['name'],
1086
                                 if_none_match=self.objects[0]['hash'])
1087
        
1088
        meta = self.client.retrieve_object_metadata(self.containers[1],
1089
                                                    self.objects[0]['name'])
1090
        self.assertEqual(meta['etag'], self.objects[0]['hash'])
1091
    
1092
    def test_if_modified_since(self):
1093
        t = datetime.datetime.utcnow()
1094
        t2 = t - datetime.timedelta(minutes=10)
1095
        
1096
        #modify the object
1097
        self.upload_data(self.containers[1],
1098
                           self.objects[0]['name'],
1099
                           self.objects[0]['data'][:200])
1100
        
1101
        for f in DATE_FORMATS:
1102
            past = t2.strftime(f)
1103
            
1104
            headers = {'if-modified-since':'%s' %past}
1105
            try:
1106
                o = self.client.retrieve_object(self.containers[1],
1107
                                                self.objects[0]['name'],
1108
                                                if_modified_since=past)
1109
                self.assertEqual(o,
1110
                                 self.client.retrieve_object(self.containers[1],
1111
                                                             self.objects[0]['name']))
1112
            except Fault, f:
1113
                self.failIf(f.status == 304)
1114
    
1115
    def test_if_modified_since_invalid_date(self):
1116
        o = self.client.retrieve_object(self.containers[1],
1117
                                        self.objects[0]['name'],
1118
                                        if_modified_since='')
1119
        self.assertEqual(o, self.client.retrieve_object(self.containers[1],
1120
                                                        self.objects[0]['name']))
1121
            
1122
    def test_if_not_modified_since(self):
1123
        now = datetime.datetime.utcnow()
1124
        since = now + datetime.timedelta(1)
1125
        
1126
        for f in DATE_FORMATS:
1127
            #assert not modified
1128
            self.assert_raises_fault(304, self.client.retrieve_object,
1129
                                     self.containers[1], self.objects[0]['name'],
1130
                                     if_modified_since=since.strftime(f))
1131
    
1132
    def test_if_unmodified_since(self):
1133
        now = datetime.datetime.utcnow()
1134
        since = now + datetime.timedelta(1)
1135
        
1136
        for f in DATE_FORMATS:
1137
            t = since.strftime(f)
1138
            status, headers, data = self.client.request_object(self.containers[1],
1139
                                                               self.objects[0]['name'],
1140
                                                               if_unmodified_since=t)
1141
            #assert success
1142
            self.assertEqual(status, 200)
1143
            self.assertEqual(self.objects[0]['data'], data)
1144
            
1145
            #assert content-type
1146
            self.assertEqual(headers['content-type'],
1147
                             self.objects[0]['meta']['content_type'])
1148
    
1149
    def test_if_unmodified_since_precondition_failed(self):
1150
        t = datetime.datetime.utcnow()
1151
        t2 = t - datetime.timedelta(minutes=10)
1152
        
1153
        #modify the object
1154
        self.upload_data(self.containers[1],
1155
                           self.objects[0]['name'],
1156
                           self.objects[0]['data'][:200])
1157
        
1158
        for f in DATE_FORMATS:
1159
            past = t2.strftime(f)
1160
            #assert precondition failed
1161
            self.assert_raises_fault(412, self.client.retrieve_object,
1162
                                     self.containers[1], self.objects[0]['name'],
1163
                                     if_unmodified_since=past)
1164
    
1165
    def test_hashes(self):
1166
        l = 8388609
1167
        fname = 'largefile'
1168
        o = self.upload_random_data(self.containers[1], fname, l)
1169
        if o:
1170
            data = self.client.retrieve_object(self.containers[1], fname,
1171
                                               format='json')
1172
            body = json.loads(data)
1173
            hashes = body['hashes']
1174
            block_size = body['block_size']
1175
            block_hash = body['block_hash']
1176
            block_num = l/block_size == 0 and l/block_size or l/block_size + 1
1177
            self.assertTrue(len(hashes), block_num)
1178
            i = 0
1179
            for h in hashes:
1180
                start = i * block_size
1181
                end = (i + 1) * block_size
1182
                hash = compute_block_hash(o['data'][start:end], block_hash)
1183
                self.assertEqual(h, hash)
1184
                i += 1
1185

    
1186
class ObjectPut(BaseTestCase):
1187
    def setUp(self):
1188
        BaseTestCase.setUp(self)
1189
        self.account = 'test'
1190
        self.container = 'c1'
1191
        self.client.create_container(self.container)
1192
    
1193
    def test_upload(self):
1194
        name = o_names[0]
1195
        meta = {'test':'test1'}
1196
        o = self.upload_random_data(self.container, name, **meta)
1197
        
1198
        headers = self.client.retrieve_object_metadata(self.container,
1199
                                                       name,
1200
                                                       restricted=True)
1201
        self.assertTrue('test' in headers.keys())
1202
        self.assertEqual(headers['test'], meta['test'])
1203
        
1204
        #assert uploaded content
1205
        status, h, data = self.client.request_object(self.container, name)
1206
        self.assertEqual(len(o['data']), int(h['content-length']))
1207
        self.assertEqual(o['data'], data)
1208
        
1209
        #assert content-type
1210
        self.assertEqual(h['content-type'], o['meta']['content_type'])
1211
    
1212
    def test_maximum_upload_size_exceeds(self):
1213
        name = o_names[0]
1214
        meta = {'test':'test1'}
1215
        #upload 100MB
1216
        length=1024*1024*100
1217
        self.assert_raises_fault(400, self.upload_random_data, self.container,
1218
                                 name, length, **meta)
1219
        
1220
        ##d = get_random_data(length=1024*1024*100)
1221
        ##self.client.create_object_using_chunks(self.container, name, StringIO(d))
1222
        
1223

    
1224
    def test_upload_with_name_containing_slash(self):
1225
        name = '/%s' % o_names[0]
1226
        meta = {'test':'test1'}
1227
        o = self.upload_random_data(self.container, name, **meta)
1228
        
1229
        self.assertEqual(o['data'],
1230
                         self.client.retrieve_object(self.container, name))
1231
        
1232
        self.assertTrue(name in self.client.list_objects(self.container))
1233
    
1234
    def test_create_directory_marker(self):
1235
        self.client.create_directory_marker(self.container, 'foo')
1236
        meta = self.client.retrieve_object_metadata(self.container, 'foo')
1237
        self.assertEqual(meta['content-length'], '0')
1238
        self.assertEqual(meta['content-type'], 'application/directory')
1239

    
1240
    def test_upload_unprocessable_entity(self):
1241
        meta={'etag':'123', 'test':'test1'}
1242
        
1243
        #assert unprocessable entity
1244
        self.assert_raises_fault(422, self.upload_random_data, self.container,
1245
                                 o_names[0], **meta)
1246
    
1247
    def test_chunked_transfer(self):
1248
        data = get_random_data()
1249
        objname = 'object'
1250
        self.client.create_object_using_chunks(self.container, objname,
1251
                                               StringIO(data))
1252
        
1253
        uploaded_data = self.client.retrieve_object(self.container, objname)
1254
        self.assertEqual(data, uploaded_data)
1255
    
1256
    def test_manifestation(self):
1257
        prefix = 'myobject/'
1258
        data = ''
1259
        for i in range(5):
1260
            part = '%s%d' %(prefix, i)
1261
            o = self.upload_random_data(self.container, part)
1262
            data += o['data']
1263
        
1264
        manifest = '%s/%s' %(self.container, prefix)
1265
        self.client.create_manifestation(self.container, 'large-object',
1266
                                         manifest)
1267
        
1268
        self.assert_object_exists(self.container, 'large-object')
1269
        self.assertEqual(data, self.client.retrieve_object(self.container,
1270
                                                           'large-object'))
1271
        
1272
        #wrong manifestation
1273
        self.client.create_manifestation(self.container, 'large-object',
1274
                                         'invalid')
1275
        
1276
class ObjectCopy(BaseTestCase):
1277
    def setUp(self):
1278
        BaseTestCase.setUp(self)
1279
        self.account = 'test'
1280
        self.containers = ['c1', 'c2']
1281
        for c in self.containers:
1282
            self.client.create_container(c)
1283
        self.obj = self.upload_random_data(self.containers[0], o_names[0])
1284
    
1285
    def tearDown(self):
1286
        pass
1287
    
1288
    def test_copy(self):
1289
        with AssertMappingInvariant(self.client.retrieve_object_metadata,
1290
                             self.containers[0], self.obj['name']):
1291
            #perform copy
1292
            meta = {'test':'testcopy'}
1293
            status = self.client.copy_object(self.containers[0],
1294
                                              self.obj['name'],
1295
                                              self.containers[0],
1296
                                              'testcopy',
1297
                                              meta)[0]
1298
            
1299
            #assert copy success
1300
            self.assertEqual(status, 201)
1301
            
1302
            #assert access the new object
1303
            headers = self.client.retrieve_object_metadata(self.containers[0],
1304
                                                           'testcopy')
1305
            self.assertTrue('x-object-meta-test' in headers.keys())
1306
            self.assertTrue(headers['x-object-meta-test'], 'testcopy')
1307
            
1308
            #assert etag is the same
1309
            self.assertEqual(headers['etag'], self.obj['hash'])
1310
            
1311
            #assert src object still exists
1312
            self.assert_object_exists(self.containers[0], self.obj['name'])
1313
    
1314
    def test_copy_from_different_container(self):
1315
        with AssertMappingInvariant(self.client.retrieve_object_metadata,
1316
                             self.containers[0], self.obj['name']):
1317
            meta = {'test':'testcopy'}
1318
            status = self.client.copy_object(self.containers[0],
1319
                                             self.obj['name'],
1320
                                             self.containers[1],
1321
                                             'testcopy',
1322
                                             meta)[0]
1323
            self.assertEqual(status, 201)
1324
            
1325
            # assert updated metadata
1326
            meta = self.client.retrieve_object_metadata(self.containers[1],
1327
                                                           'testcopy',
1328
                                                           restricted=True)
1329
            self.assertTrue('test' in meta.keys())
1330
            self.assertTrue(meta['test'], 'testcopy')
1331
            
1332
            #assert src object still exists
1333
            self.assert_object_exists(self.containers[0], self.obj['name'])
1334
    
1335
    def test_copy_invalid(self):
1336
        #copy from invalid object
1337
        meta = {'test':'testcopy'}
1338
        self.assert_raises_fault(404, self.client.copy_object, self.containers[0],
1339
                                 'test.py', self.containers[1], 'testcopy', meta)
1340
        
1341
        #copy from invalid container
1342
        meta = {'test':'testcopy'}
1343
        self.assert_raises_fault(404, self.client.copy_object, self.containers[1],
1344
                                 self.obj['name'], self.containers[1],
1345
                                 'testcopy', meta)
1346

    
1347
class ObjectMove(BaseTestCase):
1348
    def setUp(self):
1349
        BaseTestCase.setUp(self)
1350
        self.account = 'test'
1351
        self.containers = ['c1', 'c2']
1352
        for c in self.containers:
1353
            self.client.create_container(c)
1354
        self.obj = self.upload_random_data(self.containers[0], o_names[0])
1355
    
1356
    def test_move(self):
1357
        #perform move
1358
        meta = {'test':'testcopy'}
1359
        src_path = '/'.join(('/', self.containers[0], self.obj['name']))
1360
        status = self.client.move_object(self.containers[0], self.obj['name'],
1361
                                         self.containers[0], 'testcopy',
1362
                                         meta)[0]
1363
        
1364
        #assert successful move
1365
        self.assertEqual(status, 201)
1366
        
1367
        #assert updated metadata
1368
        meta = self.client.retrieve_object_metadata(self.containers[0],
1369
                                                    'testcopy',
1370
                                                    restricted=True)
1371
        self.assertTrue('test' in meta.keys())
1372
        self.assertTrue(meta['test'], 'testcopy')
1373
        
1374
        #assert src object no more exists
1375
        self.assert_object_not_exists(self.containers[0], self.obj['name'])
1376

    
1377
class ObjectPost(BaseTestCase):
1378
    def setUp(self):
1379
        BaseTestCase.setUp(self)
1380
        self.account = 'test'
1381
        self.containers = ['c1', 'c2']
1382
        for c in self.containers:
1383
            self.client.create_container(c)
1384
        self.obj = self.upload_random_data(self.containers[0], o_names[0])
1385
    
1386
    def test_update_meta(self):
1387
        #perform update metadata
1388
        more = {'foo':'foo', 'bar':'bar'}
1389
        status = self.client.update_object_metadata(self.containers[0],
1390
                                                    self.obj['name'],
1391
                                                    **more)[0]
1392
        #assert request accepted
1393
        self.assertEqual(status, 202)
1394
        
1395
        #assert old metadata are still there
1396
        headers = self.client.retrieve_object_metadata(self.containers[0],
1397
                                                       self.obj['name'],
1398
                                                       restricted=True)
1399
        #assert new metadata have been updated
1400
        for k,v in more.items():
1401
            self.assertTrue(k in headers.keys())
1402
            self.assertTrue(headers[k], v)
1403
    
1404
    def test_update_object(self,
1405
                           first_byte_pos=0,
1406
                           last_byte_pos=499,
1407
                           instance_length = True,
1408
                           content_length = 500):
1409
        l = len(self.obj['data'])
1410
        length = l if instance_length else '*'
1411
        range = 'bytes %d-%d/%s' %(first_byte_pos,
1412
                                       last_byte_pos,
1413
                                       length)
1414
        partial = last_byte_pos - first_byte_pos + 1
1415
        data = get_random_data(partial)
1416
        args = {'content_type':'application/octet-stream',
1417
                'content_range':'%s' %range}
1418
        if content_length:
1419
            args['content_length'] = content_length
1420
        
1421
        status = self.client.update_object(self.containers[0], self.obj['name'],
1422
                                  StringIO(data), **args)[0]
1423
        
1424
        if partial < 0 or (instance_length and l <= last_byte_pos):
1425
            self.assertEqual(status, 202)    
1426
        else:
1427
            self.assertEqual(status, 204)           
1428
            #check modified object
1429
            content = self.client.retrieve_object(self.containers[0],
1430
                                              self.obj['name'])
1431
            self.assertEqual(content[0:partial], data)
1432
            self.assertEqual(content[partial:l], self.obj['data'][partial:l])
1433
    
1434
    def test_update_object_no_content_length(self):
1435
        self.test_update_object(content_length = None)
1436
    
1437
    def test_update_object_invalid_content_length(self):
1438
        with AssertContentInvariant(self.client.retrieve_object,
1439
                                    self.containers[0], self.obj['name']):
1440
            self.assert_raises_fault(400, self.test_update_object,
1441
                                     content_length = 1000)
1442
    
1443
    def test_update_object_invalid_range(self):
1444
        with AssertContentInvariant(self.client.retrieve_object,
1445
                                    self.containers[0], self.obj['name']):
1446
            self.assert_raises_fault(416, self.test_update_object, 499, 0, True)
1447
    
1448
    def test_update_object_invalid_range_and_length(self):
1449
        with AssertContentInvariant(self.client.retrieve_object,
1450
                                    self.containers[0], self.obj['name']):
1451
            self.assert_raises_fault(416, self.test_update_object, 499, 0, True,
1452
                                     -1)
1453
    
1454
    def test_update_object_invalid_range_with_no_content_length(self):
1455
        with AssertContentInvariant(self.client.retrieve_object,
1456
                                    self.containers[0], self.obj['name']):
1457
            self.assert_raises_fault(416, self.test_update_object, 499, 0, True,
1458
                                     content_length = None)
1459
    
1460
    def test_update_object_out_of_limits(self):    
1461
        with AssertContentInvariant(self.client.retrieve_object,
1462
                                    self.containers[0], self.obj['name']):
1463
            l = len(self.obj['data'])
1464
            self.assert_raises_fault(416, self.test_update_object, 0, l+1, True)
1465
    
1466
    def test_append(self):
1467
        data = get_random_data(500)
1468
        headers = {}
1469
        self.client.update_object(self.containers[0], self.obj['name'],
1470
                                  StringIO(data), content_length=500,
1471
                                  content_type='application/octet-stream')
1472
        
1473
        content = self.client.retrieve_object(self.containers[0],
1474
                                              self.obj['name'])
1475
        self.assertEqual(len(content), len(self.obj['data']) + 500)
1476
        self.assertEqual(content[:-500], self.obj['data'])
1477
    
1478
    def test_update_with_chunked_transfer(self):
1479
        data = get_random_data(500)
1480
        dl = len(data)
1481
        fl = len(self.obj['data'])
1482
        
1483
        self.client.update_object_using_chunks(self.containers[0],
1484
                                               self.obj['name'], StringIO(data),
1485
                                               offset=0,
1486
                                               content_type='application/octet-stream')
1487
        
1488
        #check modified object
1489
        content = self.client.retrieve_object(self.containers[0],
1490
                                              self.obj['name'])
1491
        self.assertEqual(content[0:dl], data)
1492
        self.assertEqual(content[dl:fl], self.obj['data'][dl:fl])
1493

    
1494
class ObjectDelete(BaseTestCase):
1495
    def setUp(self):
1496
        BaseTestCase.setUp(self)
1497
        self.account = 'test'
1498
        self.containers = ['c1', 'c2']
1499
        for c in self.containers:
1500
            self.client.create_container(c)
1501
        self.obj = self.upload_random_data(self.containers[0], o_names[0])
1502
    
1503
    def test_delete(self):
1504
        #perform delete object
1505
        self.client.delete_object(self.containers[0], self.obj['name'])[0]
1506
    
1507
    def test_delete_invalid(self):
1508
        #assert item not found
1509
        self.assert_raises_fault(404, self.client.delete_object, self.containers[1],
1510
                                 self.obj['name'])
1511

    
1512
class ListSharing(BaseTestCase):
1513
    def setUp(self):
1514
        BaseTestCase.setUp(self)
1515
        self.client.create_container('c')
1516
        for i in range(2):
1517
            self.upload_random_data('c', 'o%s' %i)
1518
        accounts = OTHER_ACCOUNTS.copy()
1519
        self.o1_sharing_with = accounts.popitem()
1520
        self.o1_sharing = [self.o1_sharing_with[1]]
1521
        self.client.share_object('c', 'o1', self.o1_sharing, read=True)
1522
        
1523
        l = []
1524
        for i in range(2):
1525
            l.append(accounts.popitem())
1526
        #self.client.set_account_groups({'pithos-dev':'chazapis,verigak,papagian'})
1527
        #self.o2_sharing = 'write=%s' % 
1528
        #self.client.share_object('c', 'o2', self.o2_sharing)
1529
    
1530
    def test_listing(self):
1531
        self.other = Pithos_Client(DEFAULT_HOST,
1532
                              self.o1_sharing_with[0],
1533
                              self.o1_sharing_with[1],
1534
                              DEFAULT_API)
1535
        self.assertTrue('test' in self.other.list_shared_by_others())
1536

    
1537
class TestGreek(BaseTestCase):
1538
    def tearDown(self):
1539
        pass
1540
    
1541
    def test_create_container(self):
1542
        self.client.create_container('φάκελος')
1543
        self.assert_container_exists('φάκελος')
1544
    
1545
    def test_create_object(self):
1546
        self.client.create_container('φάκελος')
1547
        self.upload_random_data('φάκελος', 'αντικείμενο')
1548
        
1549
        self.assert_object_exists('φάκελος', 'αντικείμενο')
1550
    
1551
    def test_copy_object(self):
1552
        self.client.create_container('φάκελος')
1553
        self.upload_random_data('φάκελος', 'αντικείμενο')
1554
        
1555
        self.client.create_container('αντίγραφα')
1556
        self.client.copy_object('φάκελος', 'αντικείμενο', 'αντίγραφα',
1557
                                'αντικείμενο')
1558
        
1559
        self.assert_object_exists('αντίγραφα', 'αντικείμενο')
1560
        self.assert_object_exists('φάκελος', 'αντικείμενο')
1561
    
1562
    def test_move_object(self):
1563
        self.client.create_container('φάκελος')
1564
        self.upload_random_data('φάκελος', 'αντικείμενο')
1565
        
1566
        self.client.create_container('αντίγραφα')
1567
        self.client.copy_object('φάκελος', 'αντικείμενο', 'αντίγραφα',
1568
                                'αντικείμενο')
1569
        
1570
        self.assert_object_exists('αντίγραφα', 'αντικείμενο')
1571
        self.assert_object_not_exists('φάκελος', 'αντικείμενο')
1572
    
1573
    def test_delete_object(self):
1574
        pass
1575
    
1576
    def test_delete_container(self):
1577
        pass
1578
    
1579
    def test_account_meta(self):
1580
        pass
1581
    
1582
    def test_container_meta(self):
1583
        pass
1584
    
1585
    def test_obejct_meta(self):
1586
        pass
1587
    
1588
    def test_list_meta_filtering(self):
1589
        pass
1590
    
1591
    def test_groups(self):
1592
        pass
1593
    
1594
    def test_permissions(self):
1595
        pass
1596
    
1597
class AssertMappingInvariant(object):
1598
    def __init__(self, callable, *args, **kwargs):
1599
        self.callable = callable
1600
        self.args = args
1601
        self.kwargs = kwargs
1602
    
1603
    def __enter__(self):
1604
        self.map = self.callable(*self.args, **self.kwargs)
1605
        return self.map
1606
    
1607
    def __exit__(self, type, value, tb):
1608
        map = self.callable(*self.args, **self.kwargs)
1609
        for k in self.map.keys():
1610
            if is_date(self.map[k]):
1611
                continue
1612
            assert map[k] == self.map[k]
1613

    
1614
class AssertContentInvariant(object):
1615
    def __init__(self, callable, *args, **kwargs):
1616
        self.callable = callable
1617
        self.args = args
1618
        self.kwargs = kwargs
1619
    
1620
    def __enter__(self):
1621
        self.content = self.callable(*self.args, **self.kwargs)[2]
1622
        return self.content
1623
    
1624
    def __exit__(self, type, value, tb):
1625
        content = self.callable(*self.args, **self.kwargs)[2]
1626
        assert self.content == content
1627

    
1628
def get_content_splitted(response):
1629
    if response:
1630
        return response.content.split('\n')
1631

    
1632
def compute_md5_hash(data):
1633
    md5 = hashlib.md5()
1634
    offset = 0
1635
    md5.update(data)
1636
    return md5.hexdigest().lower()
1637

    
1638
def compute_block_hash(data, algorithm):
1639
    h = hashlib.new(algorithm)
1640
    h.update(data.rstrip('\x00'))
1641
    return h.hexdigest()
1642

    
1643
def get_random_data(length=500):
1644
    char_set = string.ascii_uppercase + string.digits
1645
    return ''.join(random.choice(char_set) for x in range(length))
1646

    
1647
def is_date(date):
1648
    MONTHS = 'jan feb mar apr may jun jul aug sep oct nov dec'.split()
1649
    __D = r'(?P<day>\d{2})'
1650
    __D2 = r'(?P<day>[ \d]\d)'
1651
    __M = r'(?P<mon>\w{3})'
1652
    __Y = r'(?P<year>\d{4})'
1653
    __Y2 = r'(?P<year>\d{2})'
1654
    __T = r'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})'
1655
    RFC1123_DATE = re.compile(r'^\w{3}, %s %s %s %s GMT$' % (__D, __M, __Y, __T))
1656
    RFC850_DATE = re.compile(r'^\w{6,9}, %s-%s-%s %s GMT$' % (__D, __M, __Y2, __T))
1657
    ASCTIME_DATE = re.compile(r'^\w{3} %s %s %s %s$' % (__M, __D2, __T, __Y))
1658
    for regex in RFC1123_DATE, RFC850_DATE, ASCTIME_DATE:
1659
        m = regex.match(date)
1660
        if m is not None:
1661
            return True
1662
    return False
1663

    
1664
o_names = ['kate.jpg',
1665
           'kate_beckinsale.jpg',
1666
           'How To Win Friends And Influence People.pdf',
1667
           'moms_birthday.jpg',
1668
           'poodle_strut.mov',
1669
           'Disturbed - Down With The Sickness.mp3',
1670
           'army_of_darkness.avi',
1671
           'the_mad.avi',
1672
           'photos/animals/dogs/poodle.jpg',
1673
           'photos/animals/dogs/terrier.jpg',
1674
           'photos/animals/cats/persian.jpg',
1675
           'photos/animals/cats/siamese.jpg',
1676
           'photos/plants/fern.jpg',
1677
           'photos/plants/rose.jpg',
1678
           'photos/me.jpg']
1679

    
1680
if __name__ == "__main__":
1681
    unittest.main()