Statistics
| Branch: | Tag: | Revision:

root / pithos / api / tests.py @ 33ffca25

History | View | Annotate | Download (66.2 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'] = '46e427d657b20defe352804f0eb6f8a2'
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
            'object':(
78
                'ETag',
79
                'Content-Length',
80
                'Content-Type',
81
                'Content-Encoding',
82
                'Last-Modified',
83
                'Date',
84
                'X-Object-Manifest',
85
                'Content-Range',)}
86
        self.contentTypes = {'xml':'application/xml',
87
                             'json':'application/json',
88
                             '':'text/plain'}
89
        self.extended = {
90
            'container':(
91
                'name',
92
                'count',
93
                'bytes',
94
                'last_modified'),
95
            'object':(
96
                'name',
97
                'hash',
98
                'bytes',
99
                'content_type',
100
                'content_encoding',
101
                'last_modified',)}
102
        self.return_codes = (400, 401, 404, 503,)
103

    
104
    def assertFault(self, response, status_code, name):
105
        self.assertEqual(response.status_code, status_code)
106

    
107
    def assertBadRequest(self, response):
108
        self.assertFault(response, 400, 'badRequest')
109

    
110
    def assertItemNotFound(self, response):
111
        self.assertFault(response, 404, 'itemNotFound')
112

    
113
    def assertUnauthorized(self, response):
114
        self.assertFault(response, 401, 'unauthorized')
115

    
116
    def assertServiceUnavailable(self, response):
117
        self.assertFault(response, 503, 'serviceUnavailable')
118

    
119
    def assertNonEmpty(self, response):
120
        self.assertFault(response, 409, 'nonEmpty')
121

    
122
    def assert_status(self, response, codes):
123
        l = [elem for elem in self.return_codes]
124
        if type(codes) == types.ListType:
125
            l.extend(codes)
126
        else:
127
            l.append(codes)
128
        self.assertTrue(response.status_code in l)
129

    
130
    def get_account_meta(self, account, exp_meta={}):
131
        path = '/v1/%s' % account
132
        response = self.client.head(path)
133
        self.assert_status(response, 204)
134
        self.assert_headers(response, 'account', exp_meta)
135
        return response
136

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

    
153
    def update_account_meta(self, account, **metadata):
154
        path = '/v1/%s' % account
155
        response = self.client.post(path, **metadata)
156
        response.content = response.content.strip()
157
        self.assert_status(response, 202)
158
        return response
159

    
160
    def get_container_meta(self, account, container, exp_meta={}):
161
        params = locals()
162
        params.pop('self')
163
        params.pop('account')
164
        params.pop('container')
165
        path = '/v1/%s/%s' %(account, container)
166
        response = self.client.head(path, params)
167
        response.content = response.content.strip()
168
        self.assert_status(response, 204)
169
        if response.status_code == 204:
170
            self.assert_headers(response, 'container', exp_meta)
171
        return response
172

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

    
188
    def create_container(self, account, name, **meta):
189
        path = '/v1/%s/%s' %(account, name)
190
        response = self.client.put(path, **meta)
191
        response.content = response.content.strip()
192
        self.assert_status(response, [201, 202])
193
        return response
194

    
195
    def update_container_meta(self, account, name, **meta):
196
        path = '/v1/%s/%s' %(account, name)
197
        response = self.client.post(path,
198
                                    data=None,
199
                                    content_type='text/xml',
200
                                    follow=False, **meta)
201
        response.content = response.content.strip()
202
        self.assert_status(response, 202)
203
        return response
204

    
205
    def delete_container(self, account, container):
206
        path = '/v1/%s/%s' %(account, container)
207
        response = self.client.delete(path)
208
        response.content = response.content.strip()
209
        self.assert_status(response, [204, 409])
210
        return response
211

    
212
    def get_object_meta(self, account, container, name):
213
        path = '/v1/%s/%s/%s' %(account, container, name)
214
        response = self.client.head(path)
215
        response.content = response.content.strip()
216
        self.assert_status(response, 204)
217
        return response
218

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

    
228
    def upload_object(self, account, container, name, data, content_type='',
229
                      **headers):
230
        path = '/v1/%s/%s/%s' %(account, container, name)
231
        response = self.client.put(path, data, content_type, **headers)
232
        response.content = response.content.strip()
233
        self.assert_status(response, [201, 411, 422])
234
        if response.status_code == 201:
235
            self.assertTrue(response['Etag'])
236
        return response
237

    
238
    def copy_object(self, account, container, name, src, **headers):
239
        path = '/v1/%s/%s/%s' %(account, container, name)
240
        headers['HTTP_X_COPY_FROM'] = src
241
        response = self.client.put(path, **headers)
242
        response.content = response.content.strip()
243
        self.assert_status(response, 201)
244
        return response
245

    
246
    def move_object(self, account, container, name, src, **headers):
247
        path = '/v1/%s/%s/%s' % (account, container, name)
248
        headers['HTTP_X_MOVE_FROM'] = src
249
        response = self.client.put(path, **headers)
250
        response.content = response.content.strip()
251
        self.assert_status(response, 201)
252
        return response
253

    
254
    def update_object(self, account, container, name, data={},
255
                      content_type='MULTIPART_CONTENT', **headers):
256
        path = '/v1/%s/%s/%s' %(account, container, name)
257
        response = self.client.post(path, data, content_type, **headers)
258
        response.content = response.content.strip()
259
        self.assert_status(response, [202, 204, 416])
260
        return response
261

    
262
    def delete_object(self, account, container, name):
263
        path = '/v1/%s/%s/%s' %(account, container, name)
264
        response = self.client.delete(path)
265
        response.content = response.content.strip()
266
        self.assert_status(response, 204)
267
        return response
268

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

    
280
    def assert_extended(self, response, format, type, size):
281
        exp_content_type = self.contentTypes[format]
282
        self.assertEqual(response['Content-Type'].find(exp_content_type), 0)
283
        if format == 'xml':
284
            self.assert_xml(response, type, size)
285
        elif format == 'json':
286
            self.assert_json(response, type, size)
287

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

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

    
313
    def upload_os_file(self, account, container, fullpath, meta={}):
314
        try:
315
            f = open(fullpath, 'r')
316
            data = f.read()
317
            name = os.path.split(fullpath)[-1]
318
            return self.upload_data(account, container, name, data)    
319
        except IOError:
320
            return
321

    
322
    def upload_random_data(self, account, container, name, length=1024,
323
                           meta={}):
324
        data = get_random_data(length)
325
        return self.upload_data(account, container, name, data, meta)
326

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

    
352
class AccountHead(BaseTestCase):
353
    def setUp(self):
