Statistics
| Branch: | Tag: | Revision:

root / pithos / api / tests.py @ f7667baf

History | View | Annotate | Download (60.1 kB)

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

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

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

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

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

    
263
class AccountHead(BaseTestCase):
264
    def setUp(self):
265
        BaseTestCase.setUp(self)
266
        self.account = 'test'
267
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
268
        for item in self.containers:
269
            self.client.create_container(item)
270
        
271
                #keep track of initial account groups
272
        self.initial_groups = self.client.retrieve_account_groups()
273
        
274
        #keep track of initial account meta
275
        self.initial_meta = self.client.retrieve_account_metadata(restricted=True)
276
        
277
        meta = {'foo':'bar'}
278
        self.client.update_account_metadata(**meta)
279
        self.updated_meta = self.initial_meta.update(meta)
280
    
281
    def tearDown(self):
282
        #delete additionally created meta
283
        l = []
284
        for m in self.client.retrieve_account_metadata(restricted=True):
285
            if m not in self.initial_meta:
286
                l.append(m)
287
        self.client.delete_account_metadata(l)
288
        
289
        #delete additionally created groups
290
        l = []
291
        for g in self.client.retrieve_account_groups():
292
            if g not in self.initial_groups:
293
                l.append(g)
294
        self.client.unset_account_groups(l)
295
        
296
        #print '#', self.client.retrieve_account_groups()
297
        #print '#', self.client.retrieve_account_metadata(restricted=True)
298
        BaseTestCase.tearDown(self)
299
    
300
    def test_get_account_meta(self):
301
        meta = self.client.retrieve_account_metadata()
302
        
303
        containers = self.client.list_containers()
304
        l = str(len(containers))
305
        self.assertEqual(meta['x-account-container-count'], l)
306
        size = 0
307
        for c in containers:
308
            m = self.client.retrieve_container_metadata(c)
309
            size = size + int(m['x-container-bytes-used'])
310
        self.assertEqual(meta['x-account-bytes-used'], str(size))
311
    
312
    def test_get_account_401(self):
313
        self.assert_raises_fault(401,
314
                                 self.invalid_client.retrieve_account_metadata)
315
    
316
    def test_get_account_meta_until(self):
317
        t = datetime.datetime.utcnow()
318
        past = t - datetime.timedelta(minutes=-15)
319
        past = int(_time.mktime(past.timetuple()))
320
        
321
        meta = {'premium':True}
322
        self.client.update_account_metadata(**meta)
323
        meta = self.client.retrieve_account_metadata(restricted=True,
324
                                                     until=past)
325
        self.assertTrue('premium' not in meta)
326
        
327
        meta = self.client.retrieve_account_metadata(restricted=True)
328
        self.assertTrue('premium' in meta)
329
    
330
    def test_get_account_meta_until_invalid_date(self):
331
        meta = {'premium':True}
332
        self.client.update_account_metadata(**meta)
333
        meta = self.client.retrieve_account_metadata(restricted=True,
334
                                                     until='kshfksfh')
335
        self.assertTrue('premium' in meta)
336

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

    
575
class ContainerGet(BaseTestCase):
576
    def setUp(self):
577
        BaseTestCase.setUp(self)
578
        self.account = 'test'
579
        self.container = ['pears', 'apples']
580
        for c in self.container:
581
            self.client.create_container(c)
582
        self.obj = []
583
        for o in o_names[:8]:
584
            self.obj.append(self.upload_random_data(self.container[0], o))
585
        for o in o_names[8:]:
586
            self.obj.append(self.upload_random_data(self.container[1], o))
587
    
588
    def test_list_objects(self):
589
        objects = self.client.list_objects(self.container[0])
590
        l = [elem['name'] for elem in self.obj[:8]]
591
        l.sort()
592
        self.assertEqual(objects, l)
593
    
594
    def test_list_objects_with_limit_marker(self):
595
        objects = self.client.list_objects(self.container[0], limit=2)
596
        l = [elem['name'] for elem in self.obj[:8]]
597
        l.sort()
598
        self.assertEqual(objects, l[:2])
599
        
600
        markers = ['How To Win Friends And Influence People.pdf',
601
                   'moms_birthday.jpg']
