Statistics
| Branch: | Tag: | Revision:

root / pithos / api / tests.py @ e7b51248

History | View | Annotate | Download (70.2 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 xml.dom import minidom
38
from StringIO import StringIO
39
import json
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
    '0009': 'κούκης'}
71

    
72
class BaseTestCase(unittest.TestCase):
73
    #TODO unauthorized request
74
    def setUp(self):
75
        self.client = Pithos_Client(DEFAULT_HOST,
76
                                    DEFAULT_AUTH,
77
                                    DEFAULT_USER,
78
                                    DEFAULT_API)
79
        self.invalid_client = Pithos_Client(DEFAULT_HOST,
80
                                                  DEFAULT_AUTH,
81
                                                  'invalid',
82
                                                  DEFAULT_API)
83
        #self.headers = {
84
        #    'account': ('x-account-container-count',
85
        #                'x-account-bytes-used',
86
        #                'last-modified',
87
        #                'content-length',
88
        #                'date',
89
        #                'content_type',
90
        #                'server',),
91
        #    'object': ('etag',
92
        #               'content-length',
93
        #               'content_type',
94
        #               'content-encoding',
95
        #               'last-modified',
96
        #               'date',
97
        #               'x-object-manifest',
98
        #               'content-range',
99
        #               'x-object-modified-by',
100
        #               'x-object-version',
101
        #               'x-object-version-timestamp',
102
        #               'server',),
103
        #    'container': ('x-container-object-count',
104
        #                  'x-container-bytes-used',
105
        #                  'content_type',
106
        #                  'last-modified',
107
        #                  'content-length',
108
        #                  'date',
109
        #                  'x-container-block-size',
110
        #                  'x-container-block-hash',
111
        #                  'x-container-policy-quota',
112
        #                  'x-container-policy-versioning',
113
        #                  'server',
114
        #                  'x-container-object-meta',
115
        #                  'x-container-policy-versioning',
116
        #                  'server',)}
117
        #
118
        #self.contentTypes = {'xml':'application/xml',
119
        #                     'json':'application/json',
120
        #                     '':'text/plain'}
121
        self.extended = {
122
            'container':(
123
                'name',
124
                'count',
125
                'bytes',
126
                'last_modified',
127
                'x_container_policy_quota',
128
                'x_container_policy_versioning',),
129
            'object':(
130
                'name',
131
                'hash',
132
                'bytes',
133
                'content_type',
134
                'content_encoding',
135
                'last_modified',)}
136
        self.return_codes = (400, 401, 404, 503,)
137
    
138
    def tearDown(self):
139
        for c in self.client.list_containers():
140
            while True:
141
                #list objects returns at most 10000 objects
142
                #so repeat until there are no more objects
143
                objects = self.client.list_objects(c)
144
                if not objects:
145
                    break
146
                for o in objects:
147
                    self.client.delete_object(c, o)
148
            self.client.delete_container(c)
149
    
150
    def assert_status(self, status, codes):
151
        l = [elem for elem in self.return_codes]
152
        if type(codes) == types.ListType:
153
            l.extend(codes)
154
        else:
155
            l.append(codes)
156
        self.assertTrue(status in l)
157
    
158
    #def assert_headers(self, headers, type, **exp_meta):
159
    #    prefix = 'x-%s-meta-' %type
160
    #    system_headers = [h for h in headers if not h.startswith(prefix)]
161
    #    for k,v in headers.items():
162
    #        if k in system_headers:
163
    #            self.assertTrue(k in headers[type])
164
    #        elif exp_meta:
165
    #            k = k.split(prefix)[-1]
166
    #            self.assertEqual(v, exp_meta[k])
167
    
168
    def assert_extended(self, data, format, type, size=10000):
169
        if format == 'xml':
170
            self._assert_xml(data, type, size)
171
        elif format == 'json':
172
            self._assert_json(data, type, size)
173
    
174
    def _assert_json(self, data, type, size):
175
        convert = lambda s: s.lower()
176
        info = [convert(elem) for elem in self.extended[type]]
177
        self.assertTrue(len(data) <= size)
178
        for item in info:
179
            for i in data:
180
                if 'subdir' in i.keys():
181
                    continue
182
                self.assertTrue(item in i.keys())
183
    
184
    def _assert_xml(self, data, type, size):
185
        convert = lambda s: s.lower()
186
        info = [convert(elem) for elem in self.extended[type]]
187
        try:
188
            info.remove('content_encoding')
189
        except ValueError:
190
            pass
191
        xml = data
192
        entities = xml.getElementsByTagName(type)