354
        BaseTestCase.setUp(self)
355
        self.account = 'test'
356
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
357
        for item in self.containers:
358
            self.create_container(self.account, item)
359

    
360
    def tearDown(self):
361
        for c in  get_content_splitted(self.list_containers(self.account)):
362
            self.delete_container(self.account, c)
363

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

    
376
    #def test_get_account_401(self):
377
    #    response = self.get_account_meta('non-existing-account')
378
    #    print response
379
    #    self.assertEqual(response.status_code, 401)
380

    
381
class AccountGet(BaseTestCase):
382
    def setUp(self):
383
        BaseTestCase.setUp(self)
384
        self.account = 'test'
385
        #create some containers
386
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
387
        for item in self.containers:
388
            self.create_container(self.account, item)
389

    
390
    def tearDown(self):
391
        for c in get_content_splitted(self.list_containers(self.account)):
392
            response = self.delete_container(self.account, c)
393

    
394
    def test_list(self):
395
        #list containers
396
        response = self.list_containers(self.account)
397
        containers = get_content_splitted(response)
398
        self.assertEquals(self.containers, containers)
399

    
400
    #def test_list_204(self):
401
    #    response = self.list_containers('non-existing-account')
402
    #    self.assertEqual(response.status_code, 204)
403

    
404
    def test_list_with_limit(self):
405
        limit = 2
406
        response = self.list_containers(self.account, limit=limit)
407
        containers = get_content_splitted(response)
408
        self.assertEquals(len(containers), limit)
409
        self.assertEquals(self.containers[:2], containers)
410

    
411
    def test_list_with_marker(self):
412
        l = 2
413
        m = 'bananas'
414
        response = self.list_containers(self.account, limit=l, marker=m)
415
        containers =  get_content_splitted(response)
416
        i = self.containers.index(m) + 1
417
        self.assertEquals(self.containers[i:(i+l)], containers)
418
        
419
        m = 'oranges'
420
        response = self.list_containers(self.account, limit=l, marker=m)
421
        containers = get_content_splitted(response)
422
        i = self.containers.index(m) + 1
423
        self.assertEquals(self.containers[i:(i+l)], containers)
424

    
425
    #def test_extended_list(self):
426
    #    self.list_containers(self.account, limit=3, format='xml')
427
    #    self.list_containers(self.account, limit=3, format='json')
428

    
429
    def test_list_json_with_marker(self):
430
        l = 2
431
        m = 'bananas'
432
        response = self.list_containers(self.account, limit=l, marker=m,
433
                                        format='json')
434
        containers = json.loads(response.content)
435
        self.assertEqual(containers[0]['name'], 'kiwis')
436
        self.assertEqual(containers[1]['name'], 'oranges')
437

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

    
448
    def test_if_modified_since(self):
449
        t = datetime.datetime.utcnow()
450
        t2 = t - datetime.timedelta(minutes=10)
451
        
452
        #add a new container
453
        self.create_container(self.account,
454
                              'dummy')
455

    
456
        for f in DATE_FORMATS:
457
            past = t2.strftime(f)
458
            
459
            headers = {'HTTP_IF_MODIFIED_SINCE':'%s' %past}
460
            r = self.list_containers(self.account, **headers)
461
            
462
            #assert get success
463
            self.assertEqual(r.status_code, 200)
464

    
465
    def test_if_modified_since_invalid_date(self):
466
        headers = {'HTTP_IF_MODIFIED_SINCE':''}
467
        r = self.list_containers(self.account, **headers)
468
            
469
        #assert get success
470
        self.assertEqual(r.status_code, 200)
471

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

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

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

    
512
class AccountPost(BaseTestCase):
513
    def setUp(self):
514
        BaseTestCase.setUp(self)
515
        self.account = 'test'
516
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
517
        for item in self.containers:
518
            self.create_container(self.account, item)
519

    
520
    def tearDown(self):
521
        for c in  get_content_splitted(self.list_containers(self.account)):
522
            self.delete_container(self.account, c)
523

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

    
534
    #def test_invalid_account_update_meta(self):
535
    #    with AssertInvariant(self.get_account_meta, self.account):
536
    #        meta = {'HTTP_X_ACCOUNT_META_TEST':'test',
537
    #               'HTTP_X_ACCOUNT_META_TOST':'tost'}
538
    #        response = self.update_account_meta('non-existing-account', **meta)
539

    
540
class ContainerHead(BaseTestCase):
541
    def setUp(self):
542
        BaseTestCase.setUp(self)
543
        self.account = 'test'
544
        self.container = 'apples'
545
        self.create_container(self.account, self.container)
546

    
547
    def tearDown(self):
548
        for o in self.list_objects(self.account, self.container):
549
            self.delete_object(self.account, self.container, o)
550
        self.delete_container(self.account, self.container)
551

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

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

    
588
    def tearDown(self):
589
        for c in self.container:
590
            for obj in get_content_splitted(self.list_objects(self.account, c)):
591
                self.delete_object(self.account, c, obj)
592
            self.delete_container(self.account, c)
593

    
594
    def test_list_objects(self):
595
        response = self.list_objects(self.account, self.container[0])
596
        objects = get_content_splitted(response)
597
        l = [elem['name'] for elem in self.obj[:8]]
598
        l.sort()
599
        self.assertEqual(objects, l)
600

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

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

    
640
    def test_extended_list_json(self):
641
        response = self.list_objects(self.account,
642
                                     self.container[1],
643
                                     format='json', limit=2,
644
                                     prefix='photos/animals',
645
                                     delimiter='/')
646
        objects = json.loads(response.content)
647
        self.assertEqual(objects[0]['subdir'], 'photos/animals/cats/')
648
        self.assertEqual(objects[1]['subdir'], 'photos/animals/dogs/')
649

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

    
664
    def test_list_using_meta(self):
665
        meta = {'HTTP_X_OBJECT_META_QUALITY':'aaa'}
666
        for o in self.obj[:2]:
667
            r = self.update_object(self.account,
668
                                    self.container[0],
669
                                    o['name'],
670
                                    **meta)
671
        meta = {'HTTP_X_OBJECT_META_STOCK':'true'}
672
        for o in self.obj[3:5]:
673
            r = self.update_object(self.account,
674
                                    self.container[0],
675
                                    o['name'],
676
                                    **meta)
677
        
678
        r = self.list_objects(self.account,
679
                          self.container[0],
680
                          meta='Quality')
681
        self.assertEqual(r.status_code, 200)
682
        obj = get_content_splitted(r)
683
        self.assertEqual(len(obj), 2)
684
        self.assertTrue(obj, [o['name'] for o in self.obj[:2]])
685
        