602
        limit = 4
603
        for m in markers:
604
            objects = self.client.list_objects(self.container[0], limit=limit,
605
                                               marker=m)
606
            l = [elem['name'] for elem in self.obj[:8]]
607
            l.sort()
608
            start = l.index(m) + 1
609
            end = start + limit
610
            end = len(l) >= end and end or len(l)
611
            self.assertEqual(objects, l[start:end])
612
    
613
    def test_list_pseudo_hierarchical_folders(self):
614
        objects = self.client.list_objects(self.container[1], prefix='photos',
615
                                           delimiter='/')
616
        self.assertEquals(['photos/animals/', 'photos/me.jpg',
617
                           'photos/plants/'], objects)
618
        
619
        objects = self.client.list_objects(self.container[1],
620
                                           prefix='photos/animals',
621
                                           delimiter='/')
622
        l = ['photos/animals/cats/', 'photos/animals/dogs/']
623
        self.assertEquals(l, objects)
624
        
625
        objects = self.client.list_objects(self.container[1], path='photos')
626
        self.assertEquals(['photos/me.jpg'], objects)
627
    
628
    def test_extended_list_json(self):
629
        objects = self.client.list_objects(self.container[1], format='json',
630
                                           limit=2, prefix='photos/animals',
631
                                           delimiter='/')
632
        self.assertEqual(objects[0]['subdir'], 'photos/animals/cats/')
633
        self.assertEqual(objects[1]['subdir'], 'photos/animals/dogs/')
634
    
635
    def test_extended_list_xml(self):
636
        xml = self.client.list_objects(self.container[1], format='xml', limit=4,
637
                                       prefix='photos', delimiter='/')
638
        dirs = xml.getElementsByTagName('subdir')
639
        self.assertEqual(len(dirs), 2)
640
        self.assertEqual(dirs[0].attributes['name'].value, 'photos/animals/')
641
        self.assertEqual(dirs[1].attributes['name'].value, 'photos/plants/')
642
        
643
        objects = xml.getElementsByTagName('name')
644
        self.assertEqual(len(objects), 1)
645
        self.assertEqual(objects[0].childNodes[0].data, 'photos/me.jpg')
646
    
647
    def test_list_meta_double_matching(self):
648
        meta = {'quality':'aaa', 'stock':'true'}
649
        self.client.update_object_metadata(self.container[0],
650
                                           self.obj[0]['name'], **meta)
651
        obj = self.client.list_objects(self.container[0], meta='Quality,Stock')
652
        self.assertEqual(len(obj), 1)
653
        self.assertTrue(obj, self.obj[0]['name'])
654
    
655
    def test_list_using_meta(self):
656
        meta = {'quality':'aaa'}
657
        for o in self.obj[:2]:
658
            self.client.update_object_metadata(self.container[0], o['name'],
659
                                               **meta)
660
        meta = {'stock':'true'}
661
        for o in self.obj[3:5]:
662
            self.client.update_object_metadata(self.container[0], o['name'],
663
                                               **meta)
664
        
665
        obj = self.client.list_objects(self.container[0], meta='Quality')
666
        self.assertEqual(len(obj), 2)
667
        self.assertTrue(obj, [o['name'] for o in self.obj[:2]])
668
        
669
        # test case insensitive
670
        obj = self.client.list_objects(self.container[0], meta='quality')
671
        self.assertEqual(len(obj), 2)
672
        self.assertTrue(obj, [o['name'] for o in self.obj[:2]])
673
        
674
        # test multiple matches
675
        obj = self.client.list_objects(self.container[0], meta='Quality,Stock')
676
        self.assertEqual(len(obj), 4)
677
        self.assertTrue(obj, [o['name'] for o in self.obj[:4]])
678
        
679
        # test non 1-1 multiple match
680
        obj = self.client.list_objects(self.container[0], meta='Quality,aaaa')
681
        self.assertEqual(len(obj), 2)
682
        self.assertTrue(obj, [o['name'] for o in self.obj[:2]])
683
    
684
    def test_if_modified_since(self):
685
        t = datetime.datetime.utcnow()
