Statistics
| Branch: | Tag: | Revision:

root / pithos / api / tests.py @ 95e92490

History | View | Annotate | Download (74 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.failIfEqual(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
            self.assertEqual(groups['pithosdev'].split(','),
562
                             self.client.retrieve_account_groups()['pithosdev'].split(','))
563
    
564
    def test_delete_account_groups(self):
565
        with AssertMappingInvariant(self.client.retrieve_account_metadata):
566
            groups = {'pithosdev':'verigak,gtsouk,chazapis',
567
                      'clientsdev':'pkanavos,mvasilak'}
568
            self.client.set_account_groups(**groups)
569
            
570
            self.client.unset_account_groups(groups.keys())
571
            
572
            self.assertEqual({}, self.client.retrieve_account_groups())
573
    
574
class ContainerHead(BaseTestCase):
575
    def setUp(self):
576
        BaseTestCase.setUp(self)
577
        self.account = 'test'
578
        self.container = 'apples'
579
        self.client.create_container(self.container)
580
    
581
    def test_get_meta(self):
582
        meta = {'trash':'true'}
583
        t1 = datetime.datetime.utcnow()
584
        o = self.upload_random_data(self.container, o_names[0], **meta)
585
        if o:
586
            headers = self.client.retrieve_container_metadata(self.container)
587
            self.assertEqual(headers['x-container-object-count'], '1')
588
            self.assertEqual(headers['x-container-bytes-used'], str(len(o['data'])))
589
            t2 = datetime.datetime.strptime(headers['last-modified'], DATE_FORMATS[2])
590
            delta = (t2 - t1)
591
            threashold = datetime.timedelta(seconds=1) 
592
            self.assertTrue(delta < threashold)
593
            self.assertTrue(headers['x-container-object-meta'])
594
            self.assertTrue('Trash' in headers['x-container-object-meta'])
595

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

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

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

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

    
859
class ObjectHead(BaseTestCase):
860
    pass
861

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

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

    
1249
    def test_upload_unprocessable_entity(self):
1250
        meta={'etag':'123', 'test':'test1'}
1251
        
1252
        #assert unprocessable entity
1253
        self.assert_raises_fault(422, self.upload_random_data, self.container,
1254
                                 o_names[0], **meta)
1255
    
1256
    def test_chunked_transfer(self):
1257
        data = get_random_data()
1258
        objname = 'object'
1259
        self.client.create_object_using_chunks(self.container, objname,
1260
                                               StringIO(data))
1261
        
1262
        uploaded_data = self.client.retrieve_object(self.container, objname)
1263
        self.assertEqual(data, uploaded_data)
1264
    
1265
    def test_manifestation(self):
1266
        prefix = 'myobject/'
1267
        data = ''
1268
        for i in range(5):
1269
            part = '%s%d' %(prefix, i)
1270
            o = self.upload_random_data(self.container, part)
1271
            data += o['data']
1272
        
1273
        manifest = '%s/%s' %(self.container, prefix)
1274
        self.client.create_manifestation(self.container, 'large-object', manifest)
1275
        
1276
        self.assert_object_exists(self.container, 'large-object')
1277
        self.assertEqual(data, self.client.retrieve_object(self.container,
1278
                                                           'large-object'))
1279
        
1280
        #wrong manifestation
1281
        self.client.create_manifestation(self.container, 'large-object',
1282
                                         '%s/invalid' % self.container)
1283
        self.assertEqual('', self.client.retrieve_object(self.container,
1284
                                                         'large-object'))
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
        #check read access
1685
        self.client.create_container('φάκελος')
1686
        o = self.upload_random_data('φάκελος', 'ο1')
1687
        self.client.share_object('φάκελος', 'ο1', ['test:σεφς'])
1688
        chef = Pithos_Client(DEFAULT_HOST,
1689
                            '0009',
1690
                            'διογένης',
1691
                            DEFAULT_API)
1692
        self.assert_not_raises_fault(401, chef.retrieve_object_metadata,
1693
                                     'φάκελος', 'ο1', account=DEFAULT_USER)
1694
        
1695
        #check write access
1696
        self.client.share_object('φάκελος', 'ο1', ['διογένης'], read=False)
1697
        new_data = get_random_data()
1698
        self.assert_not_raises_fault(401, chef.update_object,
1699
                                     'φάκελος', 'ο1', StringIO(new_data),
1700
                                     account=DEFAULT_USER)
1701
        
1702
        server_data = self.client.retrieve_object('φάκελος', 'ο1')
1703
        self.assertEqual(server_data[:len(o['data'])], o['data'])
1704
        self.assertEqual(server_data[len(o['data']):], new_data)
1705
    
1706
    def test_manifestation(self):
1707
        self.client.create_container('κουβάς')
1708
        prefix = 'μέρη/'
1709
        data = ''
1710
        for i in range(5):
1711
            part = '%s%d' %(prefix, i)
1712
            o = self.upload_random_data('κουβάς', part)
1713
            data += o['data']
1714
        
1715
        self.client.create_container('φάκελος')
1716
        manifest = '%s/%s' %('κουβάς', prefix)
1717
        self.client.create_manifestation('φάκελος', 'άπαντα', manifest)
1718
        
1719
        self.assert_object_exists('φάκελος', 'άπαντα')
1720
        self.assertEqual(data, self.client.retrieve_object('φάκελος',
1721
                                                           'άπαντα'))
1722
        
1723
        #wrong manifestation
1724
        self.client.create_manifestation('φάκελος', 'άπαντα', 'κουβάς/άκυρο')
1725
        self.assertEqual('', self.client.retrieve_object('φάκελος', 'άπαντα'))
1726
    
1727
        pass
1728

    
1729
class TestPermissions(BaseTestCase):
1730
    def setUp(self):
1731
        BaseTestCase.setUp(self)
1732
        #keep track of initial account groups
1733
        self.initial_groups = self.client.retrieve_account_groups()
1734
        #keep track of initial account meta
1735
        self.initial_meta = self.client.retrieve_account_metadata(restricted=True)
1736
        
1737
        #create a group
1738
        self.authorized = ['chazapis', 'verigak', 'gtsouk', 'papagian']
1739
        groups = {'pithosdev':','.join(self.authorized)}
1740
        self.client.set_account_groups(**groups)
1741
    
1742
    def tearDown(self):
1743
        #delete additionally created meta
1744
        l = []
1745
        for m in self.client.retrieve_account_metadata(restricted=True):
1746
            if m not in self.initial_meta:
1747
                l.append(m)
1748
        self.client.delete_account_metadata(l)
1749
        
1750
        #delete additionally created groups
1751
        l = []
1752
        for g in self.client.retrieve_account_groups():
1753
            if g not in self.initial_groups:
1754
                l.append(g)
1755
        self.client.unset_account_groups(l)
1756
        
1757
        BaseTestCase.tearDown(self)
1758
    
1759
    def test_read_access(self):
1760
        self.client.create_container('c')
1761
        o = self.upload_random_data('c', 'o')
1762
        self.client.share_object('c', 'o', ['test:pithosdev'])
1763
        for token, account in OTHER_ACCOUNTS.items():
1764
            cl = Pithos_Client(DEFAULT_HOST, token, account, DEFAULT_API) 
1765
            if account in self.authorized:
1766
                self.assert_not_raises_fault(401, cl.retrieve_object_metadata,
1767
                                             'c', 'o', account=DEFAULT_USER)
1768
            else:
1769
                self.assert_raises_fault(401, cl.retrieve_object_metadata,
1770
                                         'c', 'o', account=DEFAULT_USER)
1771
        
1772
    
1773
class AssertMappingInvariant(object):
1774
    def __init__(self, callable, *args, **kwargs):
1775
        self.callable = callable
1776
        self.args = args
1777
        self.kwargs = kwargs
1778
    
1779
    def __enter__(self):
1780
        self.map = self.callable(*self.args, **self.kwargs)
1781
        return self.map
1782
    
1783
    def __exit__(self, type, value, tb):
1784
        map = self.callable(*self.args, **self.kwargs)
1785
        for k in self.map.keys():
1786
            if is_date(self.map[k]):
1787
                continue
1788
            assert map[k] == self.map[k]
1789

    
1790
class AssertContentInvariant(object):
1791
    def __init__(self, callable, *args, **kwargs):
1792
        self.callable = callable
1793
        self.args = args
1794
        self.kwargs = kwargs
1795
    
1796
    def __enter__(self):
1797
        self.content = self.callable(*self.args, **self.kwargs)[2]
1798
        return self.content
1799
    
1800
    def __exit__(self, type, value, tb):
1801
        content = self.callable(*self.args, **self.kwargs)[2]
1802
        assert self.content == content
1803

    
1804
def get_content_splitted(response):
1805
    if response:
1806
        return response.content.split('\n')
1807

    
1808
def compute_md5_hash(data):
1809
    md5 = hashlib.md5()
1810
    offset = 0
1811
    md5.update(data)
1812
    return md5.hexdigest().lower()
1813

    
1814
def compute_block_hash(data, algorithm):
1815
    h = hashlib.new(algorithm)
1816
    h.update(data.rstrip('\x00'))
1817
    return h.hexdigest()
1818

    
1819
def get_random_data(length=500):
1820
    char_set = string.ascii_uppercase + string.digits
1821
    return ''.join(random.choice(char_set) for x in range(length))
1822

    
1823
def is_date(date):
1824
    MONTHS = 'jan feb mar apr may jun jul aug sep oct nov dec'.split()
1825
    __D = r'(?P<day>\d{2})'
1826
    __D2 = r'(?P<day>[ \d]\d)'
1827
    __M = r'(?P<mon>\w{3})'
1828
    __Y = r'(?P<year>\d{4})'
1829
    __Y2 = r'(?P<year>\d{2})'
1830
    __T = r'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})'
1831
    RFC1123_DATE = re.compile(r'^\w{3}, %s %s %s %s GMT$' % (__D, __M, __Y, __T))
1832
    RFC850_DATE = re.compile(r'^\w{6,9}, %s-%s-%s %s GMT$' % (__D, __M, __Y2, __T))
1833
    ASCTIME_DATE = re.compile(r'^\w{3} %s %s %s %s$' % (__M, __D2, __T, __Y))
1834
    for regex in RFC1123_DATE, RFC850_DATE, ASCTIME_DATE:
1835
        m = regex.match(date)
1836
        if m is not None:
1837
            return True
1838
    return False
1839

    
1840
o_names = ['kate.jpg',
1841
           'kate_beckinsale.jpg',
1842
           'How To Win Friends And Influence People.pdf',
1843
           'moms_birthday.jpg',
1844
           'poodle_strut.mov',
1845
           'Disturbed - Down With The Sickness.mp3',
1846
           'army_of_darkness.avi',
1847
           'the_mad.avi',
1848
           'photos/animals/dogs/poodle.jpg',
1849
           'photos/animals/dogs/terrier.jpg',
1850
           'photos/animals/cats/persian.jpg',
1851
           'photos/animals/cats/siamese.jpg',
1852
           'photos/plants/fern.jpg',
1853
           'photos/plants/rose.jpg',
1854
           'photos/me.jpg']
1855

    
1856
if __name__ == "__main__":
1857
    unittest.main()