686
        # test case insensitive
687
        r = self.list_objects(self.account,
688
                          self.container[0],
689
                          meta='quality')
690
        self.assertEqual(r.status_code, 200)
691
        obj = get_content_splitted(r)
692
        self.assertEqual(len(obj), 2)
693
        self.assertTrue(obj, [o['name'] for o in self.obj[:2]])
694
        
695
        # test multiple matches
696
        r = self.list_objects(self.account,
697
                          self.container[0],
698
                          meta='Quality, Stock')
699
        self.assertEqual(r.status_code, 200)
700
        obj = get_content_splitted(r)
701
        self.assertEqual(len(obj), 4)
702
        self.assertTrue(obj, [o['name'] for o in self.obj[:4]])
703
        
704
        # test non 1-1 multiple match
705
        r = self.list_objects(self.account,
706
                          self.container[0],
707
                          meta='Quality, aaaa')
708
        self.assertEqual(r.status_code, 200)
709
        obj = get_content_splitted(r)
710
        self.assertEqual(len(obj), 2)
711
        self.assertTrue(obj, [o['name'] for o in self.obj[:2]])
712

    
713
    def test_if_modified_since(self):
714
        t = datetime.datetime.utcnow()
715
        t2 = t - datetime.timedelta(minutes=10)
716
        
717
        #add a new container
718
        self.upload_random_data(self.account,
719
                                self.container[0],
720
                                'dummy.txt')
721

    
722
        for f in DATE_FORMATS:
723
            past = t2.strftime(f)
724
            
725
            headers = {'HTTP_IF_MODIFIED_SINCE':'%s' %past}
726
            r = self.list_objects(self.account,
727
                                  self.container[0], **headers)
728
            
729
            #assert get success
730
            self.assertEqual(r.status_code, 200)
731

    
732
    def test_if_modified_since_invalid_date(self):
733
        headers = {'HTTP_IF_MODIFIED_SINCE':''}
734
        r = self.list_objects(self.account,
735
                              self.container[0], **headers)
736
        
737
        #assert get success
738
        self.assertEqual(r.status_code, 200)
739

    
740
    def test_if_not_modified_since(self):
741
        now = datetime.datetime.utcnow()
742
        since = now + datetime.timedelta(1)
743
        
744
        for f in DATE_FORMATS:
745
            headers = {'HTTP_IF_MODIFIED_SINCE':'%s' %since.strftime(f)}
746
            r = self.list_objects(self.account,
747
                              self.container[0], **headers)
748
        
749
            #assert not modified
750
            self.assertEqual(r.status_code, 304)
751

    
752
    def test_if_unmodified_since(self):
753
        now = datetime.datetime.utcnow()
754
        since = now + datetime.timedelta(1)
755
        
756
        for f in DATE_FORMATS:
757
            headers = {'HTTP_IF_UNMODIFIED_SINCE':'%s' %since.strftime(f)}
758
            r = self.list_objects(self.account,
759
                              self.container[0], **headers)
760
        
761
            #assert success
762
            self.assertEqual(r.status_code, 200)
763
            objlist = self.list_objects(self.account, self.container[0])
764
            self.assertEqual(get_content_splitted(r),
765
                             get_content_splitted(objlist))
766

    
767
    def test_if_unmodified_since_precondition_failed(self):
768
        t = datetime.datetime.utcnow()
769
        t2 = t - datetime.timedelta(minutes=10)
770
        
771
        #add a new container
772
        self.create_container(self.account,
773
                              'dummy')
774

    
775
        for f in DATE_FORMATS:
776
            past = t2.strftime(f)
777
            
778
            headers = {'HTTP_IF_UNMODIFIED_SINCE':'%s' %past}
779
            r = self.list_objects(self.account,
780
                              self.container[0], **headers)
781
        
782
            #assert get success
783
            self.assertEqual(r.status_code, 412)
784

    
785
class ContainerPut(BaseTestCase):
786
    def setUp(self):
787
        BaseTestCase.setUp(self)
788
        self.account = 'test'
789
        self.containers = ['c1', 'c2']
790

    
791
    def tearDown(self):
792
        for c in self.containers:
793
            r = self.delete_container(self.account, c)
794

    
795
    def test_create(self):
796
        response = self.create_container(self.account, self.containers[0])
797
        if response.status_code == 201:
798
            response = self.list_containers(self.account)
799
            content = get_content_splitted(response)
800
            self.assertTrue(self.containers[0] in content)
801
            r = self.get_container_meta(self.account, self.containers[0])
802
            self.assertEqual(r.status_code, 204)
803

    
804
    def test_create_twice(self):
805
        response = self.create_container(self.account, self.containers[0])
806
        if response.status_code == 201:
807
            r = self.create_container(self.account, self.containers[0])
808
            self.assertTrue(r.status_code, 202)
809

    
810
class ContainerPost(BaseTestCase):
811
    def setUp(self):
812
        BaseTestCase.setUp(self)
813
        self.account = 'test'
814
        self.container = 'apples'
815
        self.create_container(self.account, self.container)
816

    
817
    def tearDown(self):
818
        for o in self.list_objects(self.account, self.container):
819
            self.delete_object(self.account, self.container, o)
820
        self.delete_container(self.account, self.container)
821

    
822
    def test_update_meta(self):
823
        meta = {'HTTP_X_CONTAINER_META_TEST':'test33',
824
                'HTTP_X_CONTAINER_META_TOST':'tost22'}
825
        response = self.update_container_meta(self.account, self.container,
826
                                              **meta)
827
        response = self.get_container_meta(self.account, self.container)
828
        for k,v in meta.items():
829
            key = '-'.join(elem.capitalize() for elem in k.split('_')[1:])
830
            self.assertTrue(response[key])
831
            self.assertEqual(response[key], v)
832

    
833
class ContainerDelete(BaseTestCase):
834
    def setUp(self):
835
        BaseTestCase.setUp(self)
836
        self.account = 'test'
837
        self.containers = ['c1', 'c2']
838
        for c in self.containers:
839
            self.create_container(self.account, c)
840
        self.upload_random_data(self.account,
841
                                self.containers[1],
842
                                'nice.jpg')
843

    
844
    def tearDown(self):
845
        for c in self.containers:
846
            for o in get_content_splitted(self.list_objects(self.account, c)):
847
                self.delete_object(self.account, c, o)
848
            self.delete_container(self.account, c)
849

    
850
    def test_delete(self):
851
        r = self.delete_container(self.account, self.containers[0])
852
        self.assertEqual(r.status_code, 204)
853

    
854
    def test_delete_non_empty(self):
855
        r = self.delete_container(self.account, self.containers[1])