686
        t2 = t - datetime.timedelta(minutes=10)
687
        
688
        #add a new object
689
        self.upload_random_data(self.container[0], o_names[0])
690
        
691
        for f in DATE_FORMATS:
692
            past = t2.strftime(f)
693
            try:
694
                o = self.client.list_objects(self.container[0],
695
                                            if_modified_since=past)
696
                self.assertEqual(o,
697
                                 self.client.list_objects(self.container[0]))
698
            except Fault, f:
699
                self.failIf(f.status == 304) #fail if not modified
700
    
701
    def test_if_modified_since_invalid_date(self):
702
        headers = {'if-modified-since':''}
703
        o = self.client.list_objects(self.container[0], if_modified_since='')
704
        self.assertEqual(o, self.client.list_objects(self.container[0]))
705
    
706
    def test_if_not_modified_since(self):
707
        now = datetime.datetime.utcnow()
708
        since = now + datetime.timedelta(1)
709
        
710
        for f in DATE_FORMATS:
711
            args = {'if_modified_since':'%s' %since.strftime(f)}
712
            
713
            #assert not modified
714
            self.assert_raises_fault(304, self.client.list_objects,
715
                                     self.container[0], **args)
716
    
717
    def test_if_unmodified_since(self):
718
        now = datetime.datetime.utcnow()
719
        since = now + datetime.timedelta(1)
720
        
721
        for f in DATE_FORMATS:
722
            obj = self.client.list_objects(self.container[0],
723
                                           if_unmodified_since=since.strftime(f))
724
            
725
            #assert unmodified
726
            self.assertEqual(obj, self.client.list_objects(self.container[0]))
727
    
728
    def test_if_unmodified_since_precondition_failed(self):
729
        t = datetime.datetime.utcnow()
730
        t2 = t - datetime.timedelta(minutes=10)
731
        
732
        #add a new container
733
        self.client.create_container('dummy')
734
        
735
        for f in DATE_FORMATS:
736
            past = t2.strftime(f)
737
            
738
            args = {'if_unmodified_since':'%s' %past}
739
            
740
            #assert precondition failed
741
            self.assert_raises_fault(412, self.client.list_objects,
742
                                     self.container[0], **args)
743

    
744
class ContainerPut(BaseTestCase):
745
    def setUp(self):
746
        BaseTestCase.setUp(self)
747
        self.account = 'test'
748
        self.containers = ['c1', 'c2']
749
    
750
    def test_create(self):
751
        self.client.create_container(self.containers[0])
752
        containers = self.client.list_containers()
753
        self.assertTrue(self.containers[0] in containers)
754
        self.assert_container_exists(self.containers[0])
755
    
756
    def test_create_twice(self):
757
        self.client.create_container(self.containers[0])
758
        self.assertTrue(not self.client.create_container(self.containers[0]))
759
    
760
class ContainerPost(BaseTestCase):
761
    def setUp(self):
762
        BaseTestCase.setUp(self)
763
        self.account = 'test'
764
        self.container = 'apples'
765
        self.client.create_container(self.container)
766
    
767
    def test_update_meta(self):
768
        meta = {'test':'test33',
769
                'tost':'tost22'}
770
        self.client.update_container_metadata(self.container, **meta)
771
        headers = self.client.retrieve_container_metadata(self.container)
772
        for k,v in meta.items():
773
            k = 'x-container-meta-%s' % k
774
            self.assertTrue(headers[k])
775
            self.assertEqual(headers[k], v)
776

    
777
class ContainerDelete(BaseTestCase):
778
    def setUp(self):
779
        BaseTestCase.setUp(self)
780
        self.account = 'test'
781
        self.containers = ['c1', 'c2']
782
        for c in self.containers:
783
            self.client.create_container(c)
784
        self.upload_random_data(self.containers[1], o_names[0])
785
    
786
    def test_delete(self):
787
        status = self.client.delete_container(self.containers[0])[0]
788
        self.assertEqual(status, 204)
789
    
790
    def test_delete_non_empty(self):
791
        self.assert_raises_fault(409, self.client.delete_container,
792
                                 self.containers[1])
793
    
794
    def test_delete_invalid(self):