193
        self.assertTrue(len(entities) <= size)
194
        for e in entities:
195
            for item in info:
196
                self.assertTrue(e.getElementsByTagName(item))
197
    
198
    def assert_raises_fault(self, status, callableObj, *args, **kwargs):
199
        """
200
        asserts that a Fault with a specific status is raised
201
        when callableObj is called with the specific arguments
202
        """
203
        try:
204
            r = callableObj(*args, **kwargs)
205
            self.fail('Should never reach here')
206
        except Fault, f:
207
            self.failUnless(f.status == status)
208
    
209
    def assert_not_raises_fault(self, status, callableObj, *args, **kwargs):
210
        """
211
        asserts that a Fault with a specific status is not raised
212
        when callableObj is called with the specific arguments
213
        """
214
        try:
215
            r = callableObj(*args, **kwargs)
216
        except Fault, f:
217
            self.failIf(f.status == status)
218
    
219
    def assert_container_exists(self, container):
220
        """
221
        asserts the existence of a container
222
        """
223
        try:
224
            self.client.retrieve_container_metadata(container)
225
        except Fault, f:
226
            self.failIf(f.status == 404)
227
    
228
    def assert_container_not_exists(self, container):
229
        """
230
        asserts there is no such a container
231
        """
232
        self.assert_raises_fault(404, self.client.retrieve_container_metadata,
233
                                 container)
234
    
235
    def assert_object_exists(self, container, object):
236
        """
237
        asserts the existence of an object
238
        """
239
        try:
240
            self.client.retrieve_object_metadata(container, object)
241
        except Fault, f:
242
            self.failIf(f.status == 404)
243
    
244
    def assert_object_not_exists(self, container, object):
245
        """
246
        asserts there is no such an object
247
        """
248
        self.assert_raises_fault(404, self.client.retrieve_object_metadata,
249
                                 container, object)
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, StringIO(obj['data']),
280
                                      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.account = 'test'
290
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
291
        for item in self.containers:
292
            self.client.create_container(item)
293
        
294
                #keep track of initial account groups
295
        self.initial_groups = self.client.retrieve_account_groups()
296
        
297
        #keep track of initial account meta
298
        self.initial_meta = self.client.retrieve_account_metadata(restricted=True)
299
        
300
        meta = {'foo':'bar'}
301
        self.client.update_account_metadata(**meta)
302
        self.updated_meta = self.initial_meta.update(meta)
303
    
304
    def tearDown(self):
305
        #delete additionally created meta
306
        l = []
307
        for m in self.client.retrieve_account_metadata(restricted=True):
308
            if m not in self.initial_meta:
309
                l.append(m)
310
        self.client.delete_account_metadata(l)
311
        
312
        #delete additionally created groups
313
        l = []
314
        for g in self.client.retrieve_account_groups():
315
            if g not in self.initial_groups:
316
                l.append(g)
317
        self.client.unset_account_groups(l)
318
        
319
        BaseTestCase.tearDown(self)
320
    
321
    def test_get_account_meta(self):
322
        meta = self.client.retrieve_account_metadata()
323
        
324
        containers = self.client.list_containers()
325
        l = str(len(containers))
326
        self.assertEqual(meta['x-account-container-count'], l)
327
        size = 0
328
        for c in containers:
329
            m = self.client.retrieve_container_metadata(c)
330
            size = size + int(m['x-container-bytes-used'])
331
        self.assertEqual(meta['x-account-bytes-used'], str(size))
332
    
333
    def test_get_account_401(self):
334
        self.assert_raises_fault(401,
335
                                 self.invalid_client.retrieve_account_metadata)
336
    
337
    def test_get_account_meta_until(self):
338
        t = datetime.datetime.utcnow()
339
        past = t - datetime.timedelta(minutes=-15)
340
        past = int(_time.mktime(past.timetuple()))
341
        
342
        meta = {'premium':True}
343
        self.client.update_account_metadata(**meta)
344
        meta = self.client.retrieve_account_metadata(restricted=True,
345
                                                     until=past)
346
        self.assertTrue('premium' not in meta)
347
        
348
        meta = self.client.retrieve_account_metadata(restricted=True)
349
        self.assertTrue('premium' in meta)
350
    
351
    def test_get_account_meta_until_invalid_date(self):
352
        meta = {'premium':True}
353
        self.client.update_account_metadata(**meta)
354
        meta = self.client.retrieve_account_metadata(restricted=True,
355
                                                     until='kshfksfh')
356
        self.assertTrue('premium' in meta)
357
    
358
class AccountGet(BaseTestCase):
359
    def setUp(self):