856
        self.assertNonEmpty(r)
857

    
858
    def test_delete_invalid(self):
859
        self.assertItemNotFound(self.delete_container(self.account, 'c3'))
860

    
861
class ObjectHead(BaseTestCase):
862
    pass
863

    
864
class ObjectGet(BaseTestCase):
865
    def setUp(self):
866
        BaseTestCase.setUp(self)
867
        self.account = 'test'
868
        self.containers = ['c1', 'c2']
869
        #create some containers
870
        for c in self.containers:
871
            self.create_container(self.account, c)
872
        
873
        #upload a file
874
        self.objects = []
875
        self.objects.append(self.upload_os_file(self.account,
876
                            self.containers[1],
877
                            './api/tests.py'))
878
        self.objects.append(self.upload_os_file(self.account,
879
                            self.containers[1],
880
                            'settings.py'))
881

    
882
    def tearDown(self):
883
        for c in self.containers:
884
            for o in get_content_splitted(self.list_objects(self.account, c)):
885
                self.delete_object(self.account, c, o)
886
            self.delete_container(self.account, c)
887

    
888
    def test_get(self):
889
        #perform get
890
        r = self.get_object(self.account,
891
                            self.containers[1],
892
                            self.objects[0]['name'],
893
                            self.objects[0]['meta'])
894
        #assert success
895
        self.assertEqual(r.status_code, 200)
896
        
897
        #assert content-type