795
        self.assert_raises_fault(404, self.client.delete_container, 'c3')
796

    
797
class ObjectHead(BaseTestCase):
798
    pass
799

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

    
1137
class ObjectPut(BaseTestCase):
1138
    def setUp(self):
1139
        BaseTestCase.setUp(self)
1140
        self.account = 'test'
1141
        self.container = 'c1'
1142
        self.client.create_container(self.container)
1143
    
1144
    def test_upload(self):
1145
        name = o_names[0]
1146
        meta = {'test':'test1'}
1147
        o = self.upload_random_data(self.container, name, **meta)
1148
        
1149
        headers = self.client.retrieve_object_metadata(self.container,
1150
                                                       name,
1151
                                                       restricted=True)
1152
        self.assertTrue('test' in headers.keys())
1153
        self.assertEqual(headers['test'], meta['test'])
1154
        
1155
        #assert uploaded content
1156
        status, h, data = self.client.request_object(self.container, name)
1157
        self.assertEqual(len(o['data']), int(h['content-length']))
1158
        self.assertEqual(o['data'], data)
1159
        
1160
        #assert content-type
1161
        self.assertEqual(h['content-type'], o['meta']['content_type'])
1162
    
1163
    def test_upload_with_name_containing_slash(self):
1164
        name = '/%s' % o_names[0]
1165
        meta = {'test':'test1'}
1166
        o = self.upload_random_data(self.container, name, **meta)
1167
        
1168
        self.assertEqual(o['data'],
1169
                         self.client.retrieve_object(self.container, name))
1170
        
1171
        self.assertTrue(name in self.client.list_objects(self.container))
1172
    
1173
    def test_create_directory_marker(self):
1174
        self.client.create_directory_marker(self.container, 'foo')
1175
        meta = self.client.retrieve_object_metadata(self.container, 'foo')
1176
        self.assertEqual(meta['content-length'], '0')
1177
        self.assertEqual(meta['content-type'], 'application/directory')
1178

    
1179
    def test_upload_unprocessable_entity(self):
1180
        meta={'etag':'123', 'test':'test1'}
1181
        
1182
        #assert unprocessable entity
1183
        self.assert_raises_fault(422, self.upload_random_data, self.container,
1184
                                 o_names[0], **meta)
1185
    
1186
    def test_chunked_transfer(self):
1187
        data = get_random_data()
1188
        objname = 'object'
1189
        self.client.create_object_using_chunks(self.container, objname,
1190
                                               StringIO(data))
1191
        
1192
        uploaded_data = self.client.retrieve_object(self.container, objname)
1193
        self.assertEqual(data, uploaded_data)
1194

    
1195
class ObjectCopy(BaseTestCase):
1196
    def setUp(self):
1197
        BaseTestCase.setUp(self)
1198
        self.account = 'test'
1199
        self.containers = ['c1', 'c2']
1200
        for c in self.containers:
1201
            self.client.create_container(c)
1202
        self.obj = self.upload_random_data(self.containers[0], o_names[0])
1203
    
1204
    def test_copy(self):
1205
        with AssertMappingInvariant(self.client.retrieve_object_metadata,
1206
                             self.containers[0], self.obj['name']):
1207
            #perform copy
1208
            meta = {'test':'testcopy'}
1209
            status = self.client.copy_object(self.containers[0],
1210
                                              self.obj['name'],
1211
                                              self.containers[0],
1212
                                              'testcopy',
1213
                                              meta)[0]
1214
            
1215
            #assert copy success
1216
            self.assertEqual(status, 201)
1217
            
1218
            #assert access the new object
1219
            headers = self.client.retrieve_object_metadata(self.containers[0],
1220
                                                           'testcopy')
1221
            self.assertTrue('x-object-meta-test' in headers.keys())
1222
            self.assertTrue(headers['x-object-meta-test'], 'testcopy')
1223
            
1224
            #assert etag is the same
1225
            self.assertEqual(headers['etag'], self.obj['hash'])
1226
            
1227
            #assert src object still exists
1228
            self.assert_object_exists(self.containers[0], self.obj['name'])
1229
    
1230
    def test_copy_from_different_container(self):