360
        BaseTestCase.setUp(self)
361
        self.account = 'test'
362
        #create some containers
363
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
364
        for item in self.containers:
365
            self.client.create_container(item)
366
    
367
    def test_list(self):
368
        #list containers
369
        containers = self.client.list_containers()
370
        self.assertEquals(self.containers, containers)
371
    
372
    def test_list_401(self):
373
        self.assert_raises_fault(401, self.invalid_client.list_containers)
374
    
375
    def test_list_with_limit(self):
376
        limit = 2
377
        containers = self.client.list_containers(limit=limit)
378
        self.assertEquals(len(containers), limit)
379
        self.assertEquals(self.containers[:2], containers)
380
    
381
    def test_list_with_marker(self):
382
        l = 2
383
        m = 'bananas'
384
        containers = self.client.list_containers(limit=l, marker=m)
385
        i = self.containers.index(m) + 1
386
        self.assertEquals(self.containers[i:(i+l)], containers)
387
        
388
        m = 'oranges'
389
        containers = self.client.list_containers(limit=l, marker=m)
390
        i = self.containers.index(m) + 1
391
        self.assertEquals(self.containers[i:(i+l)], containers)
392
    
393
    def test_list_json_with_marker(self):
394
        l = 2
395
        m = 'bananas'
396
        containers = self.client.list_containers(limit=l, marker=m, format='json')
397
        self.assert_extended(containers, 'json', 'container', l)
398
        self.assertEqual(containers[0]['name'], 'kiwis')
399
        self.assertEqual(containers[1]['name'], 'oranges')
400
    
401
    def test_list_xml_with_marker(self):
402
        l = 2
403
        m = 'oranges'
404
        xml = self.client.list_containers(limit=l, marker=m, format='xml')
405
        self.assert_extended(xml, 'xml', 'container', l)
406
        nodes = xml.getElementsByTagName('name')
407
        self.assertEqual(len(nodes), 1)
408
        self.assertEqual(nodes[0].childNodes[0].data, 'pears')
409
    
410
    def test_if_modified_since(self):
411
        t = datetime.datetime.utcnow()
412
        t2 = t - datetime.timedelta(minutes=10)
413
        
414
        #add a new container
415
        self.client.create_container('dummy')
416
        
417
        for f in DATE_FORMATS:
418
            past = t2.strftime(f)
419
            try:
420
                c = self.client.list_containers(if_modified_since=past)
421
                self.assertEqual(len(c), len(self.containers) + 1)
422
            except Fault, f:
423
                self.failIf(f.status == 304) #fail if not modified
424
    
425
    def test_if_modified_since_invalid_date(self):
426
        c = self.client.list_containers(if_modified_since='')
427
        self.assertEqual(len(c), len(self.containers))
428
    
429
    def test_if_not_modified_since(self):
430
        now = datetime.datetime.utcnow()
431
        since = now + datetime.timedelta(1)
432
        
433
        for f in DATE_FORMATS:
434
            args = {'if_modified_since':'%s' %since.strftime(f)}
435
            
436
            #assert not modified
437
            self.assert_raises_fault(304, self.client.list_containers, **args)
438
    
439
    def test_if_unmodified_since(self):
440
        now = datetime.datetime.utcnow()
441
        since = now + datetime.timedelta(1)
442
        
443
        for f in DATE_FORMATS:
444
            c = self.client.list_containers(if_unmodified_since=since.strftime(f))
445
            
446
            #assert success
447
            self.assertEqual(self.containers, c)
448
    
449
    def test_if_unmodified_since_precondition_failed(self):
450
        t = datetime.datetime.utcnow()
451
        t2 = t - datetime.timedelta(minutes=10)
452
        
453
        #add a new container
454
        self.client.create_container('dummy')
455
        
456
        for f in DATE_FORMATS:
457
            past = t2.strftime(f)
458
            
459
            args = {'if_unmodified_since':'%s' %past}
460
            
461
            #assert precondition failed
462
            self.assert_raises_fault(412, self.client.list_containers, **args)
463
    
464
class AccountPost(BaseTestCase):
465
    def setUp(self):
466
        BaseTestCase.setUp(self)
467
        self.account = 'test'
468
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
469
        for item in self.containers:
470
            self.client.create_container(item)
471
        
472
        #keep track of initial account groups
473
        self.initial_groups = self.client.retrieve_account_groups()
474
        
475
        #keep track of initial account meta
476
        self.initial_meta = self.client.retrieve_account_metadata(restricted=True)
477
        
478
        meta = {'foo':'bar'}