898
        self.assertEqual(r['Content-Type'],
899
                         self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
900

    
901
    def test_get_invalid(self):
902
        r = self.get_object(self.account,
903
                            self.containers[0],
904
                            self.objects[0]['name'])
905
        self.assertItemNotFound(r)
906

    
907
    def test_get_partial(self):
908
        #perform get with range
909
        headers = {'HTTP_RANGE':'bytes=0-499'}
910
        r = self.get_object(self.account,
911
                        self.containers[1],
912
                        self.objects[0]['name'],
913
                        **headers)
914
        
915
        #assert successful partial content
916
        self.assertEqual(r.status_code, 206)
917
        
918
        #assert content-type
919
        self.assertEqual(r['Content-Type'],
920
                         self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
921
        
922
        #assert content length
923
        self.assertEqual(int(r['Content-Length']), 500)
924
        
925
        #assert content
926
        self.assertEqual(self.objects[0]['data'][:500], r.content)
927

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

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

    
971
    def test_get_range_not_satisfiable(self):
972
        #perform get with range
973
        offset = len(self.objects[0]['data']) + 1
974
        headers = {'HTTP_RANGE':'bytes=0-%s' %offset}
975
        r = self.get_object(self.account,
976
                        self.containers[1],
977
                        self.objects[0]['name'],
978
                        **headers)
979
        
980
        #assert Range Not Satisfiable
981
        self.assertEqual(r.status_code, 416)
982

    
983
    def test_multiple_range(self):
984
        #perform get with multiple range
985
        ranges = ['0-499', '-500', '1000-']
986
        headers = {'HTTP_RANGE' : 'bytes=%s' % ','.join(ranges)}
987
        r = self.get_object(self.account,
988
                        self.containers[1],
989
                        self.objects[0]['name'],
990
                        **headers)
991
        
992
        # assert partial content
993
        self.assertEqual(r.status_code, 206)
994
        
995
        # assert Content-Type of the reply will be multipart/byteranges
996
        self.assertTrue(r['Content-Type'])
997
        content_type_parts = r['Content-Type'].split()
998
        self.assertEqual(content_type_parts[0], ('multipart/byteranges;'))
999
        
1000
        boundary = '--%s' %content_type_parts[1].split('=')[-1:][0]
1001
        cparts = r.content.split(boundary)[1:-1]
1002
        
1003
        # assert content parts are exactly 2
1004
        self.assertEqual(len(cparts), len(ranges))
1005
        
1006
        # for each content part assert headers
1007
        i = 0
1008
        for cpart in cparts:
1009
            content = cpart.split('\r\n')
1010
            headers = content[1:3]
1011
            content_range = headers[0].split(': ')
1012
            self.assertEqual(content_range[0], 'Content-Range')
1013
            
1014
            r = ranges[i].split('-')
1015
            if not r[0] and not r[1]:
1016
                pass
1017
            elif not r[0]:
1018
                start = len(self.objects[0]['data']) - int(r[1])
1019
                end = len(self.objects[0]['data'])
1020
            elif not r[1]:
1021
                start = int(r[0])
1022
                end = len(self.objects[0]['data'])
1023
            else:
1024
                start = int(r[0])
1025
                end = int(r[1]) + 1
1026
            fdata = self.objects[0]['data'][start:end]
1027
            sdata = '\r\n'.join(content[4:-1])
1028
            self.assertEqual(len(fdata), len(sdata))
1029
            self.assertEquals(fdata, sdata)
1030
            i+=1
1031

    
1032
    def test_multiple_range_not_satisfiable(self):
1033
        #perform get with multiple range
1034
        out_of_range = len(self.objects[0]['data']) + 1
1035
        ranges = ['0-499', '-500', '%d-' %out_of_range]
1036
        headers = {'HTTP_RANGE' : 'bytes=%s' % ','.join(ranges)}
1037
        r = self.get_object(self.account,
1038
                        self.containers[1],
1039
                        self.objects[0]['name'],
1040
                        **headers)
1041
        
1042
        # assert partial content
1043
        self.assertEqual(r.status_code, 416)
1044

    
1045
    def test_get_with_if_match(self):
1046
        #perform get with If-Match
1047
        headers = {'HTTP_IF_MATCH':self.objects[0]['hash']}
1048
        r = self.get_object(self.account,
1049
                        self.containers[1],
1050
                        self.objects[0]['name'],
1051
                        **headers)
1052
        #assert get success
1053
        self.assertEqual(r.status_code, 200)
1054
        
1055
        #assert content-type
1056
        self.assertEqual(r['Content-Type'],
1057
                         self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
1058
        
1059
        #assert response content
1060
        self.assertEqual(self.objects[0]['data'].strip(), r.content.strip())
1061

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

    
1079
    def test_get_with_multiple_if_match(self):
1080
        #perform get with If-Match
1081
        etags = [i['hash'] for i in self.objects if i]
1082
        etags = ','.join('"%s"' % etag for etag in etags)
1083
        headers = {'HTTP_IF_MATCH':etags}
1084
        r = self.get_object(self.account,
1085
                        self.containers[1],
1086
                        self.objects[0]['name'],
1087
                        **headers)
1088
        #assert get success
1089
        self.assertEqual(r.status_code, 200)
1090
        
1091
        #assert content-type
1092
        self.assertEqual(r['Content-Type'],
1093
                         self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
1094
        
1095
        #assert content-type
1096
        self.assertEqual(r['Content-Type'],
1097
                         self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
1098
        
1099
        #assert response content
1100
        self.assertEqual(self.objects[0]['data'].strip(), r.content.strip())
1101

    
1102
    def test_if_match_precondition_failed(self):
1103
        #perform get with If-Match
1104
        headers = {'HTTP_IF_MATCH':'123'}
1105
        r = self.get_object(self.account,
1106
                        self.containers[1],
1107
                        self.objects[0]['name'],
1108
                        **headers)
1109
        #assert precondition failed 
1110
        self.assertEqual(r.status_code, 412)
1111

    
1112
    def test_if_none_match(self):
1113
        #perform get with If-None-Match
1114
        headers = {'HTTP_IF_NONE_MATCH':'123'}
1115
        r = self.get_object(self.account,
1116
                        self.containers[1],
1117
                        self.objects[0]['name'],
1118
                        **headers)
1119
        
1120
        #assert get success
1121
        self.assertEqual(r.status_code, 200)
1122
        
1123
        #assert content-type
1124
        self.assertEqual(r['Content-Type'],
1125
                         self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
1126

    
1127
    def test_if_none_match(self):
1128
        #perform get with If-None-Match *
1129
        headers = {'HTTP_IF_NONE_MATCH':'*'}
1130
        r = self.get_object(self.account,
1131
                        self.containers[1],
1132
                        self.objects[0]['name'],
1133
                        **headers)
1134
        
1135
        #assert get success
1136
        self.assertEqual(r.status_code, 304)
1137

    
1138
    def test_if_none_match_not_modified(self):
1139
        #perform get with If-None-Match
1140
        headers = {'HTTP_IF_NONE_MATCH':'%s' %self.objects[0]['hash']}
1141
        r = self.get_object(self.account,
1142
                        self.containers[1],
1143
                        self.objects[0]['name'],
1144
                        **headers)
1145
        
1146
        #assert not modified
1147
        self.assertEqual(r.status_code, 304)
1148
        self.assertEqual(r['ETag'], self.objects[0]['hash'])
1149

    
1150
    def test_if_modified_since(self):
1151
        t = datetime.datetime.utcnow()
1152
        t2 = t - datetime.timedelta(minutes=10)
1153
        
1154
        #modify the object
1155
        self.upload_object(self.account,
1156
                           self.containers[1],
1157
                           self.objects[0]['name'],
1158
                           self.objects[0]['data'][:200])
1159
        
1160
        for f in DATE_FORMATS:
1161
            past = t2.strftime(f)
1162
            
1163
            headers = {'HTTP_IF_MODIFIED_SINCE':'%s' %past}
1164
            r = self.get_object(self.account,
1165
                        self.containers[1],
1166
                        self.objects[0]['name'],
1167
                        **headers)
1168
            
1169
            #assert get success
1170
            self.assertEqual(r.status_code, 200)
1171
            
1172
            #assert content-type
1173
            self.assertEqual(r['Content-Type'],
1174
                             self.objects[0]['meta']['HTTP_CONTENT_TYPE'])   
1175

    
1176
    def test_if_modified_since_invalid_date(self):
1177
        headers = {'HTTP_IF_MODIFIED_SINCE':''}
1178
        r = self.get_object(self.account,
1179
                    self.containers[1],
1180
                    self.objects[0]['name'],
1181
                    **headers)
1182
        
1183
        #assert get success
1184
        self.assertEqual(r.status_code, 200)
1185
        
1186
        #assert content-type
1187
        self.assertEqual(r['Content-Type'],
1188
                         self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
1189

    
1190
    def test_if_not_modified_since(self):
1191
        now = datetime.datetime.utcnow()
1192
        since = now + datetime.timedelta(1)
1193
        
1194
        for f in DATE_FORMATS:
1195
            headers = {'HTTP_IF_MODIFIED_SINCE':'%s' %since.strftime(f)}
1196
            r = self.get_object(self.account,
1197
                                self.containers[1],
1198
                                self.objects[0]['name'],
1199
                                **headers)
1200
            
1201
            #assert not modified
1202
            self.assertEqual(r.status_code, 304)
1203

    
1204
    def test_if_unmodified_since(self):
1205
        now = datetime.datetime.utcnow()
1206
        since = now + datetime.timedelta(1)
1207
        
1208
        for f in DATE_FORMATS:
1209
            headers = {'HTTP_IF_UNMODIFIED_SINCE':'%s' %since.strftime(f)}
1210
            r = self.get_object(self.account,
1211
                                self.containers[1],
1212
                                self.objects[0]['name'],
1213
                                **headers)
1214
            #assert success
1215
            self.assertEqual(r.status_code, 200)
1216
            self.assertEqual(self.objects[0]['data'], r.content)
1217
            
1218
            #assert content-type
1219
            self.assertEqual(r['Content-Type'],
1220
                             self.objects[0]['meta']['HTTP_CONTENT_TYPE'])
1221

    
1222
    def test_if_unmodified_since_precondition_failed(self):
1223
        t = datetime.datetime.utcnow()
1224
        t2 = t - datetime.timedelta(minutes=10)
1225
        
1226
        #modify the object
1227
        self.upload_object(self.account,
1228
                           self.containers[1],
1229
                           self.objects[0]['name'],
1230
                           self.objects[0]['data'][:200])
1231
        
1232
        for f in DATE_FORMATS:
1233
            past = t2.strftime(f)
1234
            
1235
            headers = {'HTTP_IF_UNMODIFIED_SINCE':'%s' %past}
1236
            r = self.get_object(self.account,
1237
                        self.containers[1],
1238
                        self.objects[0]['name'],
1239
                        **headers)
1240
            #assert get success
1241
            self.assertEqual(r.status_code, 412)
1242

    
1243
    def test_hashes(self):
1244
        l = 8388609
1245
        fname = 'largefile'
1246
        o = self.upload_random_data(self.account,
1247
                                self.containers[1],
1248
                                fname,
1249
                                l)
1250
        if o:
1251
            r = self.get_object(self.account,
1252
                                self.containers[1],
1253
                                fname,
1254
                                'json')
1255
            body = json.loads(r.content)
1256
            hashes = body['hashes']
1257
            block_size = body['block_size']
1258
            block_hash = body['block_hash']
1259
            block_num = l/block_size == 0 and l/block_size or l/block_size + 1
1260
            self.assertTrue(len(hashes), block_num)
1261
            i = 0
1262
            for h in hashes:
1263
                start = i * block_size
1264
                end = (i + 1) * block_size
1265
                hash = compute_block_hash(o['data'][start:end], block_hash)
1266
                self.assertEqual(h, hash)
1267
                i += 1
1268

    
1269
class ObjectPut(BaseTestCase):
1270
    def setUp(self):
1271
        BaseTestCase.setUp(self)
1272
        self.account = 'test'
1273
        self.container = 'c1'
1274
        self.create_container(self.account, self.container)
1275
        
1276
        self.src = os.path.join('.', 'api', 'tests.py')
1277
        self.dest = os.path.join('.', 'api', 'chunked_update_test_file')
1278
        create_chunked_update_test_file(self.src, self.dest)
1279

    
1280
    def tearDown(self):
1281
        r = self.list_objects(self.account, self.container)
1282
        for o in get_content_splitted(r):
1283
            self.delete_object(self.account, self.container, o)
1284
        self.delete_container(self.account, self.container)
1285
        
1286
        # delete test file
1287
        os.remove(self.dest)
1288

    
1289
    def test_upload(self):
1290
        filename = 'tests.py'
1291
        fullpath = os.path.join('.', 'api', filename) 
1292
        f = open(fullpath, 'r')
1293
        data = f.read()
1294
        hash = compute_md5_hash(data)
1295
        meta={'HTTP_ETAG':hash,
1296
              'HTTP_X_OBJECT_MANIFEST':123,
1297
              'HTTP_X_OBJECT_META_TEST':'test1'
1298
              }
1299
        type, enc = mimetypes.guess_type(fullpath)
1300
        meta['HTTP_CONTENT_TYPE'] = type and type or 'plain/text'
1301
        if enc:
1302
            meta['HTTP_CONTENT_ENCODING'] = enc
1303
        r = self.upload_object(self.account,
1304
                               self.container,
1305
                               filename,
1306
                               data,
1307
                               content_type=meta['HTTP_CONTENT_TYPE'],
1308
                               **meta)
1309
        self.assertEqual(r.status_code, 201)
1310
        r = self.get_object_meta(self.account, self.container, filename)
1311
        self.assertTrue(r['X-Object-Meta-Test'])
1312
        self.assertEqual(r['X-Object-Meta-Test'],
1313
                         meta['HTTP_X_OBJECT_META_TEST'])
1314
        
1315
        #assert uploaded content
1316
        r = self.get_object(self.account, self.container, filename)
1317
        self.assertEqual(os.path.getsize(fullpath), int(r['Content-Length']))
1318
        self.assertEqual(data.strip(), r.content.strip())
1319

    
1320
    def test_upload_unprocessable_entity(self):
1321
        filename = 'tests.py'
1322
        fullpath = os.path.join('.', 'api', filename) 
1323
        f = open(fullpath, 'r')
1324
        data = f.read()
1325
        meta={'HTTP_ETAG':'123',
1326
              'HTTP_X_OBJECT_MANIFEST':123,
1327
              'HTTP_X_OBJECT_META_TEST':'test1'
1328
              }
1329
        type, enc = mimetypes.guess_type(fullpath)
1330
        meta['HTTP_CONTENT_TYPE'] = type and type or 'plain/text'
1331
        if enc:
1332
            meta['HTTP_CONTENT_ENCODING'] = enc
1333
        r = self.upload_object(self.account,
1334
                               self.container,
1335
                               filename,
1336
                               data,
1337
                               content_type = meta['HTTP_CONTENT_TYPE'],
1338
                               **meta)
1339
        self.assertEqual(r.status_code, 422)
1340

    
1341
    def test_chucked_update(self):
1342
        objname = os.path.split(self.src)[-1:][0]
1343
        f = open(self.dest, 'r')
1344
        data = f.read()
1345
        meta = {}
1346
        type, enc = mimetypes.guess_type(self.dest)
1347
        meta['HTTP_CONTENT_TYPE'] = type and type or 'plain/text'
1348
        if enc:
1349
            meta['HTTP_CONTENT_ENCODING'] = enc
1350
        meta.update({'HTTP_TRANSFER_ENCODING':'chunked'})
1351
        r = self.upload_object(self.account,
1352
                               self.container,
1353
                               objname,
1354
                               data,
1355
                               content_type = 'plain/text',
1356
                               **meta)
1357
        self.assertEqual(r.status_code, 201)
1358
        
1359
        r = self.get_object(self.account,
1360
                            self.container,
1361
                            objname)
1362
        uploaded_data = r.content
1363
        f = open(self.src, 'r')
1364
        actual_data = f.read()
1365
        self.assertEqual(actual_data, uploaded_data)
1366

    
1367
class ObjectCopy(BaseTestCase):
1368
    def setUp(self):
1369
        BaseTestCase.setUp(self)
1370
        self.account = 'test'
1371
        self.containers = ['c1', 'c2']
1372
        for c in self.containers:
1373
            self.create_container(self.account, c)
1374
        self.obj = self.upload_os_file(self.account,
1375
                            self.containers[0],
1376
                            './api/tests.py')
1377

    
1378
    def tearDown(self):
1379
        for c in self.containers:
1380
            for o in get_content_splitted(self.list_objects(self.account, c)):
1381
                self.delete_object(self.account, c, o)
1382
            self.delete_container(self.account, c)
1383

    
1384
    def test_copy(self):
1385
        with AssertInvariant(self.get_object_meta, self.account,
1386
                             self.containers[0], self.obj['name']):
1387
            #perform copy
1388
            meta = {'HTTP_X_OBJECT_META_TEST':'testcopy'}
1389
            src_path = os.path.join('/', self.containers[0], self.obj['name'])
1390
            r = self.copy_object(self.account,
1391
                             self.containers[0],
1392
                             'testcopy',
1393
                             src_path,
1394
                             **meta)
1395
            #assert copy success
1396
            self.assertEqual(r.status_code, 201)
1397
            
1398
            #assert access the new object
1399
            r = self.get_object_meta(self.account,
1400
                                     self.containers[0],
1401
                                     'testcopy')
1402
            self.assertTrue(r['X-Object-Meta-Test'])
1403
            self.assertTrue(r['X-Object-Meta-Test'], 'testcopy')
1404
            
1405
            #assert etag is the same
1406
            self.assertEqual(r['ETag'], self.obj['hash'])
1407
            
1408
            #assert src object still exists
1409
            r = self.get_object_meta(self.account, self.containers[0],
1410
                                     self.obj['name'])
1411
            self.assertEqual(r.status_code, 204)
1412

    
1413
    def test_copy_from_different_container(self):
1414
        with AssertInvariant(self.get_object_meta,
1415
                             self.account,
1416
                             self.containers[0],
1417
                             self.obj['name']):
1418
            meta = {'HTTP_X_OBJECT_META_TEST':'testcopy'}
1419
            src_path = os.path.join('/', self.containers[0], self.obj['name'])
1420
            r = self.copy_object(self.account,
1421
                             self.containers[1],
1422
                             'testcopy',
1423
                             src_path,
1424
                             **meta)
1425
            self.assertEqual(r.status_code, 201)
1426
            
1427
            # assert updated metadata
1428
            r = self.get_object_meta(self.account,
1429
                                     self.containers[1],
1430
                                     'testcopy')
1431
            self.assertTrue(r['X-Object-Meta-Test'])
1432
            self.assertTrue(r['X-Object-Meta-Test'], 'testcopy')
1433
            
1434
            #assert src object still exists
1435
            r = self.get_object_meta(self.account, self.containers[0],
1436
                                     self.obj['name'])
1437
            self.assertEqual(r.status_code, 204)
1438

    
1439
    def test_copy_invalid(self):
1440
        #copy from invalid object
1441
        meta = {'HTTP_X_OBJECT_META_TEST':'testcopy'}
1442
        r = self.copy_object(self.account,
1443
                         self.containers[1],
1444
                         'testcopy',
1445
                         os.path.join('/', self.containers[0], 'test.py'),
1446
                         **meta)
1447
        self.assertItemNotFound(r)
1448
        
1449
        #copy from invalid container
1450
        meta = {'HTTP_X_OBJECT_META_TEST':'testcopy'}
1451
        src_path = os.path.join('/', self.containers[1], self.obj['name'])
1452
        r = self.copy_object(self.account,
1453
                         self.containers[1],
1454
                         'testcopy',
1455
                         src_path,
1456
                         **meta)
1457
        self.assertItemNotFound(r)
1458

    
1459
class ObjectMove(ObjectCopy):
1460
    def test_move(self):
1461
        #perform move
1462
        meta = {'HTTP_X_OBJECT_META_TEST':'testcopy'}
1463
        src_path = os.path.join('/', self.containers[0], self.obj['name'])
1464
        r = self.move_object(self.account,
1465
                         self.containers[0],
1466
                         'testcopy',
1467
                         src_path,
1468
                         **meta)
1469
        #assert successful move
1470
        self.assertEqual(r.status_code, 201)
1471
        
1472
        #assert updated metadata
1473
        r = self.get_object_meta(self.account,
1474
                                 self.containers[0],
1475
                                 'testcopy')
1476
        self.assertTrue(r['X-Object-Meta-Test'])
1477
        self.assertTrue(r['X-Object-Meta-Test'], 'testcopy')
1478
        
1479
        #assert src object no more exists
1480
        r = self.get_object_meta(self.account, self.containers[0],
1481
                                 self.obj['name'])
1482
        self.assertItemNotFound(r)
1483

    
1484
class ObjectPost(BaseTestCase):
1485
    def setUp(self):
1486
        BaseTestCase.setUp(self)
1487
        self.account = 'test'
1488
        self.containers = ['c1', 'c2']
1489
        for c in self.containers:
1490
            self.create_container(self.account, c)
1491
        self.obj = self.upload_os_file(self.account,
1492
                                       self.containers[0],
1493
                                       './api/tests.py')
1494

    
1495
    def tearDown(self):
1496
        for c in self.containers:
1497
            for o in get_content_splitted(self.list_objects(self.account, c)):
1498
                self.delete_object(self.account, c, o)
1499
            self.delete_container(self.account, c)
1500

    
1501
    def test_update_meta(self):
1502
        #perform update metadata
1503
        more = {'HTTP_X_OBJECT_META_FOO':'foo',
1504
                'HTTP_X_OBJECT_META_BAR':'bar'}
1505
        r = self.update_object(self.account,
1506
                                self.containers[0],
1507
                                self.obj['name'],
1508
                                **more)
1509
        #assert request accepted
1510
        self.assertEqual(r.status_code, 202)
1511
        
1512
        #assert old metadata are still there
1513
        r = self.get_object_meta(self.account, self.containers[0],
1514
                                 self.obj['name'])
1515
        self.assertTrue('X-Object-Meta-Test' not in r.items())
1516
        
1517
        #assert new metadata have been updated
1518
        for k,v in more.items():
1519
            key = '-'.join(elem.capitalize() for elem in k.split('_')[1:])
1520
            self.assertTrue(r[key])
1521
            self.assertTrue(r[key], v)
1522

    
1523
    def test_update_object(self,
1524
                           first_byte_pos=0,
1525
                           last_byte_pos=499,
1526
                           instance_length = True,
1527
                           content_length = 500):
1528
        l = len(self.obj['data'])
1529
        if instance_length:
1530
            range = 'bytes %d-%d/%d' %(first_byte_pos,
1531
                                       last_byte_pos,
1532
                                       l)
1533
        else:
1534
            range = 'bytes %d-%d/*' %(first_byte_pos,
1535
                                       last_byte_pos)
1536
        partial = last_byte_pos - first_byte_pos + 1
1537
        data = get_random_data(partial)
1538
        more = {'HTTP_CONTENT_RANGE':range}
1539
        if content_length:
1540
            more.update({'CONTENT_LENGTH':'%s' % content_length})
1541
        
1542
        r = self.update_object(self.account,
1543
                                self.containers[0],
1544
                                self.obj['name'],
1545
                                data,
1546
                                'application/octet-stream',
1547
                                **more)
1548
        
1549
        if partial < 0 or (instance_length and l <= last_byte_pos):
1550
            self.assertEqual(r.status_code, 202)    
1551
        elif content_length and content_length != partial:
1552
            self.assertEqual(r.status_code, 400)
1553
        else:
1554
            self.assertEqual(r.status_code, 204)
1555
            
1556
            #check modified object
1557
            r = self.get_object(self.account,
1558
                            self.containers[0],
1559
                            self.obj['name'])
1560
            self.assertEqual(r.content[0:partial], data)
1561
            self.assertEqual(r.content[partial:l], self.obj['data'][partial:l])
1562

    
1563
    def test_update_object_no_content_length(self):
1564
        self.test_update_object(content_length = None)
1565

    
1566
    def test_update_object_invalid_content_length(self):
1567
        with AssertContentInvariant(self.get_object, self.account, self.containers[0],
1568
                            self.obj['name']):
1569
            self.test_update_object(content_length = 1000)
1570

    
1571
    def test_update_object_with_unknown_instance_length(self):
1572
        self.test_update_object(instance_length = False)
1573

    
1574
    def test_update_object_invalid_range(self):
1575
        with AssertContentInvariant(self.get_object, self.account, self.containers[0],
1576
                            self.obj['name']):
1577
            self.test_update_object(499, 0, True)
1578
    
1579
    def test_update_object_invalid_range_and_length(self):
1580
        with AssertContentInvariant(self.get_object, self.account, self.containers[0],
1581
                            self.obj['name']):
1582
            self.test_update_object(499, 0, True, -1)
1583
    
1584
    def test_update_object_invalid_range_with_no_content_length(self):
1585
        with AssertContentInvariant(self.get_object, self.account, self.containers[0],
1586
                            self.obj['name']):
1587
            self.test_update_object(499, 0, True, content_length = None)
1588
    
1589
    def test_update_object_out_of_limits(self):    
1590
        with AssertContentInvariant(self.get_object, self.account, self.containers[0],
1591
                            self.obj['name']):
1592
            l = len(self.obj['data'])
1593
            self.test_update_object(0, l+1, True)
1594

    
1595
    def test_append(self):
1596
        data = get_random_data(500)
1597
        more = {'CONTENT_LENGTH':'500',
1598
                'HTTP_CONTENT_RANGE':'bytes */*'}
1599
        
1600
        r = self.update_object(self.account,
1601
                                self.containers[0],
1602
                                self.obj['name'],
1603
                                data,
1604
                                'application/octet-stream',
1605
                                **more)
1606
        
1607
        self.assertEqual(r.status_code, 204)
1608
        
1609
        r = self.get_object(self.account,
1610
                                self.containers[0],
1611
                                self.obj['name'])
1612
        self.assertEqual(len(r.content), len(self.obj['data']) + 500)
1613

    
1614
    #def test_update_with_chunked_transfer(self):
1615
    #    linenum = 5
1616
    #    data = create_random_chunked_data(linenum)
1617
    #    print data
1618
    #    first_byte_pos=0,
1619
    #    last_byte_pos=499,
1620
    #    instance_length = True,
1621
    #    content_length = 500
1622
    #    l = len(self.obj['data'])
1623
    #    meta = {'HTTP_TRANSFER_ENCODING':'chunked',
1624
    #            'HTTP_CONTENT_RANGE':'bytes 0-499/%d' %l}
1625
    #    r = self.update_object(self.account,
1626
    #                            self.containers[0],
1627
    #                            self.obj['name'],
1628
    #                            data,
1629
    #                            'application/octet-stream',
1630
    #                            **meta)
1631
    #    print r.request, r.status_code, r
1632

    
1633
class ObjectDelete(BaseTestCase):
1634
    def setUp(self):
1635
        BaseTestCase.setUp(self)
1636
        self.account = 'test'
1637
        self.containers = ['c1', 'c2']
1638
        for c in self.containers:
1639
            self.create_container(self.account, c)
1640
        self.obj = self.upload_os_file(self.account,
1641
                            self.containers[0],
1642
                            './api/tests.py')
1643

    
1644
    def tearDown(self):
1645
        for c in self.containers:
1646
            for o in get_content_splitted(self.list_objects(self.account, c)):
1647
                self.delete_object(self.account, c, o)
1648
            self.delete_container(self.account, c)
1649

    
1650
    def test_delete(self):
1651
        #perform delete object
1652
        r = self.delete_object(self.account, self.containers[0],
1653
                               self.obj['name'])
1654
        
1655
        #assert success
1656
        self.assertEqual(r.status_code, 204)
1657

    
1658
    def test_delete_invalid(self):
1659
        #perform delete object
1660
        r = self.delete_object(self.account, self.containers[1],
1661
                               self.obj['name'])
1662
        
1663
        #assert failure
1664
        self.assertItemNotFound(r)
1665

    
1666
class AssertInvariant(object):
1667
    def __init__(self, callable, *args, **kwargs):
1668
        self.callable = callable
1669
        self.args = args
1670
        self.kwargs = kwargs
1671

    
1672
    def __enter__(self):
1673
        self.items = self.callable(*self.args, **self.kwargs).items()
1674
        return self.items
1675

    
1676
    def __exit__(self, type, value, tb):
1677
        items = self.callable(*self.args, **self.kwargs).items()
1678
        assert self.items == items
1679

    
1680
class AssertContentInvariant(object):
1681
    def __init__(self, callable, *args, **kwargs):
1682
        self.callable = callable
1683
        self.args = args
1684
        self.kwargs = kwargs
1685

    
1686
    def __enter__(self):
1687
        self.content = self.callable(*self.args, **self.kwargs).content
1688
        return self.content
1689

    
1690
    def __exit__(self, type, value, tb):
1691
        content = self.callable(*self.args, **self.kwargs).content
1692
        assert self.content == content
1693

    
1694
def get_content_splitted(response):
1695
    if response:
1696
        return response.content.split('\n')
1697

    
1698
def compute_md5_hash(data):
1699
    md5 = hashlib.md5()
1700
    offset = 0
1701
    md5.update(data)
1702
    return md5.hexdigest().lower()
1703

    
1704
def compute_block_hash(data, algorithm):
1705
    h = hashlib.new(algorithm)
1706
    h.update(data.rstrip('\x00'))
1707
    return h.hexdigest()
1708

    
1709
def create_chunked_update_test_file(src, dest):
1710
    fr = open(src, 'r')
1711
    fw = open(dest, 'w')
1712
    data = fr.readline()
1713
    while data:
1714
        fw.write(hex(len(data)))
1715
        fw.write('\r\n')
1716
        fw.write(data)
1717
        data = fr.readline()
1718
    fw.write(hex(0))
1719
    fw.write('\r\n')
1720

    
1721
def create_random_chunked_data(rows):
1722
    i = 0
1723
    out = []
1724
    while i < rows:
1725
        data = get_random_data(random.randint(1, 100))
1726
        out.append(hex(len(data)))
1727
        out.append(data)
1728
        i+=1
1729
    out.append(hex(0))
1730
    out.append('\r\n')
1731
    return '\r\n'.join(out)
1732

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

    
1737
o_names = ['kate.jpg',
1738
           'kate_beckinsale.jpg',
1739
           'How To Win Friends And Influence People.pdf',
1740
           'moms_birthday.jpg',
1741
           'poodle_strut.mov',
1742
           'Disturbed - Down With The Sickness.mp3',
1743
           'army_of_darkness.avi',
1744
           'the_mad.avi',
1745
           'photos/animals/dogs/poodle.jpg',
1746
           'photos/animals/dogs/terrier.jpg',
1747
           'photos/animals/cats/persian.jpg',
1748
           'photos/animals/cats/siamese.jpg',
1749
           'photos/plants/fern.jpg',
1750
           'photos/plants/rose.jpg',
1751
           'photos/me.jpg']