1231
        with AssertMappingInvariant(self.client.retrieve_object_metadata,
1232
                             self.containers[0], self.obj['name']):
1233
            meta = {'test':'testcopy'}
1234
            status = self.client.copy_object(self.containers[0],
1235
                                             self.obj['name'],
1236
                                             self.containers[1],
1237
                                             'testcopy',
1238
                                             meta)[0]
1239
            self.assertEqual(status, 201)
1240
            
1241
            # assert updated metadata
1242
            meta = self.client.retrieve_object_metadata(self.containers[1],
1243
                                                           'testcopy',
1244
                                                           restricted=True)
1245
            self.assertTrue('test' in meta.keys())
1246
            self.assertTrue(meta['test'], 'testcopy')
1247
            
1248
            #assert src object still exists
1249
            self.assert_object_exists(self.containers[0], self.obj['name'])
1250
    
1251
    def test_copy_invalid(self):
1252
        #copy from invalid object
1253
        meta = {'test':'testcopy'}
1254
        self.assert_raises_fault(404, self.client.copy_object, self.containers[0],
1255
                                 'test.py', self.containers[1], 'testcopy', meta)
1256
        
1257
        #copy from invalid container
1258
        meta = {'test':'testcopy'}
1259
        self.assert_raises_fault(404, self.client.copy_object, self.containers[1],
1260
                                 self.obj['name'], self.containers[1],
1261
                                 'testcopy', meta)
1262
        
1263

    
1264
class ObjectMove(BaseTestCase):
1265
    def setUp(self):
1266
        BaseTestCase.setUp(self)
1267
        self.account = 'test'
1268
        self.containers = ['c1', 'c2']
1269
        for c in self.containers:
1270
            self.client.create_container(c)
1271
        self.obj = self.upload_random_data(self.containers[0], o_names[0])
1272
    
1273
    def test_move(self):
1274
        #perform move
1275
        meta = {'test':'testcopy'}
1276
        src_path = '/'.join(('/', self.containers[0], self.obj['name']))
1277
        status = self.client.move_object(self.containers[0], self.obj['name'],
1278
                                         self.containers[0], 'testcopy',
1279
                                         meta)[0]
1280
        
1281
        #assert successful move
1282
        self.assertEqual(status, 201)
1283
        
1284
        #assert updated metadata
1285
        meta = self.client.retrieve_object_metadata(self.containers[0],
1286
                                                    'testcopy',
1287
                                                    restricted=True)
1288
        self.assertTrue('test' in meta.keys())
1289
        self.assertTrue(meta['test'], 'testcopy')
1290
        
1291
        #assert src object no more exists
1292
        self.assert_object_not_exists(self.containers[0], self.obj['name'])
1293

    
1294
class ObjectPost(BaseTestCase):
1295
    def setUp(self):
1296
        BaseTestCase.setUp(self)
1297
        self.account = 'test'
1298
        self.containers = ['c1', 'c2']
1299
        for c in self.containers:
1300
            self.client.create_container(c)
1301
        self.obj = self.upload_random_data(self.containers[0], o_names[0])
1302
    
1303
    def test_update_meta(self):
1304
        #perform update metadata
1305
        more = {'foo':'foo', 'bar':'bar'}
1306
        status = self.client.update_object_metadata(self.containers[0],
1307
                                                    self.obj['name'],
1308
                                                    **more)[0]
1309
        #assert request accepted
1310
        self.assertEqual(status, 202)
1311
        
1312
        #assert old metadata are still there
1313
        headers = self.client.retrieve_object_metadata(self.containers[0],
1314
                                                       self.obj['name'],
1315
                                                       restricted=True)
1316
        #assert new metadata have been updated
1317
        for k,v in more.items():
1318
            self.assertTrue(k in headers.keys())
1319
            self.assertTrue(headers[k], v)
1320
    
1321
    def test_update_object(self,
1322
                           first_byte_pos=0,
1323
                           last_byte_pos=499,
1324
                           instance_length = True,
1325
                           content_length = 500):
1326
        l = len(self.obj['data'])
1327
        length = l if instance_length else '*'