479
        self.client.update_account_metadata(**meta)
480
        self.updated_meta = self.initial_meta.update(meta)
481
    
482
    def tearDown(self):
483
        #delete additionally created meta
484
        l = []
485
        for m in self.client.retrieve_account_metadata(restricted=True):
486
            if m not in self.initial_meta:
487
                l.append(m)
488
        self.client.delete_account_metadata(l)
489
        
490
        #delete additionally created groups
491
        l = []
492
        for g in self.client.retrieve_account_groups():
493
            if g not in self.initial_groups:
494
                l.append(g)
495
        self.client.unset_account_groups(l)
496
        
497
        BaseTestCase.tearDown(self)
498
    
499
    def test_update_meta(self):
500
        with AssertMappingInvariant(self.client.retrieve_account_groups):
501
            meta = {'test':'test', 'tost':'tost'}
502
            self.client.update_account_metadata(**meta)
503
            
504
            meta.update(self.initial_meta)
505
            self.assertEqual(meta,
506
                             self.client.retrieve_account_metadata(
507
                                restricted=True))
508
        
509
    def test_invalid_account_update_meta(self):
510
        meta = {'test':'test', 'tost':'tost'}
511
        self.assert_raises_fault(401,
512
                                 self.invalid_client.update_account_metadata,
513
                                 **meta)
514
    
515
    def test_reset_meta(self):
516
        with AssertMappingInvariant(self.client.retrieve_account_groups):
517
            meta = {'test':'test', 'tost':'tost'}
518
            self.client.update_account_metadata(**meta)
519
            
520
            meta = {'test':'test33'}
521
            self.client.reset_account_metadata(**meta)
522
            
523
            self.assertEqual(meta, self.client.retrieve_account_metadata(restricted=True))
524
    
525
    def test_delete_meta(self):
526
        with AssertMappingInvariant(self.client.retrieve_account_groups):
527
            meta = {'test':'test', 'tost':'tost'}
528
            self.client.update_account_metadata(**meta)
529
            
530
            self.client.delete_account_metadata(meta.keys())
531
            
532
            account_meta = self.client.retrieve_account_metadata(restricted=True)
533
            for m in meta:
534
                self.assertTrue(m not in account_meta.keys())
535
    
536
    def test_set_account_groups(self):
537
        with AssertMappingInvariant(self.client.retrieve_account_metadata):
538
            groups = {'pithosdev':'verigak,gtsouk,chazapis'}
539
            self.client.set_account_groups(**groups)
540
            
541
            self.assertEqual(groups, self.client.retrieve_account_groups())
542
            
543
            more_groups = {'clientsdev':'pkanavos,mvasilak'}
544
            self.client.set_account_groups(**more_groups)
545
            
546
            groups.update(more_groups)
547
            self.assertEqual(set(groups['pithosdev']),
548
                             set(self.client.retrieve_account_groups()['pithosdev']))
549
    
550
    def test_reset_account_groups(self):
551
        with AssertMappingInvariant(self.client.retrieve_account_metadata):
552
            groups = {'pithosdev':'verigak,gtsouk,chazapis',
553
                      'clientsdev':'pkanavos,mvasilak'}
554
            self.client.set_account_groups(**groups)
555
            
556
            self.assertEqual(groups, self.client.retrieve_account_groups())
557
            
558
            groups = {'pithosdev':'verigak,gtsouk,chazapis,papagian'}
559
            self.client.reset_account_groups(**groups)
560
            
561
            print '#', groups, self.client.retrieve_account_groups()
