Statistics
| Branch: | Tag: | Revision:

root / pithos / api / tests.py @ 1d5c57d3

History | View | Annotate | Download (67.8 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 django.test.client import Client
35
from django.test import TestCase
36
from django.utils import simplejson as json
37
from xml.dom import minidom
38
import types
39
import hashlib
40
import os
41
import mimetypes
42
import random
43
import datetime
44
import string
45
from pithos.backends import backend
46

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

    
51
class AaiClient(Client):
52
    def request(self, **request):
53
        request['HTTP_X_AUTH_TOKEN'] = '0000'
54
        return super(AaiClient, self).request(**request)
55

    
56
class BaseTestCase(TestCase):
57
    #TODO unauthorized request
58
    def setUp(self):
59
        self.client = AaiClient()
60
        self.headers = {
61
            'account':(
62
                'X-Account-Container-Count',
63
                'X-Account-Bytes-Used',
64
                'Last-Modified',
65
                'Content-Length',
66
                'Date',
67
                'Content-Type',),
68
            'container':(
69
                'X-Container-Object-Count',
70
                'X-Container-Bytes-Used',
71
                'Content-Type',
72
                'Last-Modified',
73
                'Content-Length',
74
                'Date',
75
                'X-Container-Block-Size',
76
                'X-Container-Block-Hash',
77
                'X-Container-Policy-Quota',
78
                'X-Container-Policy-Versioning',),
79
            'object':(
80
                'ETag',
81
                'Content-Length',
82
                'Content-Type',
83
                'Content-Encoding',
84
                'Last-Modified',
85
                'Date',
86
                'X-Object-Manifest',
87
                'Content-Range',
88
                'X-Object-Modified-By',
89
                'X-Object-Version',
90
                'X-Object-Version-Timestamp',)}
91
        self.contentTypes = {'xml':'application/xml',
92
                             'json':'application/json',
93
                             '':'text/plain'}
94
        self.extended = {
95
            'container':(
96
                'name',
97
                'count',
98
                'bytes',
99
                'last_modified'),
100
            'object':(
101
                'name',
102
                'hash',
103
                'bytes',
104
                'content_type',
105
                'content_encoding',
106
                'last_modified',)}
107
        self.return_codes = (400, 401, 404, 503,)
108

    
109
    def assertFault(self, response, status_code, name):
110
        self.assertEqual(response.status_code, status_code)
111

    
112
    def assertBadRequest(self, response):
113
        self.assertFault(response, 400, 'badRequest')
114

    
115
    def assertItemNotFound(self, response):
116
        self.assertFault(response, 404, 'itemNotFound')
117

    
118
    def assertUnauthorized(self, response):
119
        self.assertFault(response, 401, 'unauthorized')
120

    
121
    def assertServiceUnavailable(self, response):
122
        self.assertFault(response, 503, 'serviceUnavailable')
123

    
124
    def assertNonEmpty(self, response):
125
        self.assertFault(response, 409, 'nonEmpty')
126

    
127
    def assert_status(self, response, codes):
128
        l = [elem for elem in self.return_codes]
129
        if type(codes) == types.ListType:
130
            l.extend(codes)
131
        else:
132
            l.append(codes)
133
        self.assertTrue(response.status_code in l)
134

    
135
    def get_account_meta(self, account, exp_meta={}):
136
        path = '/v1/%s' % account
137
        response = self.client.head(path)
138
        self.assert_status(response, 204)
139
        self.assert_headers(response, 'account', exp_meta)
140
        return response
141

    
142
    def list_containers(self, account, limit=10000, marker='', format='',
143
                        **headers):
144
        params = locals()
145
        params.pop('self')
146
        params.pop('account')
147
        path = '/v1/%s' % account
148
        response = self.client.get(path, params, **headers)
149
        self.assert_status(response, [200, 204, 304, 412])
150
        response.content = response.content.strip()
151
        if format:
152
            self.assert_extended(response, format, 'container', limit)
153
        else:
154
            names = get_content_splitted(response)
155
            self.assertTrue(len(names) <= limit)
156
        return response
157

    
158
    def update_account_meta(self, account, **metadata):
159
        path = '/v1/%s' % account
160
        response = self.client.post(path, **metadata)
161
        response.content = response.content
162
        self.assert_status(response, 202)
163
        return response
164

    
165
    def get_container_meta(self, account, container, exp_meta={}):
166
        params = locals()
167
        params.pop('self')
168
        params.pop('account')
169
        params.pop('container')
170
        path = '/v1/%s/%s' %(account, container)
171
        response = self.client.head(path, params)
172
        response.content = response.content
173
        self.assert_status(response, 204)
174
        if response.status_code == 204:
175
            self.assert_headers(response, 'container', exp_meta)
176
        return response
177

    
178
    def list_objects(self, account, container, limit=10000, marker='',
179
                     prefix='', format='', path='', delimiter='', meta='',
180
                     **headers):
181
        params = locals()
182
        params.pop('self')
183
        params.pop('account')
184
        params.pop('container')
185
        path = '/v1/%s/%s' % (account, container)
186
        response = self.client.get(path, params, **headers)
187
        response.content = response.content.strip()
188
        if format:
189
            self.assert_extended(response, format, 'object', limit)
190
        self.assert_status(response, [200, 204, 304, 412])
191
        return response
192

    
193
    def create_container(self, account, name, **meta):
194
        path = '/v1/%s/%s' %(account, name)
195
        response = self.client.put(path, **meta)
196
        response.content = response.content
197
        self.assert_status(response, [201, 202])
198
        return response
199

    
200
    def update_container_meta(self, account, name, **meta):
201
        path = '/v1/%s/%s' %(account, name)
202
        response = self.client.post(path,
203
                                    data=None,
204
                                    content_type='text/xml',
205
                                    follow=False, **meta)
206
        response.content = response.content
207
        self.assert_status(response, 202)
208
        return response
209

    
210
    def delete_container(self, account, container):
211
        path = '/v1/%s/%s' %(account, container)
212
        response = self.client.delete(path)
213
        response.content = response.content
214
        self.assert_status(response, [204, 409])
215
        return response
216

    
217
    def get_object_meta(self, account, container, name):
218
        path = '/v1/%s/%s/%s' %(account, container, name)
219
        response = self.client.head(path)
220
        response.content = response.content
221
        self.assert_status(response, 200)
222
        return response
223

    
224
    def get_object(self, account, container, name, format='', **headers):
225
        path = '/v1/%s/%s/%s' %(account, container, name)
226
        response = self.client.get(path, {'format':format}, **headers)
227
        response.content = response.content
228
        self.assert_status(response, [200, 206, 304, 412, 416])
229
        if response.status_code in [200, 206]:
230
            self.assert_headers(response, 'object')
231
        return response
232

    
233
    def upload_object(self, account, container, name, data, content_type='',
234
                      **headers):
235
        path = '/v1/%s/%s/%s' %(account, container, name)
236
        response = self.client.put(path, data, content_type, **headers)
237
        response.content = response.content
238
        self.assert_status(response, [201, 411, 422])
239
        if response.status_code == 201:
240
            self.assertTrue(response['Etag'])
241
        return response
242

    
243
    def copy_object(self, account, container, name, src, **headers):
244
        path = '/v1/%s/%s/%s' %(account, container, name)
245
        headers['HTTP_X_COPY_FROM'] = src
246
        response = self.client.put(path, **headers)
247
        response.content = response.content
248
        self.assert_status(response, 201)
249
        return response
250

    
251
    def move_object(self, account, container, name, src, **headers):
252
        path = '/v1/%s/%s/%s' % (account, container, name)
253
        headers['HTTP_X_MOVE_FROM'] = src
254
        response = self.client.put(path, **headers)
255
        response.content = response.content
256
        self.assert_status(response, 201)
257
        return response
258

    
259
    def update_object(self, account, container, name, data='',
260
                      content_type='', **headers):
261
        path = '/v1/%s/%s/%s' %(account, container, name)
262
        response = self.client.post(path, data, content_type, **headers)
263
        response.content = response.content
264
        self.assert_status(response, [202, 204, 416])
265
        return response
266

    
267
    def delete_object(self, account, container, name):
268
        path = '/v1/%s/%s/%s' %(account, container, name)
269
        response = self.client.delete(path)
270
        response.content = response.content
271
        self.assert_status(response, 204)
272
        return response
273

    
274
    def assert_headers(self, response, type, exp_meta={}):
275
        entities = ['Account', 'Container', 'Container-Object', 'Object']
276
        user_defined_meta = ['X-%s-Meta' %elem for elem in entities]
277
        headers = [item for item in response._headers.values()]
278
        t = tuple(user_defined_meta)
279
        system_headers = [h for h in headers if not h[0].startswith(t)]
280
        for h in system_headers:
281
            self.assertTrue(h[0] in self.headers[type])
282
            if exp_meta:
283
                self.assertEqual(h[1], exp_meta[h[0]])
284

    
285
    def assert_extended(self, response, format, type, size):
286
        exp_content_type = self.contentTypes[format]
287
        self.assertEqual(response['Content-Type'].find(exp_content_type), 0)
288
        if format == 'xml':
289
            self.assert_xml(response, type, size)
290
        elif format == 'json':
291
            self.assert_json(response, type, size)
292

    
293
    def assert_json(self, response, type, size):
294
        convert = lambda s: s.lower()
295
        info = [convert(elem) for elem in self.extended[type]]
296
        data = json.loads(response.content)
297
        self.assertTrue(len(data) <= size)
298
        for item in info:
299
            for i in data:
300
                if 'subdir' in i.keys():
301
                    continue
302
                self.assertTrue(item in i.keys())
303

    
304
    def assert_xml(self, response, type, size):
305
        convert = lambda s: s.lower()
306
        info = [convert(elem) for elem in self.extended[type]]
307
        try:
308
            info.remove('content_encoding')
309
        except ValueError:
310
            pass
311
        xml = minidom.parseString(response.content)
312
        for item in info:
313
            nodes = xml.getElementsByTagName(item)
314
            self.assertTrue(nodes)
315
            self.assertTrue(len(nodes) <= size)
316
            
317

    
318
    def upload_os_file(self, account, container, fullpath, meta={}):
319
        try:
320
            f = open(fullpath, 'r')
321
            data = f.read()
322
            name = os.path.split(fullpath)[-1]
323
            return self.upload_data(account, container, name, data)    
324
        except IOError:
325
            return
326

    
327
    def upload_random_data(self, account, container, name, length=1024,
328
                           meta={}):
329
        data = get_random_data(length)
330
        return self.upload_data(account, container, name, data, meta)
331

    
332
    def upload_data(self, account, container, name, data, meta={}):
333
        obj = {}
334
        obj['name'] = name
335
        try:
336
            obj['data'] = data
337
            obj['hash'] = compute_md5_hash(obj['data'])
338
            meta.update({'HTTP_X_OBJECT_META_TEST':'test1',
339
                         'HTTP_ETAG':obj['hash']})
340
            type, enc = mimetypes.guess_type(name)
341
            meta['HTTP_CONTENT_TYPE'] = type and type or 'plain/text'
342
            if enc:
343
                meta['HTTP_CONTENT_ENCODING'] = enc
344
            
345
            obj['meta'] = meta
346
            r = self.upload_object(account,
347
                               container,
348
                               obj['name'],
349
                               obj['data'],
350
                               meta['HTTP_CONTENT_TYPE'],
351
                               **meta)
352
            if r.status_code == 201:
353
                return obj
354
        except IOError:
355
            return
356

    
357
class AccountHead(BaseTestCase):
358
    def setUp(self):
359
        BaseTestCase.setUp(self)
360
        self.account = 'test'
361
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
362
        for item in self.containers:
363
            self.create_container(self.account, item)
364

    
365
    def tearDown(self):
366
        for c in  get_content_splitted(self.list_containers(self.account)):
367
            self.delete_container(self.account, c)
368

    
369
    def test_get_account_meta(self):
370
        response = self.get_account_meta(self.account)
371
        r2 = self.list_containers(self.account)
372
        containers =  get_content_splitted(r2)
373
        l = str(len(containers))
374
        self.assertEqual(response['X-Account-Container-Count'], l)
375
        size = 0
376
        for c in containers:
377
            r = self.get_container_meta(self.account, c)
378
            size = size + int(r['X-Container-Bytes-Used'])
379
        self.assertEqual(response['X-Account-Bytes-Used'], str(size))
380

    
381
    #def test_get_account_401(self):
382
    #    response = self.get_account_meta('non-existing-account')
383
    #    print response
384
    #    self.assertEqual(response.status_code, 401)
385

    
386
class AccountGet(BaseTestCase):
387
    def setUp(self):
388
        BaseTestCase.setUp(self)
389
        self.account = 'test'
390
        #create some containers
391
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
392
        for item in self.containers:
393
            self.create_container(self.account, item)
394

    
395
    def tearDown(self):
396
        for c in get_content_splitted(self.list_containers(self.account)):
397
            response = self.delete_container(self.account, c)
398

    
399
    def test_list(self):
400
        #list containers
401
        response = self.list_containers(self.account)
402
        containers = get_content_splitted(response)
403
        self.assertEquals(self.containers, containers)
404

    
405
    #def test_list_204(self):
406
    #    response = self.list_containers('non-existing-account')
407
    #    self.assertEqual(response.status_code, 204)
408

    
409
    def test_list_with_limit(self):
410
        limit = 2
411
        response = self.list_containers(self.account, limit=limit)
412
        containers = get_content_splitted(response)
413
        self.assertEquals(len(containers), limit)
414
        self.assertEquals(self.containers[:2], containers)
415

    
416
    def test_list_with_marker(self):
417
        l = 2
418
        m = 'bananas'
419
        response = self.list_containers(self.account, limit=l, marker=m)
420
        containers =  get_content_splitted(response)
421
        i = self.containers.index(m) + 1
422
        self.assertEquals(self.containers[i:(i+l)], containers)
423
        
424
        m = 'oranges'
425
        response = self.list_containers(self.account, limit=l, marker=m)
426
        containers = get_content_splitted(response)
427
        i = self.containers.index(m) + 1
428
        self.assertEquals(self.containers[i:(i+l)], containers)
429

    
430
    #def test_extended_list(self):
431
    #    self.list_containers(self.account, limit=3, format='xml')
432
    #    self.list_containers(self.account, limit=3, format='json')
433

    
434
    def test_list_json_with_marker(self):
435
        l = 2
436
        m = 'bananas'
437
        response = self.list_containers(self.account, limit=l, marker=m,
438
                                        format='json')
439
        containers = json.loads(response.content)
440
        self.assertEqual(containers[0]['name'], 'kiwis')
441
        self.assertEqual(containers[1]['name'], 'oranges')
442

    
443
    def test_list_xml_with_marker(self):
444
        l = 2
445
        m = 'oranges'
446
        response = self.list_containers(self.account, limit=l, marker=m,
447
                                        format='xml')
448
        xml = minidom.parseString(response.content)
449
        nodes = xml.getElementsByTagName('name')
450
        self.assertEqual(len(nodes), 1)
451
        self.assertEqual(nodes[0].childNodes[0].data, 'pears')
452

    
453
    def test_if_modified_since(self):
454
        t = datetime.datetime.utcnow()
455
        t2 = t - datetime.timedelta(minutes=10)
456
        
457
        #add a new container
458
        self.create_container(self.account,
459
                              'dummy')
460

    
461
        for f in DATE_FORMATS:
462
            past = t2.strftime(f)
463
            
464
            headers = {'HTTP_IF_MODIFIED_SINCE':'%s' %past}
465
            r = self.list_containers(self.account, **headers)
466
            
467
            #assert get success
468
            self.assertEqual(r.status_code, 200)
469

    
470
    def test_if_modified_since_invalid_date(self):
471
        headers = {'HTTP_IF_MODIFIED_SINCE':''}
472
        r = self.list_containers(self.account, **headers)
473
            
474
        #assert get success
475
        self.assertEqual(r.status_code, 200)
476

    
477
    def test_if_not_modified_since(self):
478
        now = datetime.datetime.utcnow()
479
        since = now + datetime.timedelta(1)
480
        
481
        for f in DATE_FORMATS:
482
            headers = {'HTTP_IF_MODIFIED_SINCE':'%s' %since.strftime(f)}
483
            r = self.list_containers(self.account, **headers)
484
            
485
            #assert not modified
486
            self.assertEqual(r.status_code, 304)
487

    
488
    def test_if_unmodified_since(self):
489
        now = datetime.datetime.utcnow()
490
        since = now + datetime.timedelta(1)
491
        
492
        for f in DATE_FORMATS:
493
            headers = {'HTTP_IF_UNMODIFIED_SINCE':'%s' %since.strftime(f)}
494
            r = self.list_containers(self.account, **headers)
495
            
496
            #assert success
497
            self.assertEqual(r.status_code, 200)
498
            self.assertEqual(self.containers, get_content_splitted(r))
499

    
500
    def test_if_unmodified_since_precondition_failed(self):
501
        t = datetime.datetime.utcnow()
502
        t2 = t - datetime.timedelta(minutes=10)
503
        
504
        #add a new container
505
        self.create_container(self.account,
506
                              'dummy')
507
        
508
        for f in DATE_FORMATS:
509
            past = t2.strftime(f)
510
            
511
            headers = {'HTTP_IF_UNMODIFIED_SINCE':'%s' %past}
512
            r = self.list_containers(self.account, **headers)
513
            
514
            #assert get success
515
            self.assertEqual(r.status_code, 412)
516

    
517
class AccountPost(BaseTestCase):
518
    def setUp(self):
519
        BaseTestCase.setUp(self)
520
        self.account = 'test'
521
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
522
        for item in self.containers:
523
            self.create_container(self.account, item)
524

    
525
    def tearDown(self):
526
        for c in  get_content_splitted(self.list_containers(self.account)):
527
            self.delete_container(self.account, c)
528

    
529
    def test_update_meta(self):
530
        meta = {'HTTP_X_ACCOUNT_META_TEST':'test',
531
                'HTTP_X_ACCOUNT_META_TOST':'tost'}
532
        response = self.update_account_meta(self.account, **meta)
533
        response = self.get_account_meta(self.account)
534
        for k,v in meta.items():
535
            key = '-'.join(elem.capitalize() for elem in k.split('_')[1:])
536
            self.assertTrue(response[key])
537
            self.assertEqual(response[key], v)
538

    
539
    #def test_invalid_account_update_meta(self):
540
    #    with AssertInvariant(self.get_account_meta, self.account):
541
    #        meta = {'HTTP_X_ACCOUNT_META_TEST':'test',
542
    #               'HTTP_X_ACCOUNT_META_TOST':'tost'}
543
    #        response = self.update_account_meta('non-existing-account', **meta)
544

    
545
class ContainerHead(BaseTestCase):
546
    def setUp(self):
547
        BaseTestCase.setUp(self)
548
        self.account = 'test'
549
        self.container = 'apples'
550
        self.create_container(self.account, self.container)
551

    
552
    def tearDown(self):
553
        for o in self.list_objects(self.account, self.container):
554
            self.delete_object(self.account, self.container, o)
555
        self.delete_container(self.account, self.container)
556

    
557
    def test_get_meta(self):
558
        headers = {'HTTP_X_OBJECT_META_TRASH':'true'}
559
        t1 = datetime.datetime.utcnow()
560
        o = self.upload_random_data(self.account,
561
                                self.container,
562
                                'McIntosh.jpg',
563
                                meta=headers)
564
        if o:
565
            r = self.get_container_meta(self.account,
566
                                        self.container)
567
            self.assertEqual(r['X-Container-Object-Count'], '1')
568
            self.assertEqual(r['X-Container-Bytes-Used'], str(len(o['data'])))
569
            t2 = datetime.datetime.strptime(r['Last-Modified'], DATE_FORMATS[2])
570
            delta = (t2 - t1)
571
            threashold = datetime.timedelta(seconds=1) 
572
            self.assertTrue(delta < threashold)
573
            self.assertTrue(r['X-Container-Object-Meta'])
574
            self.assertTrue('Trash' in r['X-Container-Object-Meta'])
575

    
576
class ContainerGet(BaseTestCase):
577
    def setUp(self):
578
        BaseTestCase.setUp(self)
579
        self.account = 'test'
580
        self.container = ['pears', 'apples']
581
        for c in self.container:
582
            self.create_container(self.account, c)
583
        self.obj = []
584
        for o in o_names[:8]:
585
            self.obj.append(self.upload_random_data(self.account,
586
                                                    self.container[0],
587
                                                    o))
588
        for o in o_names[8:]:
589
            self.obj.append(self.upload_random_data(self.account,
590
                                                    self.container[1],
591
                                                    o))
592

    
593
    def tearDown(self):
594
        for c in self.container:
595
            for obj in get_content_splitted(self.list_objects(self.account, c)):
596
                self.delete_object(self.account, c, obj)
597
            self.delete_container(self.account, c)
598

    
599
    def test_list_objects(self):
600
        response = self.list_objects(self.account, self.container[0])
601
        objects = get_content_splitted(response)
602
        l = [elem['name'] for elem in self.obj[:8]]
603
        l.sort()
604
        self.assertEqual(objects, l)
605

    
606
    def test_list_objects_with_limit_marker(self):
607
        response = self.list_objects(self.account, self.container[0], limit=2)
608
        objects = get_content_splitted(response)
609
        l = [elem['name'] for elem in self.obj[:8]]
610
        l.sort()
611
        self.assertEqual(objects, l[:2])
612
        
613
        markers = ['How To Win Friends And Influence People.pdf',
614
                   'moms_birthday.jpg']
615
        limit = 4
616
        for m in markers:
617
            response = self.list_objects(self.account, self.container[0],
618
                                         limit=limit, marker=m)
619
            objects = get_content_splitted(response)
620
            l = [elem['name'] for elem in self.obj[:8]]
621
            l.sort()
622
            start = l.index(m) + 1
623
            end = start + limit
624
            end = len(l) >= end and end or len(l)
625
            self.assertEqual(objects, l[start:end])
626

    
627
    def test_list_pseudo_hierarchical_folders(self):
628
        response = self.list_objects(self.account, self.container[1],
629
                                     prefix='photos', delimiter='/')
630
        objects = get_content_splitted(response)
631
        self.assertEquals(['photos/animals/', 'photos/me.jpg',
632
                           'photos/plants/'], objects)
633
        
634
        response = self.list_objects(self.account, self.container[1],
635
                                     prefix='photos/animals', delimiter='/')
636
        objs = get_content_splitted(response)
637
        l = ['photos/animals/cats/', 'photos/animals/dogs/']
638
        self.assertEquals(l, objs)
639
        
640
        response = self.list_objects(self.account, self.container[1],
641
                                     path='photos')
642
        objects = get_content_splitted(response)
643
        self.assertEquals(['photos/me.jpg'], objects)
644

    
645
    def test_extended_list_json(self):
646
        response = self.list_objects(self.account,
647
                                     self.container[1],
648
                                     format='json', limit=2,
649
                                     prefix='photos/animals',
650
                                     delimiter='/')
651
        objects = json.loads(response.content)
652
        self.assertEqual(objects[0]['subdir'], 'photos/animals/cats/')
653
        self.assertEqual(objects[1]['subdir'], 'photos/animals/dogs/')
654

    
655
    def test_extended_list_xml(self):
656
        response = self.list_objects(self.account, self.container[1],
657
                                     format='xml', limit=4, prefix='photos',
658
                                     delimiter='/')
659
        xml = minidom.parseString(response.content)
660
        dirs = xml.getElementsByTagName('subdir')
661
        self.assertEqual(len(dirs), 2)
662
        self.assertEqual(dirs[0].attributes['name'].value, 'photos/animals/')
663
        self.assertEqual(dirs[1].attributes['name'].value, 'photos/plants/')
664
        
665
        objects = xml.getElementsByTagName('name')
666
        self.assertEqual(len(objects), 1)
667
        self.assertEqual(objects[0].childNodes[0].data, 'photos/me.jpg')
668

    
669
    def test_list_meta_double_matching(self):
670
        meta = {'HTTP_X_OBJECT_META_QUALITY':'aaa',
671
                'HTTP_X_OBJECT_META_STOCK':'true'}
672
        r = self.update_object(self.account,
673
                                    self.container[0],
674
                                    self.obj[0]['name'],
675
                                    **meta)
676
        r = self.list_objects(self.account,
677
                          self.container[0],
678
                          meta='Quality,Stock')
679
        self.assertEqual(r.status_code, 200)
680
        obj = get_content_splitted(r)
681
        self.assertEqual(len(obj), 1)
682
        self.assertTrue(obj, self.obj[0]['name'])
683

    
684
    def test_list_using_meta(self):
685
        meta = {'HTTP_X_OBJECT_META_QUALITY':'aaa'}
686
        for o in self.obj[:2]:
687
            r = self.update_object(self.account,
688
                                    self.container[0],
689
                                    o['name'],
690
                                    **meta)
691
        meta = {'HTTP_X_OBJECT_META_STOCK':'true'}
692
        for o in self.obj[3:5]:
693
            r = self.update_object(self.account,
694
                                    self.container[0],
695
                                    o['name'],
696
                                    **meta)
697
        
698
        r = self.list_objects(self.account,
699
                          self.container[0],
700
                          meta='Quality')
701
        self.assertEqual(r.status_code, 200)
702
        obj = get_content_splitted(r)
703
        self.assertEqual(len(obj), 2)
704
        self.assertTrue(obj, [o['name'] for o in self.obj[:2]])
705
        
706
        # test case insensitive
707
        r = self.list_objects(self.account,
708
                          self.container[0],
709
                          meta='quality')
710
        self.assertEqual(r.status_code, 200)
711
        obj = get_content_splitted(r)
712
        self.assertEqual(len(obj), 2)
713
        self.assertTrue(obj, [o['name'] for o in self.obj[:2]])
714
        
715
        # test multiple matches
716
        r = self.list_objects(self.account,
717
                          self.container[0],
718
                          meta='Quality,Stock')
719
        self.assertEqual(r.status_code, 200)
720
        obj = get_content_splitted(r)
721
        self.assertEqual(len(obj), 4)
722
        self.assertTrue(obj, [o['name'] for o in self.obj[:4]])
723
        
724
        # test non 1-1 multiple match
725
        r = self.list_objects(self.account,
726
                          self.container[0],
727
                          meta='Quality,aaaa')
728
        self.assertEqual(r.status_code, 200)
729
        obj = get_content_splitted(r)
730
        self.assertEqual(len(obj), 2)
731
        self.assertTrue(obj, [o['name'] for o in self.obj[:2]])
732

    
733
    def test_if_modified_since(self):
734
        t = datetime.datetime.utcnow()
735
        t2 = t - datetime.timedelta(minutes=10)
736
        
737
        #add a new container
738
        self.upload_random_data(self.account,
739
                                self.container[0],
740
                                'dummy.txt')
741

    
742
        for f in DATE_FORMATS:
743
            past = t2.strftime(f)
744
            
745
            headers = {'HTTP_IF_MODIFIED_SINCE':'%s' %past}
746
            r = self.list_objects(self.account,
747
                                  self.container[0], **headers)
748
            
749
            #assert get success
750
            self.assertEqual(r.status_code, 200)
751

    
752
    def test_if_modified_since_invalid_date(self):
753
        headers = {'HTTP_IF_MODIFIED_SINCE':''}
754
        r = self.list_objects(self.account,
755
                              self.container[0], **headers)
756
        
757
        #assert get success
758
        self.assertEqual(r.status_code, 200)
759

    
760
    def test_if_not_modified_since(self):
761
        now = datetime.datetime.utcnow()
762
        since = now + datetime.timedelta(1)
763
        
764
        for f in DATE_FORMATS:
765
            headers = {'HTTP_IF_MODIFIED_SINCE':'%s' %since.strftime(f)}
766
            r = self.list_objects(self.account,
767
                              self.container[0], **headers)
768
        
769
            #assert not modified
770
            self.assertEqual(r.status_code, 304)
771

    
772
    def test_if_unmodified_since(self):
773
        now = datetime.datetime.utcnow()
774
        since = now + datetime.timedelta(1)
775
        
776
        for f in DATE_FORMATS:
777
            headers = {'HTTP_IF_UNMODIFIED_SINCE':'%s' %since.strftime(f)}
778
            r = self.list_objects(self.account,
779
                              self.container[0], **headers)
780
        
781
            #assert success
782
            self.assertEqual(r.status_code, 200)
783
            objlist = self.list_objects(self.account, self.container[0])
784
            self.assertEqual(get_content_splitted(r),
785
                             get_content_splitted(objlist))
786

    
787
    def test_if_unmodified_since_precondition_failed(self):
788
        t = datetime.datetime.utcnow()
789
        t2 = t - datetime.timedelta(minutes=10)
790
        
791
        #add a new container
792
        self.create_container(self.account,
793
                              'dummy')
794

    
795
        for f in DATE_FORMATS:
796
            past = t2.strftime(f)
797
            
798
            headers = {'HTTP_IF_UNMODIFIED_SINCE':'%s' %past}
799
            r = self.list_objects(self.account,
800
                              self.container[0], **headers)
801
        
802
            #assert get success
803
            self.assertEqual(r.status_code, 412)
804

    
805
class ContainerPut(BaseTestCase):
806
    def setUp(self):
807
        BaseTestCase.setUp(self)
808
        self.account = 'test'
809
        self.containers = ['c1', 'c2']
810

    
811
    def tearDown(self):
812
        for c in self.containers:
813
            r = self.delete_container(self.account, c)
814

    
815
    def test_create(self):
816
        response = self.create_container(self.account, self.containers[0])
817
        if response.status_code == 201:
818
            response = self.list_containers(self.account)
819
            content = get_content_splitted(response)
820
            self.assertTrue(self.containers[0] in content)
821
            r = self.get_container_meta(self.account, self.containers[0])
822
            self.assertEqual(r.status_code, 204)
823

    
824
    def test_create_twice(self):
825
        response = self.create_container(self.account, self.containers[0])
826
        if response.status_code == 201:
827
            r = self.create_container(self.account, self.containers[0])
828
            self.assertTrue(r.status_code, 202)
829

    
830
class ContainerPost(BaseTestCase):
831
    def setUp(self):
832
        BaseTestCase.setUp(self)
833
        self.account = 'test'
834
        self.container = 'apples'
835
        self.create_container(self.account, self.container)
836

    
837
    def tearDown(self):
838
        for o in self.list_objects(self.account, self.container):
839
            self.delete_object(self.account, self.container, o)
840
        self.delete_container(self.account, self.container)
841

    
842
    def test_update_meta(self):
843
        meta = {'HTTP_X_CONTAINER_META_TEST':'test33',
844
                'HTTP_X_CONTAINER_META_TOST':'tost22'}
845
        response = self.update_container_meta(self.account, self.container,
846
                                              **meta)
847
        response = self.get_container_meta(self.account, self.container)
848
        for k,v in meta.items():
849
            key = '-'.join(elem.capitalize() for elem in k.split('_')[1:])
850
            self.assertTrue(response[key])
851
            self.assertEqual(response[key], v)
852

    
853
class ContainerDelete(BaseTestCase):
854
    def setUp(self):
855
        BaseTestCase.setUp(self)
856
        self.account = 'test'
857
        self.containers = ['c1', 'c2']
858
        for c in self.containers:
859
            self.create_container(self.account, c)
860
        self.upload_random_data(self.account,
861
                                self.containers[1],
862
                                'nice.jpg')
863

    
864
    def tearDown(self):
865
        for c in self.containers:
866
            for o in get_content_splitted(self.list_objects(self.account, c)):
867
                self.delete_object(self.account, c, o)
868
            self.delete_container(self.account, c)
869

    
870
    def test_delete(self):
871
        r = self.delete_container(self.account, self.containers[0])
872
        self.assertEqual(r.status_code, 204)
873

    
874
    def test_delete_non_empty(self):
875
        r = self.delete_container(self.account, self.containers[1])
876
        self.assertNonEmpty(r)
877

    
878
    def test_delete_invalid(self):
879
        self.assertItemNotFound(self.delete_container(self.account, 'c3'))
880

    
881
class ObjectHead(BaseTestCase):
882
    pass
883

    
884
class ObjectGet(BaseTestCase):
885
    def setUp(self):
886
        BaseTestCase.setUp(self)
887
        self.account = 'test'
888
        self.containers = ['c1', 'c2']
889
        #create some containers
890
        for c in self.containers:
891
            self.create_container(self.account, c)
892
        
893
        #upload a file
894
        self.objects = []
895
        self.objects.append(self.upload_os_file(self.account,
896
                            self.containers[1],
897
                            './api/tests.py'))
898
        self.objects.append(self.upload_os_file(self.account,
899
                            self.containers[1],
900
                            'settings.py'))
901

    
902
    def tearDown(self):
903
        for c in self.containers:
904
            for o in get_content_splitted(self.list_objects(self.account, c)):
905
                self.delete_object(self.account, c, o)
906
            self.delete_container(self.account, c)
907

    
908
    def test_get(self):
909
        #perform get
910
        r = self.get_object(self.account,
911
                            self.containers[1],
912
                            self.objects[0]['name'],
913
                            self.objects[0]['meta'])
914
        #assert success
915
        self.assertEqual(r.status_code, 200)
916
        
917
        #assert content-type
918
        self.assertEqual(r['Content-Type'],
919
                         self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
920

    
921
    def test_get_invalid(self):
922
        r = self.get_object(self.account,
923
                            self.containers[0],
924
                            self.objects[0]['name'])
925
        self.assertItemNotFound(r)
926

    
927
    def test_get_partial(self):
928
        #perform get with range
929
        headers = {'HTTP_RANGE':'bytes=0-499'}
930
        r = self.get_object(self.account,
931
                        self.containers[1],
932
                        self.objects[0]['name'],
933
                        **headers)
934
        
935
        #assert successful partial content
936
        self.assertEqual(r.status_code, 206)
937
        
938
        #assert content-type
939
        self.assertEqual(r['Content-Type'],
940
                         self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
941
        
942
        #assert content length
943
        self.assertEqual(int(r['Content-Length']), 500)
944
        
945
        #assert content
946
        self.assertEqual(self.objects[0]['data'][:500], r.content)
947

    
948
    def test_get_final_500(self):
949
        #perform get with range
950
        headers = {'HTTP_RANGE':'bytes=-500'}
951
        r = self.get_object(self.account,
952
                        self.containers[1],
953
                        self.objects[0]['name'],
954
                        **headers)
955
        
956
        #assert successful partial content
957
        self.assertEqual(r.status_code, 206)
958
        
959
        #assert content-type
960
        self.assertEqual(r['Content-Type'],
961
                         self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
962
        
963
        #assert content length
964
        self.assertEqual(int(r['Content-Length']), 500)
965
        
966
        #assert content
967
        self.assertTrue(self.objects[0]['data'][-500:], r.content)
968

    
969
    def test_get_rest(self):
970
        #perform get with range
971
        offset = len(self.objects[0]['data']) - 500
972
        headers = {'HTTP_RANGE':'bytes=%s-' %offset}
973
        r = self.get_object(self.account,
974
                        self.containers[1],
975
                        self.objects[0]['name'],
976
                        **headers)
977
        
978
        #assert successful partial content
979
        self.assertEqual(r.status_code, 206)
980
        
981
        #assert content-type
982
        self.assertEqual(r['Content-Type'],
983
                         self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
984
        
985
        #assert content length
986
        self.assertEqual(int(r['Content-Length']), 500)
987
        
988
        #assert content
989
        self.assertTrue(self.objects[0]['data'][-500:], r.content)
990

    
991
    def test_get_range_not_satisfiable(self):
992
        #perform get with range
993
        offset = len(self.objects[0]['data']) + 1
994
        headers = {'HTTP_RANGE':'bytes=0-%s' %offset}
995
        r = self.get_object(self.account,
996
                        self.containers[1],
997
                        self.objects[0]['name'],
998
                        **headers)
999
        
1000
        #assert Range Not Satisfiable
1001
        self.assertEqual(r.status_code, 416)
1002

    
1003
    def test_multiple_range(self):
1004
        #perform get with multiple range
1005
        ranges = ['0-499', '-500', '1000-']
1006
        headers = {'HTTP_RANGE' : 'bytes=%s' % ','.join(ranges)}
1007
        r = self.get_object(self.account,
1008
                        self.containers[1],
1009
                        self.objects[0]['name'],
1010
                        **headers)
1011
        
1012
        # assert partial content
1013
        self.assertEqual(r.status_code, 206)
1014
        
1015
        # assert Content-Type of the reply will be multipart/byteranges
1016
        self.assertTrue(r['Content-Type'])
1017
        content_type_parts = r['Content-Type'].split()
1018
        self.assertEqual(content_type_parts[0], ('multipart/byteranges;'))
1019
        
1020
        boundary = '--%s' %content_type_parts[1].split('=')[-1:][0]
1021
        cparts = r.content.split(boundary)[1:-1]
1022
        
1023
        # assert content parts are exactly 2
1024
        self.assertEqual(len(cparts), len(ranges))
1025
        
1026
        # for each content part assert headers
1027
        i = 0
1028
        for cpart in cparts:
1029
            content = cpart.split('\r\n')
1030
            headers = content[1:3]
1031
            content_range = headers[0].split(': ')
1032
            self.assertEqual(content_range[0], 'Content-Range')
1033
            
1034
            r = ranges[i].split('-')
1035
            if not r[0] and not r[1]:
1036
                pass
1037
            elif not r[0]:
1038
                start = len(self.objects[0]['data']) - int(r[1])
1039
                end = len(self.objects[0]['data'])
1040
            elif not r[1]:
1041
                start = int(r[0])
1042
                end = len(self.objects[0]['data'])
1043
            else:
1044
                start = int(r[0])
1045
                end = int(r[1]) + 1
1046
            fdata = self.objects[0]['data'][start:end]
1047
            sdata = '\r\n'.join(content[4:-1])
1048
            self.assertEqual(len(fdata), len(sdata))
1049
            self.assertEquals(fdata, sdata)
1050
            i+=1
1051

    
1052
    def test_multiple_range_not_satisfiable(self):
1053
        #perform get with multiple range
1054
        out_of_range = len(self.objects[0]['data']) + 1
1055
        ranges = ['0-499', '-500', '%d-' %out_of_range]
1056
        headers = {'HTTP_RANGE' : 'bytes=%s' % ','.join(ranges)}
1057
        r = self.get_object(self.account,
1058
                        self.containers[1],
1059
                        self.objects[0]['name'],
1060
                        **headers)
1061
        
1062
        # assert partial content
1063
        self.assertEqual(r.status_code, 416)
1064

    
1065
    def test_get_with_if_match(self):
1066
        #perform get with If-Match
1067
        headers = {'HTTP_IF_MATCH':self.objects[0]['hash']}
1068
        r = self.get_object(self.account,
1069
                        self.containers[1],
1070
                        self.objects[0]['name'],
1071
                        **headers)
1072
        #assert get success
1073
        self.assertEqual(r.status_code, 200)
1074
        
1075
        #assert content-type
1076
        self.assertEqual(r['Content-Type'],
1077
                         self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
1078
        
1079
        #assert response content
1080
        self.assertEqual(self.objects[0]['data'].strip(), r.content.strip())
1081

    
1082
    def test_get_with_if_match_star(self):
1083
        #perform get with If-Match *
1084
        headers = {'HTTP_IF_MATCH':'*'}
1085
        r = self.get_object(self.account,
1086
                        self.containers[1],
1087
                        self.objects[0]['name'],
1088
                        **headers)
1089
        #assert get success
1090
        self.assertEqual(r.status_code, 200)
1091
        
1092
        #assert content-type
1093
        self.assertEqual(r['Content-Type'],
1094
                         self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
1095
        
1096
        #assert response content
1097
        self.assertEqual(self.objects[0]['data'].strip(), r.content.strip())
1098

    
1099
    def test_get_with_multiple_if_match(self):
1100
        #perform get with If-Match
1101
        etags = [i['hash'] for i in self.objects if i]
1102
        etags = ','.join('"%s"' % etag for etag in etags)
1103
        headers = {'HTTP_IF_MATCH':etags}
1104
        r = self.get_object(self.account,
1105
                        self.containers[1],
1106
                        self.objects[0]['name'],
1107
                        **headers)
1108
        #assert get success
1109
        self.assertEqual(r.status_code, 200)
1110
        
1111
        #assert content-type
1112
        self.assertEqual(r['Content-Type'],
1113
                         self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
1114
        
1115
        #assert content-type
1116
        self.assertEqual(r['Content-Type'],
1117
                         self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
1118
        
1119
        #assert response content
1120
        self.assertEqual(self.objects[0]['data'].strip(), r.content.strip())
1121

    
1122
    def test_if_match_precondition_failed(self):
1123
        #perform get with If-Match
1124
        headers = {'HTTP_IF_MATCH':'123'}
1125
        r = self.get_object(self.account,
1126
                        self.containers[1],
1127
                        self.objects[0]['name'],
1128
                        **headers)
1129
        #assert precondition failed 
1130
        self.assertEqual(r.status_code, 412)
1131

    
1132
    def test_if_none_match(self):
1133
        #perform get with If-None-Match
1134
        headers = {'HTTP_IF_NONE_MATCH':'123'}
1135
        r = self.get_object(self.account,
1136
                        self.containers[1],
1137
                        self.objects[0]['name'],
1138
                        **headers)
1139
        
1140
        #assert get success
1141
        self.assertEqual(r.status_code, 200)
1142
        
1143
        #assert content-type
1144
        self.assertEqual(r['Content-Type'],
1145
                         self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
1146

    
1147
    def test_if_none_match(self):
1148
        #perform get with If-None-Match *
1149
        headers = {'HTTP_IF_NONE_MATCH':'*'}
1150
        r = self.get_object(self.account,
1151
                        self.containers[1],
1152
                        self.objects[0]['name'],
1153
                        **headers)
1154
        
1155
        #assert get success
1156
        self.assertEqual(r.status_code, 304)
1157

    
1158
    def test_if_none_match_not_modified(self):
1159
        #perform get with If-None-Match
1160
        headers = {'HTTP_IF_NONE_MATCH':'%s' %self.objects[0]['hash']}
1161
        r = self.get_object(self.account,
1162
                        self.containers[1],
1163
                        self.objects[0]['name'],
1164
                        **headers)
1165
        
1166
        #assert not modified
1167
        self.assertEqual(r.status_code, 304)
1168
        self.assertEqual(r['ETag'], self.objects[0]['hash'])
1169

    
1170
    def test_if_modified_since(self):
1171
        t = datetime.datetime.utcnow()
1172
        t2 = t - datetime.timedelta(minutes=10)
1173
        
1174
        #modify the object
1175
        self.upload_object(self.account,
1176
                           self.containers[1],
1177
                           self.objects[0]['name'],
1178
                           self.objects[0]['data'][:200])
1179
        
1180
        for f in DATE_FORMATS:
1181
            past = t2.strftime(f)
1182
            
1183
            headers = {'HTTP_IF_MODIFIED_SINCE':'%s' %past}
1184
            r = self.get_object(self.account,
1185
                        self.containers[1],
1186
                        self.objects[0]['name'],
1187
                        **headers)
1188
            
1189
            #assert get success
1190
            self.assertEqual(r.status_code, 200)
1191
            
1192
            #assert content-type
1193
            self.assertEqual(r['Content-Type'],
1194
                             self.objects[0]['meta']['HTTP_CONTENT_TYPE'])   
1195

    
1196
    def test_if_modified_since_invalid_date(self):
1197
        headers = {'HTTP_IF_MODIFIED_SINCE':''}
1198
        r = self.get_object(self.account,
1199
                    self.containers[1],
1200
                    self.objects[0]['name'],
1201
                    **headers)
1202
        
1203
        #assert get success
1204
        self.assertEqual(r.status_code, 200)
1205
        
1206
        #assert content-type
1207
        self.assertEqual(r['Content-Type'],
1208
                         self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
1209

    
1210
    def test_if_not_modified_since(self):
1211
        now = datetime.datetime.utcnow()
1212
        since = now + datetime.timedelta(1)
1213
        
1214
        for f in DATE_FORMATS:
1215
            headers = {'HTTP_IF_MODIFIED_SINCE':'%s' %since.strftime(f)}
1216
            r = self.get_object(self.account,
1217
                                self.containers[1],
1218
                                self.objects[0]['name'],
1219
                                **headers)
1220
            
1221
            #assert not modified
1222
            self.assertEqual(r.status_code, 304)
1223

    
1224
    def test_if_unmodified_since(self):
1225
        now = datetime.datetime.utcnow()
1226
        since = now + datetime.timedelta(1)
1227
        
1228
        for f in DATE_FORMATS:
1229
            headers = {'HTTP_IF_UNMODIFIED_SINCE':'%s' %since.strftime(f)}
1230
            r = self.get_object(self.account,
1231
                                self.containers[1],
1232
                                self.objects[0]['name'],
1233
                                **headers)
1234
            #assert success
1235
            self.assertEqual(r.status_code, 200)
1236
            self.assertEqual(self.objects[0]['data'], r.content)
1237
            
1238
            #assert content-type
1239
            self.assertEqual(r['Content-Type'],
1240
                             self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
1241

    
1242
    def test_if_unmodified_since_precondition_failed(self):
1243
        t = datetime.datetime.utcnow()
1244
        t2 = t - datetime.timedelta(minutes=10)
1245
        
1246
        #modify the object
1247
        self.upload_object(self.account,
1248
                           self.containers[1],
1249
                           self.objects[0]['name'],
1250
                           self.objects[0]['data'][:200])
1251
        
1252
        for f in DATE_FORMATS:
1253
            past = t2.strftime(f)
1254
            
1255
            headers = {'HTTP_IF_UNMODIFIED_SINCE':'%s' %past}
1256
            r = self.get_object(self.account,
1257
                        self.containers[1],
1258
                        self.objects[0]['name'],
1259
                        **headers)
1260
            #assert get success
1261
            self.assertEqual(r.status_code, 412)
1262

    
1263
    def test_hashes(self):
1264
        l = 8388609
1265
        fname = 'largefile'
1266
        o = self.upload_random_data(self.account,
1267
                                self.containers[1],
1268
                                fname,
1269
                                l)
1270
        if o:
1271
            r = self.get_object(self.account,
1272
                                self.containers[1],
1273
                                fname,
1274
                                'json')
1275
            body = json.loads(r.content)
1276
            hashes = body['hashes']
1277
            block_size = body['block_size']
1278
            block_hash = body['block_hash']
1279
            block_num = l/block_size == 0 and l/block_size or l/block_size + 1
1280
            self.assertTrue(len(hashes), block_num)
1281
            i = 0
1282
            for h in hashes:
1283
                start = i * block_size
1284
                end = (i + 1) * block_size
1285
                hash = compute_block_hash(o['data'][start:end], block_hash)
1286
                self.assertEqual(h, hash)
1287
                i += 1
1288

    
1289
class ObjectPut(BaseTestCase):
1290
    def setUp(self):
1291
        BaseTestCase.setUp(self)
1292
        self.account = 'test'
1293
        self.container = 'c1'
1294
        self.create_container(self.account, self.container)
1295
        
1296
        self.src = os.path.join('.', 'api', 'tests.py')
1297
        self.dest = os.path.join('.', 'api', 'chunked_update_test_file')
1298
        create_chunked_update_test_file(self.src, self.dest)
1299

    
1300
    def tearDown(self):
1301
        r = self.list_objects(self.account, self.container)
1302
        for o in get_content_splitted(r):
1303
            self.delete_object(self.account, self.container, o)
1304
        self.delete_container(self.account, self.container)
1305
        
1306
        # delete test file
1307
        os.remove(self.dest)
1308

    
1309
    def test_upload(self):
1310
        filename = 'tests.py'
1311
        fullpath = os.path.join('.', 'api', filename) 
1312
        f = open(fullpath, 'r')
1313
        data = f.read()
1314
        hash = compute_md5_hash(data)
1315
        meta={'HTTP_ETAG':hash,
1316
              'HTTP_X_OBJECT_META_TEST':'test1'
1317
              }
1318
        type, enc = mimetypes.guess_type(fullpath)
1319
        meta['HTTP_CONTENT_TYPE'] = type and type or 'plain/text'
1320
        if enc:
1321
            meta['HTTP_CONTENT_ENCODING'] = enc
1322
        r = self.upload_object(self.account,
1323
                               self.container,
1324
                               filename,
1325
                               data,
1326
                               content_type=meta['HTTP_CONTENT_TYPE'],
1327
                               **meta)
1328
        self.assertEqual(r.status_code, 201)
1329
        r = self.get_object_meta(self.account, self.container, filename)
1330
        self.assertTrue(r['X-Object-Meta-Test'])
1331
        self.assertEqual(r['X-Object-Meta-Test'],
1332
                         meta['HTTP_X_OBJECT_META_TEST'])
1333
        
1334
        #assert uploaded content
1335
        r = self.get_object(self.account, self.container, filename)
1336
        self.assertEqual(os.path.getsize(fullpath), int(r['Content-Length']))
1337
        self.assertEqual(data.strip(), r.content.strip())
1338

    
1339
    def test_upload_unprocessable_entity(self):
1340
        filename = 'tests.py'
1341
        fullpath = os.path.join('.', 'api', filename) 
1342
        f = open(fullpath, 'r')
1343
        data = f.read()
1344
        meta={'HTTP_ETAG':'123',
1345
              'HTTP_X_OBJECT_META_TEST':'test1'
1346
              }
1347
        type, enc = mimetypes.guess_type(fullpath)
1348
        meta['HTTP_CONTENT_TYPE'] = type and type or 'plain/text'
1349
        if enc:
1350
            meta['HTTP_CONTENT_ENCODING'] = enc
1351
        r = self.upload_object(self.account,
1352
                               self.container,
1353
                               filename,
1354
                               data,
1355
                               content_type = meta['HTTP_CONTENT_TYPE'],
1356
                               **meta)
1357
        self.assertEqual(r.status_code, 422)
1358

    
1359
    def test_chucked_update(self):
1360
        objname = os.path.split(self.src)[-1:][0]
1361
        f = open(self.dest, 'r')
1362
        data = f.read()
1363
        meta = {}
1364
        type, enc = mimetypes.guess_type(self.dest)
1365
        meta['HTTP_CONTENT_TYPE'] = type and type or 'plain/text'
1366
        if enc:
1367
            meta['HTTP_CONTENT_ENCODING'] = enc
1368
        meta.update({'HTTP_TRANSFER_ENCODING':'chunked'})
1369
        r = self.upload_object(self.account,
1370
                               self.container,
1371
                               objname,
1372
                               data,
1373
                               content_type = 'plain/text',
1374
                               **meta)
1375
        self.assertEqual(r.status_code, 201)
1376
        
1377
        r = self.get_object(self.account,
1378
                            self.container,
1379
                            objname)
1380
        uploaded_data = r.content
1381
        f = open(self.src, 'r')
1382
        actual_data = f.read()
1383
        self.assertEqual(actual_data, uploaded_data)
1384

    
1385
class ObjectCopy(BaseTestCase):
1386
    def setUp(self):
1387
        BaseTestCase.setUp(self)
1388
        self.account = 'test'
1389
        self.containers = ['c1', 'c2']
1390
        for c in self.containers:
1391
            self.create_container(self.account, c)
1392
        self.obj = self.upload_os_file(self.account,
1393
                            self.containers[0],
1394
                            './api/tests.py')
1395

    
1396
    def tearDown(self):
1397
        for c in self.containers:
1398
            for o in get_content_splitted(self.list_objects(self.account, c)):
1399
                self.delete_object(self.account, c, o)
1400
            self.delete_container(self.account, c)
1401

    
1402
    def test_copy(self):
1403
        with AssertInvariant(self.get_object_meta, self.account,
1404
                             self.containers[0], self.obj['name']):
1405
            #perform copy
1406
            meta = {'HTTP_X_OBJECT_META_TEST':'testcopy'}
1407
            src_path = os.path.join('/', self.containers[0], self.obj['name'])
1408
            r = self.copy_object(self.account,
1409
                             self.containers[0],
1410
                             'testcopy',
1411
                             src_path,
1412
                             **meta)
1413
            #assert copy success
1414
            self.assertEqual(r.status_code, 201)
1415
            
1416
            #assert access the new object
1417
            r = self.get_object_meta(self.account,
1418
                                     self.containers[0],
1419
                                     'testcopy')
1420
            self.assertTrue(r['X-Object-Meta-Test'])
1421
            self.assertTrue(r['X-Object-Meta-Test'], 'testcopy')
1422
            
1423
            #assert etag is the same
1424
            self.assertEqual(r['ETag'], self.obj['hash'])
1425
            
1426
            #assert src object still exists
1427
            r = self.get_object_meta(self.account, self.containers[0],
1428
                                     self.obj['name'])
1429
            self.assertEqual(r.status_code, 200)
1430

    
1431
    def test_copy_from_different_container(self):
1432
        with AssertInvariant(self.get_object_meta,
1433
                             self.account,
1434
                             self.containers[0],
1435
                             self.obj['name']):
1436
            meta = {'HTTP_X_OBJECT_META_TEST':'testcopy'}
1437
            src_path = os.path.join('/', self.containers[0], self.obj['name'])
1438
            r = self.copy_object(self.account,
1439
                             self.containers[1],
1440
                             'testcopy',
1441
                             src_path,
1442
                             **meta)
1443
            self.assertEqual(r.status_code, 201)
1444
            
1445
            # assert updated metadata
1446
            r = self.get_object_meta(self.account,
1447
                                     self.containers[1],
1448
                                     'testcopy')
1449
            self.assertTrue(r['X-Object-Meta-Test'])
1450
            self.assertTrue(r['X-Object-Meta-Test'], 'testcopy')
1451
            
1452
            #assert src object still exists
1453
            r = self.get_object_meta(self.account, self.containers[0],
1454
                                     self.obj['name'])
1455
            self.assertEqual(r.status_code, 200)
1456

    
1457
    def test_copy_invalid(self):
1458
        #copy from invalid object
1459
        meta = {'HTTP_X_OBJECT_META_TEST':'testcopy'}
1460
        r = self.copy_object(self.account,
1461
                         self.containers[1],
1462
                         'testcopy',
1463
                         os.path.join('/', self.containers[0], 'test.py'),
1464
                         **meta)
1465
        self.assertItemNotFound(r)
1466
        
1467
        #copy from invalid container
1468
        meta = {'HTTP_X_OBJECT_META_TEST':'testcopy'}
1469
        src_path = os.path.join('/', self.containers[1], self.obj['name'])
1470
        r = self.copy_object(self.account,
1471
                         self.containers[1],
1472
                         'testcopy',
1473
                         src_path,
1474
                         **meta)
1475
        self.assertItemNotFound(r)
1476

    
1477
class ObjectMove(ObjectCopy):
1478
    def test_move(self):
1479
        #perform move
1480
        meta = {'HTTP_X_OBJECT_META_TEST':'testcopy'}
1481
        src_path = os.path.join('/', self.containers[0], self.obj['name'])
1482
        r = self.move_object(self.account,
1483
                         self.containers[0],
1484
                         'testcopy',
1485
                         src_path,
1486
                         **meta)
1487
        #assert successful move
1488
        self.assertEqual(r.status_code, 201)
1489
        
1490
        #assert updated metadata
1491
        r = self.get_object_meta(self.account,
1492
                                 self.containers[0],
1493
                                 'testcopy')
1494
        self.assertTrue(r['X-Object-Meta-Test'])
1495
        self.assertTrue(r['X-Object-Meta-Test'], 'testcopy')
1496
        
1497
        #assert src object no more exists
1498
        r = self.get_object_meta(self.account, self.containers[0],
1499
                                 self.obj['name'])
1500
        self.assertItemNotFound(r)
1501

    
1502
class ObjectPost(BaseTestCase):
1503
    def setUp(self):
1504
        BaseTestCase.setUp(self)
1505
        self.account = 'test'
1506
        self.containers = ['c1', 'c2']
1507
        for c in self.containers:
1508
            self.create_container(self.account, c)
1509
        self.obj = self.upload_os_file(self.account,
1510
                                       self.containers[0],
1511
                                       './api/tests.py')
1512

    
1513
    def tearDown(self):
1514
        for c in self.containers:
1515
            for o in get_content_splitted(self.list_objects(self.account, c)):
1516
                self.delete_object(self.account, c, o)
1517
            self.delete_container(self.account, c)
1518

    
1519
    def test_update_meta(self):
1520
        #perform update metadata
1521
        more = {'HTTP_X_OBJECT_META_FOO':'foo',
1522
                'HTTP_X_OBJECT_META_BAR':'bar'}
1523
        r = self.update_object(self.account,
1524
                                self.containers[0],
1525
                                self.obj['name'],
1526
                                **more)
1527
        #assert request accepted
1528
        self.assertEqual(r.status_code, 202)
1529
        
1530
        #assert old metadata are still there
1531
        r = self.get_object_meta(self.account, self.containers[0],
1532
                                 self.obj['name'])
1533
        self.assertTrue('X-Object-Meta-Test' not in r.items())
1534
        
1535
        #assert new metadata have been updated
1536
        for k,v in more.items():
1537
            key = '-'.join(elem.capitalize() for elem in k.split('_')[1:])
1538
            self.assertTrue(r[key])
1539
            self.assertTrue(r[key], v)
1540

    
1541
    def test_update_object(self,
1542
                           first_byte_pos=0,
1543
                           last_byte_pos=499,
1544
                           instance_length = True,
1545
                           content_length = 500):
1546
        l = len(self.obj['data'])
1547
        length =  instance_length and l or '*'
1548
        range = 'bytes %d-%d/%s' %(first_byte_pos,
1549
                                       last_byte_pos,
1550
                                       length)
1551
        partial = last_byte_pos - first_byte_pos + 1
1552
        data = get_random_data(partial)
1553
        more = {'HTTP_CONTENT_RANGE':range}
1554
        if content_length:
1555
            more.update({'CONTENT_LENGTH':'%s' % content_length})
1556
        
1557
        r = self.update_object(self.account,
1558
                                self.containers[0],
1559
                                self.obj['name'],
1560
                                data,
1561
                                'application/octet-stream',
1562
                                **more)
1563
        
1564
        if partial < 0 or (instance_length and l <= last_byte_pos):
1565
            self.assertEqual(r.status_code, 416)    
1566
        elif content_length and content_length != partial:
1567
            self.assertEqual(r.status_code, 400)
1568
        else:
1569
            self.assertEqual(r.status_code, 204)
1570
            
1571
            #check modified object
1572
            r = self.get_object(self.account,
1573
                            self.containers[0],
1574
                            self.obj['name'])
1575
            self.assertEqual(r.content[0:partial], data)
1576
            self.assertEqual(r.content[partial:l], self.obj['data'][partial:l])
1577

    
1578
    def test_update_object_no_content_length(self):
1579
        self.test_update_object(content_length = None)
1580

    
1581
    def test_update_object_invalid_content_length(self):
1582
        with AssertContentInvariant(self.get_object, self.account, self.containers[0],
1583
                            self.obj['name']):
1584
            self.test_update_object(content_length = 1000)
1585

    
1586
    def test_update_object_with_unknown_instance_length(self):
1587
        self.test_update_object(instance_length = False)
1588

    
1589
    def test_update_object_invalid_range(self):
1590
        with AssertContentInvariant(self.get_object, self.account, self.containers[0],
1591
                            self.obj['name']):
1592
            self.test_update_object(499, 0, True)
1593
    
1594
    def test_update_object_invalid_range_and_length(self):
1595
        with AssertContentInvariant(self.get_object, self.account, self.containers[0],
1596
                            self.obj['name']):
1597
            self.test_update_object(499, 0, True, -1)
1598
    
1599
    def test_update_object_invalid_range_with_no_content_length(self):
1600
        with AssertContentInvariant(self.get_object, self.account, self.containers[0],
1601
                            self.obj['name']):
1602
            self.test_update_object(499, 0, True, content_length = None)
1603
    
1604
    def test_update_object_out_of_limits(self):    
1605
        with AssertContentInvariant(self.get_object, self.account, self.containers[0],
1606
                            self.obj['name']):
1607
            l = len(self.obj['data'])
1608
            self.test_update_object(0, l+1, True)
1609

    
1610
    def test_append(self):
1611
        data = get_random_data(500)
1612
        more = {'CONTENT_LENGTH':'500',
1613
                'HTTP_CONTENT_RANGE':'bytes */*'}
1614
        
1615
        r = self.update_object(self.account,
1616
                                self.containers[0],
1617
                                self.obj['name'],
1618
                                data,
1619
                                'application/octet-stream',
1620
                                **more)
1621
        
1622
        self.assertEqual(r.status_code, 204)
1623
        
1624
        r = self.get_object(self.account,
1625
                                self.containers[0],
1626
                                self.obj['name'])
1627
        self.assertEqual(len(r.content), len(self.obj['data']) + 500)
1628
        self.assertEqual(r.content[:-500], self.obj['data'])
1629

    
1630
    def test_update_with_chunked_transfer(self):
1631
        data, pure = create_random_chunked_data()
1632
        dl = len(pure)
1633
        fl = len(self.obj['data'])
1634
        meta = {'HTTP_TRANSFER_ENCODING':'chunked',
1635
                'HTTP_CONTENT_RANGE':'bytes 0-/%d' %fl}
1636
        r = self.update_object(self.account,
1637
                                self.containers[0],
1638
                                self.obj['name'],
1639
                                data,
1640
                                'application/octet-stream',
1641
                                **meta)
1642
        
1643
        #check modified object
1644
        r = self.get_object(self.account,
1645
                        self.containers[0],
1646
                        self.obj['name'])
1647
        self.assertEqual(r.content[0:dl], pure)
1648
        self.assertEqual(r.content[dl:fl], self.obj['data'][dl:fl])
1649

    
1650
    def test_update_with_chunked_transfer_strict_range(self):
1651
        data, pure = create_random_chunked_data()
1652
        dl = len(pure) - 1
1653
        fl = len(self.obj['data'])
1654
        meta = {'HTTP_TRANSFER_ENCODING':'chunked',
1655
                'HTTP_CONTENT_RANGE':'bytes 0-%d/%d' %(dl, fl)}
1656
        r = self.update_object(self.account,
1657
                                self.containers[0],
1658
                                self.obj['name'],
1659
                                data,
1660
                                'application/octet-stream',
1661
                                **meta)
1662
        
1663
        #check modified object
1664
        r = self.get_object(self.account,
1665
                        self.containers[0],
1666
                        self.obj['name'])
1667
        self.assertEqual(r.content[0:dl+1], pure)
1668
        self.assertEqual(r.content[dl+1:fl], self.obj['data'][dl+1:fl])
1669

    
1670
class ObjectDelete(BaseTestCase):
1671
    def setUp(self):
1672
        BaseTestCase.setUp(self)
1673
        self.account = 'test'
1674
        self.containers = ['c1', 'c2']
1675
        for c in self.containers:
1676
            self.create_container(self.account, c)
1677
        self.obj = self.upload_os_file(self.account,
1678
                            self.containers[0],
1679
                            './api/tests.py')
1680

    
1681
    def tearDown(self):
1682
        for c in self.containers:
1683
            for o in get_content_splitted(self.list_objects(self.account, c)):
1684
                self.delete_object(self.account, c, o)
1685
            self.delete_container(self.account, c)
1686

    
1687
    def test_delete(self):
1688
        #perform delete object
1689
        r = self.delete_object(self.account, self.containers[0],
1690
                               self.obj['name'])
1691
        
1692
        #assert success
1693
        self.assertEqual(r.status_code, 204)
1694

    
1695
    def test_delete_invalid(self):
1696
        #perform delete object
1697
        r = self.delete_object(self.account, self.containers[1],
1698
                               self.obj['name'])
1699
        
1700
        #assert failure
1701
        self.assertItemNotFound(r)
1702

    
1703
class AssertInvariant(object):
1704
    def __init__(self, callable, *args, **kwargs):
1705
        self.callable = callable
1706
        self.args = args
1707
        self.kwargs = kwargs
1708

    
1709
    def __enter__(self):
1710
        self.items = self.callable(*self.args, **self.kwargs).items()
1711
        return self.items
1712

    
1713
    def __exit__(self, type, value, tb):
1714
        items = self.callable(*self.args, **self.kwargs).items()
1715
        assert self.items == items
1716

    
1717
class AssertContentInvariant(object):
1718
    def __init__(self, callable, *args, **kwargs):
1719
        self.callable = callable
1720
        self.args = args
1721
        self.kwargs = kwargs
1722

    
1723
    def __enter__(self):
1724
        self.content = self.callable(*self.args, **self.kwargs).content
1725
        return self.content
1726

    
1727
    def __exit__(self, type, value, tb):
1728
        content = self.callable(*self.args, **self.kwargs).content
1729
        assert self.content == content
1730

    
1731
def get_content_splitted(response):
1732
    if response:
1733
        return response.content.split('\n')
1734

    
1735
def compute_md5_hash(data):
1736
    md5 = hashlib.md5()
1737
    offset = 0
1738
    md5.update(data)
1739
    return md5.hexdigest().lower()
1740

    
1741
def compute_block_hash(data, algorithm):
1742
    h = hashlib.new(algorithm)
1743
    h.update(data.rstrip('\x00'))
1744
    return h.hexdigest()
1745

    
1746
def create_chunked_update_test_file(src, dest):
1747
    fr = open(src, 'r')
1748
    fw = open(dest, 'w')
1749
    data = fr.readline()
1750
    while data:
1751
        fw.write(hex(len(data)))
1752
        fw.write('\r\n')
1753
        fw.write(data)
1754
        data = fr.readline()
1755
    fw.write(hex(0))
1756
    fw.write('\r\n')
1757

    
1758
def create_random_chunked_data(rows=5):
1759
    i = 0
1760
    out = []
1761
    pure= []
1762
    while i < rows:
1763
        data = get_random_data(random.randint(1, 100))
1764
        out.append(hex(len(data)))
1765
        out.append(data)
1766
        pure.append(data)
1767
        i+=1
1768
    out.append(hex(0))
1769
    out.append('\r\n')
1770
    return '\r\n'.join(out), ''.join(pure)
1771

    
1772
def get_random_data(length=500):
1773
    char_set = string.ascii_uppercase + string.digits
1774
    return ''.join(random.choice(char_set) for x in range(length))
1775

    
1776
o_names = ['kate.jpg',
1777
           'kate_beckinsale.jpg',
1778
           'How To Win Friends And Influence People.pdf',
1779
           'moms_birthday.jpg',
1780
           'poodle_strut.mov',
1781
           'Disturbed - Down With The Sickness.mp3',
1782
           'army_of_darkness.avi',
1783
           'the_mad.avi',
1784
           'photos/animals/dogs/poodle.jpg',
1785
           'photos/animals/dogs/terrier.jpg',
1786
           'photos/animals/cats/persian.jpg',
1787
           'photos/animals/cats/siamese.jpg',
1788
           'photos/plants/fern.jpg',
1789
           'photos/plants/rose.jpg',
1790
           'photos/me.jpg']