1328
        range = 'bytes %d-%d/%s' %(first_byte_pos,
1329
                                       last_byte_pos,
1330
                                       length)
1331
        partial = last_byte_pos - first_byte_pos + 1
1332
        data = get_random_data(partial)
1333
        args = {'content_type':'application/octet-stream',
1334
                'content_range':'%s' %range}
1335
        if content_length:
1336
            args['content_length'] = content_length
1337
        
1338
        status = self.client.update_object(self.containers[0], self.obj['name'],
1339
                                  StringIO(data), **args)[0]
1340
        
1341
        if partial < 0 or (instance_length and l <= last_byte_pos):
1342
            self.assertEqual(status, 202)    
1343
        else:
1344
            self.assertEqual(status, 204)           
1345
            #check modified object
1346
            content = self.client.retrieve_object(self.containers[0],
1347
                                              self.obj['name'])
1348
            self.assertEqual(content[0:partial], data)
1349
            self.assertEqual(content[partial:l], self.obj['data'][partial:l])
1350
    
1351
    def test_update_object_no_content_length(self):
1352
        self.test_update_object(content_length = None)
1353
    
1354
    def test_update_object_invalid_content_length(self):
1355
        with AssertContentInvariant(self.client.retrieve_object,
1356
                                    self.containers[0], self.obj['name']):
1357
            self.assert_raises_fault(400, self.test_update_object,
1358
                                     content_length = 1000)
1359
    
1360
    def test_update_object_invalid_range(self):
1361
        with AssertContentInvariant(self.client.retrieve_object,
1362
                                    self.containers[0], self.obj['name']):
1363
            self.assert_raises_fault(416, self.test_update_object, 499, 0, True)
1364
    
1365
    def test_update_object_invalid_range_and_length(self):
1366
        with AssertContentInvariant(self.client.retrieve_object,
1367
                                    self.containers[0], self.obj['name']):
1368
            self.assert_raises_fault(416, self.test_update_object, 499, 0, True,
1369
                                     -1)
1370
    
1371
    def test_update_object_invalid_range_with_no_content_length(self):
1372
        with AssertContentInvariant(self.client.retrieve_object,
1373
                                    self.containers[0], self.obj['name']):
1374
            self.assert_raises_fault(416, self.test_update_object, 499, 0, True,
1375
                                     content_length = None)
1376
    
1377
    def test_update_object_out_of_limits(self):    
1378
        with AssertContentInvariant(self.client.retrieve_object,
1379
                                    self.containers[0], self.obj['name']):
1380
            l = len(self.obj['data'])
1381
            self.assert_raises_fault(416, self.test_update_object, 0, l+1, True)
1382
    
1383
    def test_append(self):
1384
        data = get_random_data(500)
1385
        headers = {}
1386
        self.client.update_object(self.containers[0], self.obj['name'],
1387
                                  StringIO(data), content_length=500,
1388
                                  content_type='application/octet-stream')
1389
        
1390
        content = self.client.retrieve_object(self.containers[0],
1391
                                              self.obj['name'])
1392
        self.assertEqual(len(content), len(self.obj['data']) + 500)
1393
        self.assertEqual(content[:-500], self.obj['data'])
1394
    
1395
    def test_update_with_chunked_transfer(self):
1396
        data = get_random_data(500)
1397
        dl = len(data)
1398
        fl = len(self.obj['data'])
1399
        
1400
        self.client.update_object_using_chunks(self.containers[0],
1401
                                               self.obj['name'], StringIO(data),
1402
                                               offset=0,
1403
                                               content_type='application/octet-stream')
1404
        
1405
        #check modified object
1406
        content = self.client.retrieve_object(self.containers[0],
1407
                                              self.obj['name'])
1408
        self.assertEqual(content[0:dl], data)
1409
        self.assertEqual(content[dl:fl], self.obj['data'][dl:fl])
1410

    
1411
class ObjectDelete(BaseTestCase):
1412
    def setUp(self):
1413
        BaseTestCase.setUp(self)
1414
        self.account = 'test'
1415
        self.containers = ['c1', 'c2']
1416
        for c in self.containers:
1417
            self.client.create_container(c)
1418
        self.obj = self.upload_random_data(self.containers[0], o_names[0])