562
            self.assertEqual(groups['pithosdev'].split(','),
563
                             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.account = 'test'
579
        self.container = 'apples'
580
        self.client.create_container(self.container)
581
    
582
    def test_get_meta(self):
583
        meta = {'trash':'true'}
584
        t1 = datetime.datetime.utcnow()
585
        o = self.upload_random_data(self.container, o_names[0], **meta)
586
        if o:
587
            headers = self.client.retrieve_container_metadata(self.container)
588
            self.assertEqual(headers['x-container-object-count'], '1')
589
            self.assertEqual(headers['x-container-bytes-used'], str(len(o['data'])))
590
            t2 = datetime.datetime.strptime(headers['last-modified'], DATE_FORMATS[2])
591
            delta = (t2 - t1)
592
            threashold = datetime.timedelta(seconds=1) 
593
            self.assertTrue(delta < threashold)
594
            self.assertTrue(headers['x-container-object-meta'])
595
            self.assertTrue('Trash' in headers['x-container-object-meta'])
596

    
597
class ContainerGet(BaseTestCase):
598
    def setUp(self):
599
        BaseTestCase.setUp(self)
600
        self.account = 'test'
601
        self.container = ['pears', 'apples']
602
        for c in self.container:
603
            self.client.create_container(c)
604
        self.obj = []
605
        for o in o_names[:8]:
606
            self.obj.append(self.upload_random_data(self.container[0], o))
607
        for o in o_names[8:]:
608
            self.obj.append(self.upload_random_data(self.container[1], o))
609
    
610
    def test_list_objects(self):
611
        objects = self.client.list_objects(self.container[0])
612
        l = [elem['name'] for elem in self.obj[:8]]
613
        l.sort()
614
        self.assertEqual(objects, l)
615
    
616
    def test_list_objects_containing_slash(self):
617
        self.client.create_container('test')
618
        self.upload_random_data('test', '/objectname')
619
        
620
        objects = self.client.list_objects('test')
621
        self.assertEqual(objects, ['/objectname'])
622
        
623
        objects = self.client.list_objects('test', format='json')
624
        self.assertEqual(objects[0]['name'], '/objectname')
625
        
626
        objects = self.client.list_objects('test', format='xml')
627
        self.assert_extended(objects, 'xml', 'object')
628
        node_name = objects.getElementsByTagName('name')[0]
629
        self.assertEqual(node_name.firstChild.data, '/objectname')
630
        
631
        #objects = self.client.list_objects('test', prefix='/')
632
        #self.assertEqual(objects, ['/objectname'])
633
        #
634
        #objects = self.client.list_objects('test', path='/')
635
        #self.assertEqual(objects, ['/objectname'])
636
        #
637
        #objects = self.client.list_objects('test', prefix='/', delimiter='n')
638
        #self.assertEqual(objects, ['/object'])
639

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

    
807
class ContainerPut(BaseTestCase):
808
    def setUp(self):
809
        BaseTestCase.setUp(self)
810
        self.account = 'test'
811
        self.containers = ['c1', 'c2']
812
    
813
    def test_create(self):
814
        self.client.create_container(self.containers[0])
815
        containers = self.client.list_containers()
816
        self.assertTrue(self.containers[0] in containers)
817
        self.assert_container_exists(self.containers[0])
818
    
819
    def test_create_twice(self):
820
        self.client.create_container(self.containers[0])
821
        self.assertTrue(not self.client.create_container(self.containers[0]))
822
    
823
class ContainerPost(BaseTestCase):
824
    def setUp(self):
825
        BaseTestCase.setUp(self)
826
        self.account = 'test'
827
        self.container = 'apples'
828
        self.client.create_container(self.container)
829
    
830
    def test_update_meta(self):
831
        meta = {'test':'test33',
832
                'tost':'tost22'}
833
        self.client.update_container_metadata(self.container, **meta)
834
        headers = self.client.retrieve_container_metadata(self.container)
835
        for k,v in meta.items():
836
            k = 'x-container-meta-%s' % k
837
            self.assertTrue(headers[k])
838
            self.assertEqual(headers[k], v)
839

    
840
class ContainerDelete(BaseTestCase):
841
    def setUp(self):
842
        BaseTestCase.setUp(self)
843
        self.account = 'test'
844
        self.containers = ['c1', 'c2']
845
        for c in self.containers:
846
            self.client.create_container(c)
847
        self.upload_random_data(self.containers[1], o_names[0])
848
    
849
    def test_delete(self):
850
        status = self.client.delete_container(self.containers[0])[0]
851
        self.assertEqual(status, 204)
852
    
853
    def test_delete_non_empty(self):
854
        self.assert_raises_fault(409, self.client.delete_container,
855
                                 self.containers[1])
856
    
857
    def test_delete_invalid(self):
858
        self.assert_raises_fault(404, self.client.delete_container, 'c3')
859

    
860
class ObjectHead(BaseTestCase):
861
    pass
862

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

    
1200
class ObjectPut(BaseTestCase):
1201
    def setUp(self):
1202
        BaseTestCase.setUp(self)
1203
        self.account = 'test'
1204
        self.container = 'c1'
1205
        self.client.create_container(self.container)
1206
    
1207
    def test_upload(self):
1208
        name = o_names[0]
1209
        meta = {'test':'test1'}
1210
        o = self.upload_random_data(self.container, name, **meta)
1211
        
1212
        headers = self.client.retrieve_object_metadata(self.container,
1213
                                                       name,
1214
                                                       restricted=True)
1215
        self.assertTrue('test' in headers.keys())
1216
        self.assertEqual(headers['test'], meta['test'])
1217
        
1218
        #assert uploaded content
1219
        status, h, data = self.client.request_object(self.container, name)
1220
        self.assertEqual(len(o['data']), int(h['content-length']))
1221
        self.assertEqual(o['data'], data)
1222
        
1223
        #assert content-type
1224
        self.assertEqual(h['content-type'], o['meta']['content_type'])
1225
    
1226
    def _test_maximum_upload_size_exceeds(self):
1227
        name = o_names[0]
1228
        meta = {'test':'test1'}
1229
        #upload 100MB
1230
        length=1024*1024*100
1231
        self.assert_raises_fault(400, self.upload_random_data, self.container,
1232
                                 name, length, **meta)
1233
    
1234
    def test_upload_with_name_containing_slash(self):
1235
        name = '/%s' % o_names[0]
1236
        meta = {'test':'test1'}
1237
        o = self.upload_random_data(self.container, name, **meta)
1238
        
1239
        self.assertEqual(o['data'],
1240
                         self.client.retrieve_object(self.container, name))
1241
        
1242
        self.assertTrue(name in self.client.list_objects(self.container))
1243
    
1244
    def test_create_directory_marker(self):
1245
        self.client.create_directory_marker(self.container, 'foo')
1246
        meta = self.client.retrieve_object_metadata(self.container, 'foo')
1247
        self.assertEqual(meta['content-length'], '0')
1248
        self.assertEqual(meta['content-type'], 'application/directory')
1249

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

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

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

    
1504
class ObjectDelete(BaseTestCase):
1505
    def setUp(self):
1506
        BaseTestCase.setUp(self)
1507
        self.account = 'test'
1508
        self.containers = ['c1', 'c2']
1509
        for c in self.containers:
1510
            self.client.create_container(c)
1511
        self.obj = self.upload_random_data(self.containers[0], o_names[0])
1512
    
1513
    def test_delete(self):
1514
        #perform delete object
1515
        self.client.delete_object(self.containers[0], self.obj['name'])[0]
1516
    
1517
    def test_delete_invalid(self):
1518
        #assert item not found
1519
        self.assert_raises_fault(404, self.client.delete_object, self.containers[1],
1520
                                 self.obj['name'])
1521

    
1522
class ListSharing(BaseTestCase):
1523
    def setUp(self):
1524
        BaseTestCase.setUp(self)
1525
        self.client.create_container('c')
1526
        for i in range(2):
1527
            self.upload_random_data('c', 'o%s' %i)
1528
        accounts = OTHER_ACCOUNTS.copy()
1529
        self.o1_sharing_with = accounts.popitem()
1530
        self.o1_sharing = [self.o1_sharing_with[1]]
1531
        self.client.share_object('c', 'o1', self.o1_sharing, read=True)
1532
        
1533
        l = []
1534
        for i in range(2):
1535
            l.append(accounts.popitem())
1536
        #self.client.set_account_groups({'pithos-dev':'chazapis,verigak,papagian'})
1537
        #self.o2_sharing = 'write=%s' % 
1538
        #self.client.share_object('c', 'o2', self.o2_sharing)
1539
    
1540
    def test_listing(self):
1541
        self.other = Pithos_Client(DEFAULT_HOST,
1542
                              self.o1_sharing_with[0],
1543
                              self.o1_sharing_with[1],
1544
                              DEFAULT_API)
1545
        self.assertTrue('test' in self.other.list_shared_by_others())
1546

    
1547
class TestGreek(BaseTestCase):
1548
    def setUp(self):
1549
        BaseTestCase.setUp(self)
1550
        #keep track of initial account groups
1551
        self.initial_groups = self.client.retrieve_account_groups()
1552
        
1553
        #keep track of initial account meta
1554
        self.initial_meta = self.client.retrieve_account_metadata(restricted=True)
1555
    
1556
    def tearDown(self):
1557
        #delete additionally created meta
1558
        l = []
1559
        for m in self.client.retrieve_account_metadata(restricted=True):
1560
            if m not in self.initial_meta:
1561
                l.append(m)
1562
        self.client.delete_account_metadata(l)
1563
        
1564
        #delete additionally created groups
1565
        l = []
1566
        for g in self.client.retrieve_account_groups():
1567
            if g not in self.initial_groups:
1568
                l.append(g)
1569
        self.client.unset_account_groups(l)
1570
        
1571
        BaseTestCase.tearDown(self)
1572
    
1573
    def test_create_container(self):
1574
        self.client.create_container('φάκελος')
1575
        self.assert_container_exists('φάκελος')
1576
        
1577
        self.assertTrue('φάκελος' in self.client.list_containers())
1578
    
1579
    def test_create_object(self):
1580
        self.client.create_container('φάκελος')
1581
        self.upload_random_data('φάκελος', 'αντικείμενο')
1582
        
1583
        self.assert_object_exists('φάκελος', 'αντικείμενο')
1584
        self.assertTrue('αντικείμενο' in self.client.list_objects('φάκελος'))
1585
    
1586
    def test_copy_object(self):
1587
        src_container = 'φάκελος'
1588
        src_object = 'αντικείμενο'
1589
        dest_container = 'αντίγραφα'
1590
        dest_object = 'ασφαλές-αντίγραφο'
1591
        
1592
        self.client.create_container(src_container)
1593
        self.upload_random_data(src_container, src_object)
1594
        
1595
        self.client.create_container(dest_container)
1596
        self.client.copy_object(src_container, src_object, dest_container,
1597
                                dest_object)
1598
        
1599
        self.assert_object_exists(src_container, src_object)
1600
        self.assert_object_exists(dest_container, dest_object)
1601
        self.assertTrue(dest_object in self.client.list_objects(dest_container))
1602
    
1603
    def test_move_object(self):
1604
        src_container = 'φάκελος'
1605
        src_object = 'αντικείμενο'
1606
        dest_container = 'αντίγραφα'
1607
        dest_object = 'ασφαλές-αντίγραφο'
1608
        
1609
        self.client.create_container(src_container)
1610
        self.upload_random_data(src_container, src_object)
1611
        
1612
        self.client.create_container(dest_container)
1613
        self.client.move_object(src_container, src_object, dest_container,
1614
                                dest_object)
1615
        
1616
        self.assert_object_not_exists(src_container, src_object)
1617
        self.assert_object_exists(dest_container, dest_object)
1618
        self.assertTrue(dest_object in self.client.list_objects(dest_container))
1619
    
1620
    def test_delete_object(self):
1621
        self.client.create_container('φάκελος')
1622
        self.upload_random_data('φάκελος', 'αντικείμενο')
1623
        self.assert_object_exists('φάκελος', 'αντικείμενο')
1624
    
1625
        self.client.delete_object('φάκελος', 'αντικείμενο')
1626
        self.assert_object_not_exists('φάκελος', 'αντικείμενο')
1627
        self.assertTrue('αντικείμενο' not in self.client.list_objects('φάκελος'))
1628
    
1629
    def test_delete_container(self):
1630
        self.client.create_container('φάκελος')
1631
        self.assert_container_exists('φάκελος')
1632
        
1633
        self.client.delete_container('φάκελος')
1634
        self.assert_container_not_exists('φάκελος')
1635
        self.assertTrue('φάκελος' not in self.client.list_containers())
1636

    
1637
    def test_account_meta(self):
1638
        meta = {'ποιότητα':'ΑΑΑ'}
1639
        self.client.update_account_metadata(**meta)
1640
        meta = self.client.retrieve_account_metadata(restricted=True)
1641
        self.assertTrue('ποιότητα' in meta.keys())
1642
        self.assertEqual(meta['ποιότητα'], 'ΑΑΑ')
1643
    
1644
    def test_container_meta(self):
1645
        meta = {'ποιότητα':'ΑΑΑ'}
1646
        self.client.create_container('φάκελος', **meta)
1647
        
1648
        meta = self.client.retrieve_container_metadata('φάκελος', restricted=True)
1649
        self.assertTrue('ποιότητα' in meta.keys())
1650
        self.assertEqual(meta['ποιότητα'], 'ΑΑΑ')
1651
    
1652
    def test_object_meta(self):
1653
        self.client.create_container('φάκελος')
1654
        meta = {'ποιότητα':'ΑΑΑ'}
1655
        self.upload_random_data('φάκελος', 'αντικείμενο', **meta)
1656
        
1657
        meta = self.client.retrieve_object_metadata('φάκελος', 'αντικείμενο',
1658
                                                    restricted=True)
1659
        self.assertTrue('ποιότητα' in meta.keys())
1660
        self.assertEqual(meta['ποιότητα'], 'ΑΑΑ')    
1661
    
1662
    def test_list_meta_filtering(self):
1663
        self.client.create_container('φάκελος')
1664
        meta = {'ποιότητα':'ΑΑΑ'}
1665
        self.upload_random_data('φάκελος', 'ο1', **meta)
1666
        self.upload_random_data('φάκελος', 'ο2')
1667
        self.upload_random_data('φάκελος', 'ο3')
1668
        
1669
        meta = {'ποσότητα':'μεγάλη'}
1670
        self.client.update_object_metadata('φάκελος', 'ο2', **meta)
1671
        objects = self.client.list_objects('φάκελος', meta='ποιότητα, ποσότητα')
1672
        self.assertTrue('ο1' in objects)
1673
        self.assertTrue('ο2' in objects)
1674
        self.assertTrue('ο3' not in objects)
1675
    
1676
    def test_groups(self):
1677
        #create a group
1678
        groups = {'σεφς':'chazapis,κούκης'}
1679
        self.client.set_account_groups(**groups)
1680
        groups.update(self.initial_groups)
1681
        self.assertEqual(groups['σεφς'],
1682
                         self.client.retrieve_account_groups()['σεφς'])
1683
        
1684
    
1685
    def test_permissions(self):
1686
        pass
1687
    
1688
class AssertMappingInvariant(object):
1689
    def __init__(self, callable, *args, **kwargs):
1690
        self.callable = callable
1691
        self.args = args
1692
        self.kwargs = kwargs
1693
    
1694
    def __enter__(self):
1695
        self.map = self.callable(*self.args, **self.kwargs)
1696
        return self.map
1697
    
1698
    def __exit__(self, type, value, tb):
1699
        map = self.callable(*self.args, **self.kwargs)
1700
        for k in self.map.keys():
1701
            if is_date(self.map[k]):
1702
                continue
1703
            assert map[k] == self.map[k]
1704

    
1705
class AssertContentInvariant(object):
1706
    def __init__(self, callable, *args, **kwargs):
1707
        self.callable = callable
1708
        self.args = args
1709
        self.kwargs = kwargs
1710
    
1711
    def __enter__(self):
1712
        self.content = self.callable(*self.args, **self.kwargs)[2]
1713
        return self.content
1714
    
1715
    def __exit__(self, type, value, tb):
1716
        content = self.callable(*self.args, **self.kwargs)[2]
1717
        assert self.content == content
1718

    
1719
def get_content_splitted(response):
1720
    if response:
1721
        return response.content.split('\n')
1722

    
1723
def compute_md5_hash(data):
1724
    md5 = hashlib.md5()
1725
    offset = 0
1726
    md5.update(data)
1727
    return md5.hexdigest().lower()
1728

    
1729
def compute_block_hash(data, algorithm):
1730
    h = hashlib.new(algorithm)
1731
    h.update(data.rstrip('\x00'))
1732
    return h.hexdigest()
1733

    
1734
def get_random_data(length=500):
1735
    char_set = string.ascii_uppercase + string.digits
1736
    return ''.join(random.choice(char_set) for x in range(length))
1737

    
1738
def is_date(date):
1739
    MONTHS = 'jan feb mar apr may jun jul aug sep oct nov dec'.split()
1740
    __D = r'(?P<day>\d{2})'
1741
    __D2 = r'(?P<day>[ \d]\d)'
1742
    __M = r'(?P<mon>\w{3})'
1743
    __Y = r'(?P<year>\d{4})'
1744
    __Y2 = r'(?P<year>\d{2})'
1745
    __T = r'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})'
1746
    RFC1123_DATE = re.compile(r'^\w{3}, %s %s %s %s GMT$' % (__D, __M, __Y, __T))
1747
    RFC850_DATE = re.compile(r'^\w{6,9}, %s-%s-%s %s GMT$' % (__D, __M, __Y2, __T))
1748
    ASCTIME_DATE = re.compile(r'^\w{3} %s %s %s %s$' % (__M, __D2, __T, __Y))
1749
    for regex in RFC1123_DATE, RFC850_DATE, ASCTIME_DATE:
1750
        m = regex.match(date)
1751
        if m is not None:
1752
            return True
1753
    return False
1754

    
1755
o_names = ['kate.jpg',
1756
           'kate_beckinsale.jpg',
1757
           'How To Win Friends And Influence People.pdf',
1758
           'moms_birthday.jpg',
1759
           'poodle_strut.mov',
1760
           'Disturbed - Down With The Sickness.mp3',
1761
           'army_of_darkness.avi',
1762
           'the_mad.avi',
1763
           'photos/animals/dogs/poodle.jpg',
1764
           'photos/animals/dogs/terrier.jpg',
1765
           'photos/animals/cats/persian.jpg',
1766
           'photos/animals/cats/siamese.jpg',
1767
           'photos/plants/fern.jpg',
1768
           'photos/plants/rose.jpg',
1769
           'photos/me.jpg']
1770

    
1771
if __name__ == "__main__":
1772
    unittest.main()