1419
    
1420
    def test_delete(self):
1421
        #perform delete object
1422
        self.client.delete_object(self.containers[0], self.obj['name'])[0]
1423
    
1424
    def test_delete_invalid(self):
1425
        #assert item not found
1426
        self.assert_raises_fault(404, self.client.delete_object, self.containers[1],
1427
                                 self.obj['name'])
1428

    
1429
class AssertMappingInvariant(object):
1430
    def __init__(self, callable, *args, **kwargs):
1431
        self.callable = callable
1432
        self.args = args
1433
        self.kwargs = kwargs
1434
    
1435
    def __enter__(self):
1436
        self.map = self.callable(*self.args, **self.kwargs)
1437
        return self.map
1438
    
1439
    def __exit__(self, type, value, tb):
1440
        map = self.callable(*self.args, **self.kwargs)
1441
        for k in self.map.keys():
1442
            if is_date(self.map[k]):
1443
                continue
1444
            assert map[k] == self.map[k]
1445

    
1446
class AssertContentInvariant(object):
1447
    def __init__(self, callable, *args, **kwargs):
1448
        self.callable = callable
1449
        self.args = args
1450
        self.kwargs = kwargs
1451
    
1452
    def __enter__(self):
1453
        self.content = self.callable(*self.args, **self.kwargs)[2]
1454
        return self.content
1455
    
1456
    def __exit__(self, type, value, tb):
1457
        content = self.callable(*self.args, **self.kwargs)[2]
1458
        assert self.content == content
1459

    
1460
def get_content_splitted(response):
1461
    if response:
1462
        return response.content.split('\n')
1463

    
1464
def compute_md5_hash(data):
1465
    md5 = hashlib.md5()
1466
    offset = 0
1467
    md5.update(data)
1468
    return md5.hexdigest().lower()
1469

    
1470
def compute_block_hash(data, algorithm):
1471
    h = hashlib.new(algorithm)
1472
    h.update(data.rstrip('\x00'))
1473
    return h.hexdigest()
1474

    
1475
def get_random_data(length=500):
1476
    char_set = string.ascii_uppercase + string.digits
1477
    return ''.join(random.choice(char_set) for x in range(length))
1478

    
1479
def is_date(date):
1480
    MONTHS = 'jan feb mar apr may jun jul aug sep oct nov dec'.split()
1481
    __D = r'(?P<day>\d{2})'
1482
    __D2 = r'(?P<day>[ \d]\d)'
1483
    __M = r'(?P<mon>\w{3})'
1484
    __Y = r'(?P<year>\d{4})'
1485
    __Y2 = r'(?P<year>\d{2})'
1486
    __T = r'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})'
1487
    RFC1123_DATE = re.compile(r'^\w{3}, %s %s %s %s GMT$' % (__D, __M, __Y, __T))
1488
    RFC850_DATE = re.compile(r'^\w{6,9}, %s-%s-%s %s GMT$' % (__D, __M, __Y2, __T))
1489
    ASCTIME_DATE = re.compile(r'^\w{3} %s %s %s %s$' % (__M, __D2, __T, __Y))
1490
    for regex in RFC1123_DATE, RFC850_DATE, ASCTIME_DATE:
1491
        m = regex.match(date)
1492
        if m is not None:
1493
            return True
1494
    return False
1495

    
1496
o_names = ['kate.jpg',
1497
           'kate_beckinsale.jpg',
1498
           'How To Win Friends And Influence People.pdf',
1499
           'moms_birthday.jpg',
1500
           'poodle_strut.mov',
1501
           'Disturbed - Down With The Sickness.mp3',
1502
           'army_of_darkness.avi',
1503
           'the_mad.avi',
1504
           'photos/animals/dogs/poodle.jpg',
1505
           'photos/animals/dogs/terrier.jpg',
1506
           'photos/animals/cats/persian.jpg',
1507
           'photos/animals/cats/siamese.jpg',
1508
           'photos/plants/fern.jpg',
1509
           'photos/plants/rose.jpg',
1510
           'photos/me.jpg']
1511

    
1512
if __name__ == "__main__":
1513
    unittest.main()