Revision b09c9aaa pithos/api/tests.py

b/pithos/api/tests.py
1
from django.test.client import Client
2
from django.test import TestCase
3
from django.utils import simplejson as json
4
from xml.dom import minidom
5
import types
6
import hashlib
7
import os
8

  
9

  
10
class AaiClient(Client):
11
    def request(self, **request):
12
        request['HTTP_X_AUTH_TOKEN'] = '46e427d657b20defe352804f0eb6f8a2'
13
        return super(AaiClient, self).request(**request)
14

  
15

  
16
class BaseTestCase(TestCase):
17
    #TODO unauthorized request
18
    def setUp(self):
19
        self.client = AaiClient()
20
        self.headers = {
21
            'account':(
22
                'X-Account-Container-Count',
23
                'X-Account-Bytes-Used',
24
                'Last-Modified',),
25
            'container':(
26
                'X-Container-Object-Count',
27
                'X-Container-Bytes-Used',
28
                'Last-Modified',),
29
            'object':(
30
                'ETag',
31
                'Content-Length',
32
                'Content-Type',
33
                'Content-Encoding',
34
                'Last-Modified',)}
35
        self.contentTypes = {'xml':'application/xml',
36
                             'json':'application/json',
37
                             '':'text/plain'}
38
        self.extended = {
39
            'container':(
40
                'name',
41
                'count',
42
                'bytes',
43
                'last_modified'),
44
            'object':(
45
                'name',
46
                'hash',
47
                'bytes',
48
                'content_type',
49
                'content_encoding',
50
                'last_modified',)}
51
        self.return_codes = (400, 401, 404, 503,)
52
    
53
    def assertFault(self, response, status_code, name):
54
        self.assertEqual(response.status_code, status_code)
55
    
56
    def assertBadRequest(self, response):
57
        self.assertFault(response, 400, 'badRequest')
58
    
59
    def assertItemNotFound(self, response):
60
        self.assertFault(response, 404, 'itemNotFound')
61

  
62
    def assertUnauthorized(self, response):
63
        self.assertFault(response, 401, 'unauthorized')
64

  
65
    def assertServiceUnavailable(self, response):
66
        self.assertFault(response, 503, 'serviceUnavailable')
67

  
68
    def assertNonEmpty(self, response):
69
        self.assertFault(response, 409, 'nonEmpty')
70

  
71
    def assert_status(self, response, codes):
72
        l = [elem for elem in self.return_codes]
73
        if type(codes) == types.ListType:
74
            l.extend(codes)
75
        else:
76
            l.append(codes)
77
        self.assertTrue(response.status_code in l)
78

  
79
    def get_account_meta(self, account):
80
        path = '/v1/%s' % account
81
        response = self.client.head(path)
82
        self.assert_status(response, 204)
83
        self.assert_headers(response, 'account')
84
        return response
85

  
86
    def list_containers(self, account, limit=10000, marker='', format=''):
87
        params = locals()
88
        params.pop('self')
89
        params.pop('account')
90
        path = '/v1/%s' % account
91
        response = self.client.get(path, params)
92
        self.assert_status(response, [200, 204])
93
        response.content = response.content.strip()
94
        if format:
95
            self.assert_extended(response, format, 'container', limit)
96
        else:
97
            names = get_content_splitted(response)
98
            self.assertTrue(len(names) <= limit)
99
        return response
100

  
101
    def update_account_meta(self, account, **metadata):
102
        path = '/v1/%s' % account
103
        response = self.client.post(path, **metadata)
104
        response.content = response.content.strip()
105
        self.assert_status(response, 202)
106
        return response
107

  
108
    def get_container_meta(self, account, container):
109
        params = locals()
110
        params.pop('self')
111
        params.pop('account')
112
        params.pop('container')
113
        path = '/v1/%s/%s' %(account, container)
114
        response = self.client.head(path, params)
115
        response.content = response.content.strip()
116
        self.assert_status(response, 204)
117
        if response.status_code == 204:
118
            self.assert_headers(response, 'container')
119
        return response
120

  
121
    def list_objects(self, account, container, limit=10000, marker='', prefix='', format='', path='', delimiter=''):
122
        params = locals()
123
        params.pop('self')
124
        params.pop('account')
125
        params.pop('container')
126
        path = '/v1/%s/%s' % (account, container)
127
        response = self.client.get(path, params)
128
        response.content = response.content.strip()
129
        if format:
130
            self.assert_extended(response, format, 'object', limit)
131
        self.assert_status(response, [200, 204])
132
        return response
133

  
134
    def create_container(self, account, name, **meta):
135
        path = '/v1/%s/%s' %(account, name)
136
        response = self.client.put(path, **meta)
137
        response.content = response.content.strip()
138
        self.assert_status(response, [201, 202])
139
        return response
140

  
141
    def update_container_meta(self, account, name, **meta):
142
        path = '/v1/%s/%s' %(account, name)
143
        response = self.client.post(path, data={}, content_type='text/xml', follow=False, **meta)
144
        response.content = response.content.strip()
145
        self.assert_status(response, 202)
146
        return response
147

  
148
    def delete_container(self, account, container):
149
        path = '/v1/%s/%s' %(account, container)
150
        response = self.client.delete(path)
151
        response.content = response.content.strip()
152
        self.assert_status(response, [204, 409])
153
        return response
154

  
155
    def get_object_meta(self, account, container, name):
156
        path = '/v1/%s/%s/%s' %(account, container, name)
157
        response = self.client.head(path)
158
        response.content = response.content.strip()
159
        self.assert_status(response, 204)
160
        return response
161

  
162
    def get_object(self, account, container, name, **headers):
163
        path = '/v1/%s/%s/%s' %(account, container, name)
164
        response = self.client.get(path, **headers)
165
        response.content = response.content.strip()
166
        self.assert_status(response, [200, 206, 304, 412, 416])
167
        if response.status_code in [200, 206]:
168
            self.assert_headers(response, 'object')
169
        return response
170

  
171
    def upload_object(self, account, container, name, data, content_type='application/json', **headers):
172
        path = '/v1/%s/%s/%s' %(account, container, name)
173
        response = self.client.put(path, data, content_type, **headers)
174
        response.content = response.content.strip()
175
        self.assert_status(response, [201, 411, 422])
176
        if response.status_code == 201:
177
            self.assertTrue(response['Etag'])
178
        return response
179

  
180
    def copy_object(self, account, container, name, src, **headers):
181
        path = '/v1/%s/%s/%s' %(account, container, name)
182
        headers['X-Copy-From'] = src
183
        response = self.client.put(path, **headers)
184
        response.content = response.content.strip()
185
        self.assert_status(response, 201)
186
        return response
187

  
188
    def move_object(self, account, container, name, **headers):
189
        path = '/v1/%s/%s/%s' % account, container, name
190
        response = self.client.move(path, **headers)
191
        response.content = response.content.strip()
192
        self.assert_status(response, 201)
193
        return response
194

  
195
    def update_object_meta(self, account, container, name, **headers):
196
        path = '/v1/%s/%s/%s' %(account, container, name)
197
        response = self.client.post(path, **headers)
198
        response.content = response.content.strip()
199
        self.assert_status(response, 202)
200
        return response
201

  
202
    def delete_object(self, account, container, name):
203
        path = '/v1/%s/%s/%s' %(account, container, name)
204
        response = self.client.delete(path)
205
        response.content = response.content.strip()
206
        self.assert_status(response, 204)
207
        return response
208

  
209
    def assert_headers(self, response, type):
210
        for item in self.headers[type]:
211
            self.assertTrue(response[item])
212

  
213
    def assert_extended(self, response, format, type, size):
214
        self.assertEqual(response['Content-Type'].find(self.contentTypes[format]), 0)
215
        if format == 'xml':
216
            self.assert_xml(response, type, size)
217
        elif format == 'json':
218
            self.assert_json(response, type, size)
219

  
220
    def assert_json(self, response, type, size):
221
        convert = lambda s: s.lower()
222
        info = [convert(elem) for elem in self.extended[type]]
223
        data = json.loads(response.content)
224
        self.assertTrue(len(data) <= size)
225
        for item in info:
226
            for i in data:
227
                if 'subdir' in i.keys():
228
                    continue
229
                self.assertTrue(item in i.keys())
230

  
231
    def assert_xml(self, response, type, size):
232
        convert = lambda s: s.lower()
233
        info = [convert(elem) for elem in self.extended[type]]
234
        xml = minidom.parseString(response.content)
235
        for item in info:
236
            nodes = xml.getElementsByTagName(item)
237
            self.assertTrue(nodes)
238
            self.assertTrue(len(nodes) <= size)
239

  
240
class ListContainers(BaseTestCase):
241
    def setUp(self):
242
        BaseTestCase.setUp(self)
243
        self.account = 'test'
244
        #create some containers
245
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
246
        for item in self.containers:
247
            self.create_container(self.account, item)
248

  
249
    def tearDown(self):
250
        for c in get_content_splitted(self.list_containers(self.account)):
251
            response = self.delete_container(self.account, c)
252

  
253
    def test_list(self):
254
        #list containers
255
        response = self.list_containers(self.account)
256
        containers = get_content_splitted(response)
257
        self.assertEquals(self.containers, containers)
258

  
259
    #def test_list_204(self):
260
    #    response = self.list_containers('non-existing-account')
261
    #    self.assertEqual(response.status_code, 204)
262

  
263
    def test_list_with_limit(self):
264
        limit = 2
265
        response = self.list_containers(self.account, limit=limit)
266
        containers = get_content_splitted(response)
267
        self.assertEquals(len(containers), limit)
268
        self.assertEquals(self.containers[:2], containers)
269

  
270
    def test_list_with_marker(self):
271
        limit = 2
272
        marker = 'bananas'
273
        response = self.list_containers(self.account, limit=limit, marker=marker)
274
        containers =  get_content_splitted(response)
275
        i = self.containers.index(marker) + 1
276
        self.assertEquals(self.containers[i:(i+limit)], containers)
277
        
278
        marker = 'oranges'
279
        response = self.list_containers(self.account, limit=limit, marker=marker)
280
        containers = get_content_splitted(response)
281
        i = self.containers.index(marker) + 1
282
        self.assertEquals(self.containers[i:(i+limit)], containers)
283

  
284
    def test_extended_list(self):
285
        self.list_containers(self.account, limit=3, format='xml')
286
        self.list_containers(self.account, limit=3, format='json')
287

  
288
    def test_list_json_with_marker(self):
289
        limit = 2
290
        marker = 'bananas'
291
        response = self.list_containers(self.account, limit=limit, marker=marker, format='json')
292
        containers = json.loads(response.content)
293
        self.assertEqual(containers[0]['name'], 'kiwis')
294
        self.assertEqual(containers[1]['name'], 'oranges')
295

  
296
    def test_list_xml_with_marker(self):
297
        limit = 2
298
        marker = 'oranges'
299
        response = self.list_containers(self.account, limit=limit, marker=marker, format='xml')
300
        xml = minidom.parseString(response.content)
301
        nodes = xml.getElementsByTagName('name')
302
        self.assertEqual(len(nodes), 1)
303
        self.assertEqual(nodes[0].childNodes[0].data, 'pears')
304

  
305
class AccountMetadata(BaseTestCase):
306
    def setUp(self):
307
        BaseTestCase.setUp(self)
308
        self.account = 'test'
309
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
310
        for item in self.containers:
311
            self.create_container(self.account, item)
312

  
313
    def tearDown(self):
314
        for c in  get_content_splitted(self.list_containers(self.account)):
315
            self.delete_container(self.account, c)
316

  
317
    def test_get_account_meta(self):
318
        response = self.get_account_meta(self.account)
319
        r2 = self.list_containers(self.account)
320
        containers =  get_content_splitted(r2)
321
        self.assertEqual(response['X-Account-Container-Count'], str(len(containers)))
322
        size = 0
323
        for c in containers:
324
            r = self.get_container_meta(self.account, c)
325
            size = size + int(r['X-Container-Bytes-Used'])
326
        self.assertEqual(response['X-Account-Bytes-Used'], str(size))
327

  
328
    #def test_get_account_401(self):
329
    #    response = self.get_account_meta('non-existing-account')
330
    #    print response
331
    #    self.assertEqual(response.status_code, 401)
332

  
333
    def test_update_meta(self):
334
        meta = {'HTTP_X_ACCOUNT_META_TEST':'test', 'HTTP_X_ACCOUNT_META_TOST':'tost'}
335
        response = self.update_account_meta(self.account, **meta)
336
        response = self.get_account_meta(self.account)
337
        for k,v in meta.items():
338
            key = '-'.join(elem.capitalize() for elem in k.split('_')[1:])
339
            self.assertTrue(response[key])
340
            self.assertEqual(response[key], v)
341

  
342
    #def test_invalid_account_update_meta(self):
343
    #    with AssertInvariant(self.get_account_meta, self.account):
344
    #        meta = {'HTTP_X_ACCOUNT_META_TEST':'test', 'HTTP_X_ACCOUNT_META_TOST':'tost'}
345
    #        response = self.update_account_meta('non-existing-account', **meta)
346

  
347
class ListObjects(BaseTestCase):
348
    def setUp(self):
349
        BaseTestCase.setUp(self)
350
        self.account = 'test'
351
        self.container = ['pears', 'apples']
352
        self.create_container(self.account, self.container[0])
353
        self.l = [
354
            {'name':'kate.jpg',
355
             'meta':{'HTTP_ETAG':'99914b932bd37a50b983c5e7c90ae93b',
356
                     'HTTP_CONTENT-LENGTH':23,
357
                     'HTTP_CONTENT_TYPE':'image/jpeg',
358
                     'HTTP_CONTENT_ENCODING':'utf8',
359
                     'HTTP_X_OBJECT_MANIFEST':123,
360
                     'HTTP_X_OBJECT_META_TEST':'test1'}},
361
            {'name':'kate_beckinsale.jpg',
362
             'meta':{'HTTP_ETAG':'99914b932bd37a50b983c5e7c90ae93b',
363
                     'HTTP_CONTENT-LENGTH':23,
364
                     'HTTP_CONTENT_TYPE':'image/jpeg',
365
                     'HTTP_CONTENT_ENCODING':'utf8',
366
                     'HTTP_X_OBJECT_MANIFEST':123,
367
                     'HTTP_X_OBJECT_META_TEST':'test1'}},
368
            {'name':'How To Win Friends And Influence People.pdf',
369
             'meta':{'HTTP_ETAG':'99914b932bd37a50b983c5e7c90ae93b',
370
                     'HTTP_CONTENT-LENGTH':28,
371
                     'HTTP_CONTENT_TYPE':'application/pdf',
372
                     'HTTP_CONTENT_ENCODING':'utf8',
373
                     'HTTP_X_OBJECT_MANIFEST':123,
374
                     'HTTP_X_OBJECT_META_TEST':'test2'}},
375
            {'name':'moms_birthday.jpg',
376
             'meta':{'HTTP_ETAG':'99914b932bd37a50b983c5e7c90ae93b',
377
                     'HTTP_CONTENT-LENGTH':255, 'HTTP_CONTENT_TYPE':'image/jpeg',
378
                     'HTTP_CONTENT_ENCODING':'utf8',
379
                     'HTTP_X_OBJECT_MANIFEST':123,
380
                     'HTTP_X_OBJECT_META_TEST':'test3'}},
381
            {'name':'poodle_strut.mov',
382
             'meta':{'HTTP_ETAG':'99914b932bd37a50b983c5e7c90ae93b',
383
                     'HTTP_CONTENT-LENGTH':23,
384
                     'HTTP_CONTENT_TYPE':'image/jpeg',
385
                     'HTTP_CONTENT_ENCODING':'utf8',
386
                     'HTTP_X_OBJECT_MANIFEST':123,
387
                     'HTTP_X_OBJECT_META_TEST':'test4'}},
388
            {'name':'Disturbed - Down With The Sickness.mp3',
389
             'meta':{'HTTP_ETAG':'99914b932bd37a50b983c5e7c90ae93b',
390
                     'HTTP_CONTENT-LENGTH':23,
391
                     'HTTP_CONTENT_TYPE':'image/jpeg',
392
                     'HTTP_CONTENT_ENCODING':'utf8',
393
                     'HTTP_X_OBJECT_MANIFEST':123,
394
                     'HTTP_X_OBJECT_META_TEST':'test5'}},
395
            {'name':'army_of_darkness.avi',
396
             'meta':{'HTTP_ETAG':'99914b932bd37a50b983c5e7c90ae93b',
397
                     'HTTP_CONTENT-LENGTH':23,
398
                     'HTTP_CONTENT_TYPE':'image/jpeg',
399
                     'HTTP_CONTENT_ENCODING':'utf8',
400
                     'HTTP_X_OBJECT_MANIFEST':123,
401
                     'HTTP_X_OBJECT_META_':'test6'}},
402
            {'name':'the_mad.avi',
403
             'meta':{'HTTP_ETAG':'wrong-hash',
404
                     'HTTP_CONTENT-LENGTH':23,
405
                     'HTTP_CONTENT_TYPE':'image/jpeg',
406
                     'HTTP_CONTENT_ENCODING':'utf8',
407
                     'HTTP_X_OBJECT_MANIFEST':123,
408
                     'HTTP_X_OBJECT_META_TEST':'test7'}}
409
        ]
410
        for item in self.l[:-1]:
411
            response = self.upload_object(self.account, self.container[0], item['name'], json.dumps({}), **item['meta'])
412
        self.create_container(self.account, self.container[1])
413
        l = [
414
            {'name':'photos/animals/dogs/poodle.jpg',
415
             'meta':{'HTTP_ETAG':'99914b932bd37a50b983c5e7c90ae93b',
416
                     'HTTP_CONTENT-LENGTH':23,
417
                     'HTTP_CONTENT_TYPE':'image/jpeg',
418
                     'HTTP_CONTENT_ENCODING':'utf8',
419
                     'HTTP_X_OBJECT_MANIFEST':123,
420
                     'HTTP_X_OBJECT_META_TEST':'test1'}},
421
            {'name':'photos/animals/dogs/terrier.jpg',
422
             'meta':{'HTTP_ETAG':'99914b932bd37a50b983c5e7c90ae93b',
423
                     'HTTP_CONTENT-LENGTH':23,
424
                     'HTTP_CONTENT_TYPE':'image/jpeg',
425
                     'HTTP_CONTENT_ENCODING':'utf8',
426
                     'HTTP_X_OBJECT_MANIFEST':123,
427
                     'HTTP_X_OBJECT_META_TEST':'test1'}},
428
            {'name':'photos/animals/cats/persian.jpg',
429
             'meta':{'HTTP_ETAG':'99914b932bd37a50b983c5e7c90ae93b',
430
                     'HTTP_CONTENT-LENGTH':28,
431
                     'HTTP_CONTENT_TYPE':'application/pdf',
432
                     'HTTP_CONTENT_ENCODING':'utf8',
433
                     'HTTP_X_OBJECT_MANIFEST':123,
434
                     'HTTP_X_OBJECT_META_TEST':'test2'}},
435
            {'name':'photos/animals/cats/siamese.jpg',
436
             'meta':{'HTTP_ETAG':'99914b932bd37a50b983c5e7c90ae93b',
437
                     'HTTP_CONTENT-LENGTH':255,
438
                     'HTTP_CONTENT_TYPE':'image/jpeg',
439
                     'HTTP_CONTENT_ENCODING':'utf8',
440
                     'HTTP_X_OBJECT_MANIFEST':123,
441
                     'HTTP_X_OBJECT_META_TEST':'test3'}},
442
            {'name':'photos/plants/fern.jpg',
443
             'meta':{'HTTP_ETAG':'99914b932bd37a50b983c5e7c90ae93b',
444
                     'HTTP_CONTENT-LENGTH':23,
445
                     'HTTP_CONTENT_TYPE':'image/jpeg',
446
                     'HTTP_CONTENT_ENCODING':'utf8',
447
                     'HTTP_X_OBJECT_MANIFEST':123,
448
                     'HTTP_X_OBJECT_META_TEST':'test4'}},
449
            {'name':'photos/plants/rose.jpg',
450
             'meta':{'HTTP_ETAG':'99914b932bd37a50b983c5e7c90ae93b',
451
                     'HTTP_CONTENT-LENGTH':23,
452
                     'HTTP_CONTENT_TYPE':'image/jpeg',
453
                     'HTTP_CONTENT_ENCODING':'utf8',
454
                     'HTTP_X_OBJECT_MANIFEST':123,
455
                     'XHTTP__OBJECT_META_TEST':'test5'}},
456
            {'name':'photos/me.jpg',
457
             'meta':{'HTTP_ETAG':'99914b932bd37a50b983c5e7c90ae93b',
458
                     'HTTP_CONTENT-LENGTH':23,
459
                     'HTTP_CONTENT_TYPE':'image/jpeg',
460
                     'HTTP_CONTENT_ENCODING':'utf8',
461
                     'X_OBJECT_MANIFEST':123,
462
                     'HTTP_X_OBJECT_META_TEST':'test6'}},
463
        ]
464
        for item in l:
465
            response = self.upload_object(self.account, self.container[1], item['name'], json.dumps({}), **item['meta'])
466

  
467
    def tearDown(self):
468
        for c in self.container:
469
            self.delete_container_recursively(c)
470

  
471
    def delete_container_recursively(self, c):
472
        for obj in get_content_splitted(self.list_objects(self.account, c)):
473
            self.delete_object(self.account, c, obj)
474
        self.delete_container(self.account, c)
475

  
476
    def test_list_objects(self):
477
        response = self.list_objects(self.account, self.container[0])
478
        objects = get_content_splitted(response)
479
        l = [elem['name'] for elem in self.l[:-1]]
480
        l.sort()
481
        self.assertEqual(objects, l)
482

  
483
    def test_list_objects_with_limit_marker(self):
484
        response = self.list_objects(self.account, self.container[0], limit=2)
485
        objects = get_content_splitted(response)
486
        l = [elem['name'] for elem in self.l[:-1]]
487
        l.sort()
488
        self.assertEqual(objects, l[:2])
489
        
490
        response = self.list_objects(self.account, self.container[0], limit=4, marker='How To Win Friends And Influence People.pdf')
491
        objects = get_content_splitted(response)
492
        l = [elem['name'] for elem in self.l[:-1]]
493
        l.sort()
494
        self.assertEqual(objects, l[2:6])
495
        
496
        response = self.list_objects(self.account, self.container[0], limit=4, marker='moms_birthday.jpg')
497
        objects = get_content_splitted(response)
498
        l = [elem['name'] for elem in self.l[:-1]]
499
        l.sort()
500
        self.assertEqual(objects, l[-1:])
501

  
502
    def test_list_pseudo_hierarchical_folders(self):
503
        response = self.list_objects(self.account, self.container[1], prefix='photos', delimiter='/')
504
        objects = get_content_splitted(response)
505
        self.assertEquals(['photos/animals/', 'photos/me.jpg', 'photos/plants/'], objects)
506
        
507
        response = self.list_objects(self.account, self.container[1], prefix='photos/animals', delimiter='/')
508
        objects = get_content_splitted(response)
509
        self.assertEquals(['photos/animals/cats/', 'photos/animals/dogs/'], objects)
510
        
511
        response = self.list_objects(self.account, self.container[1], path='photos')
512
        objects = get_content_splitted(response)
513
        self.assertEquals(['photos/me.jpg'], objects)
514

  
515
    def test_extended_list_json(self):
516
        response = self.list_objects(self.account, self.container[1], format='json', limit=2, prefix='photos/animals', delimiter='/')
517
        objects = json.loads(response.content)
518
        self.assertEqual(objects[0]['subdir'], 'photos/animals/cats/')
519
        self.assertEqual(objects[1]['subdir'], 'photos/animals/dogs/')
520

  
521
    def test_extended_list_xml(self):
522
        response = self.list_objects(self.account, self.container[1], format='xml', limit=4, prefix='photos', delimiter='/')
523
        xml = minidom.parseString(response.content)
524
        dirs = xml.getElementsByTagName('subdir')
525
        self.assertEqual(len(dirs), 2)
526
        self.assertEqual(dirs[0].attributes['name'].value, 'photos/animals/')
527
        self.assertEqual(dirs[1].attributes['name'].value, 'photos/plants/')
528
        
529
        objects = xml.getElementsByTagName('name')
530
        self.assertEqual(len(objects), 1)
531
        self.assertEqual(objects[0].childNodes[0].data, 'photos/me.jpg')
532

  
533
class ContainerMeta(BaseTestCase):
534
    def setUp(self):
535
        BaseTestCase.setUp(self)
536
        self.account = 'test'
537
        self.container = 'apples'
538
        self.create_container(self.account, self.container)
539

  
540
    def tearDown(self):
541
        self.delete_container(self.account, self.container)
542

  
543
    def test_update_meta(self):
544
        meta = {'HTTP_X_CONTAINER_META_TEST':'test33', 'HTTP_X_CONTAINER_META_TOST':'tost22'}
545
        response = self.update_container_meta(self.account, self.container, **meta)
546
        response = self.get_container_meta(self.account, self.container)
547
        for k,v in meta.items():
548
            key = '-'.join(elem.capitalize() for elem in k.split('_')[1:])
549
            self.assertTrue(response[key])
550
            self.assertEqual(response[key], v)
551

  
552
class CreateContainer(BaseTestCase):
553
    def setUp(self):
554
        BaseTestCase.setUp(self)
555
        self.account = 'test'
556
        self.containers = ['c1', 'c2']
557

  
558
    def tearDown(self):
559
        for c in self.containers:
560
            r = self.delete_container(self.account, c)
561

  
562
    def test_create(self):
563
        response = self.create_container(self.account, self.containers[0])
564
        if response.status_code == 201:
565
            response = self.list_containers(self.account)
566
            self.assertTrue(self.containers[0] in get_content_splitted(response))
567
            r = self.get_container_meta(self.account, self.containers[0])
568
            self.assertEqual(r.status_code, 204)
569

  
570
    def test_create_twice(self):
571
        response = self.create_container(self.account, self.containers[0])
572
        if response.status_code == 201:
573
            self.assertTrue(self.create_container(self.account, self.containers[0]).status_code, 202)
574

  
575
class DeleteContainer(BaseTestCase):
576
    def setUp(self):
577
        BaseTestCase.setUp(self)
578
        self.account = 'test'
579
        self.containers = ['c1', 'c2']
580
        for c in self.containers:
581
            self.create_container(self.account, c)
582
        obj = {'name':'kate.jpg',
583
               'meta':{'HTTP_ETAG':'99914b932bd37a50b983c5e7c90ae93b',
584
                       'HTTP_CONTENT-LENGTH':23,
585
                       'HTTP_CONTENT_TYPE':'image/jpeg',
586
                       'HTTP_CONTENT_ENCODING':'utf8',
587
                       'HTTP_X_OBJECT_MANIFEST':123,
588
                       'HTTP_X_OBJECT_META_TEST':'test1'
589
                       }
590
                }
591
        self.upload_object(self.account, self.containers[1], obj['name'], json.dumps({}), **obj['meta'])
592

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

  
599
    def test_delete(self):
600
        r = self.delete_container(self.account, self.containers[0])
601
        self.assertEqual(r.status_code, 204)
602

  
603
    def test_delete_non_empty(self):
604
        r = self.delete_container(self.account, self.containers[1])
605
        self.assertNonEmpty(r)
606

  
607
    def test_delete_invalid(self):
608
        self.assertItemNotFound(self.delete_container(self.account, 'c3'))
609

  
610
class GetObjects(BaseTestCase):
611
    def setUp(self):
612
        BaseTestCase.setUp(self)
613
        self.account = 'test'
614
        self.containers = ['c1', 'c2']
615
        for c in self.containers:
616
            self.create_container(self.account, c)
617
        self.obj = {'name':'kate.jpg',
618
               'meta':{'HTTP_ETAG':'99914b932bd37a50b983c5e7c90ae93b',
619
                       'HTTP_CONTENT-LENGTH':23,
620
                       'HTTP_CONTENT_TYPE':'image/jpeg',
621
                       'HTTP_CONTENT_ENCODING':'utf8',
622
                       'HTTP_X_OBJECT_MANIFEST':123,
623
                       'HTTP_X_OBJECT_META_TEST':'test1'
624
                       }
625
                }
626
        self.upload_object(self.account, self.containers[1], self.obj['name'], json.dumps({}), **self.obj['meta'])
627

  
628
    def tearDown(self):
629
        for c in self.containers:
630
            for o in get_content_splitted(self.list_objects(self.account, c)):
631
                self.delete_object(self.account, c, o)
632
            self.delete_container(self.account, c)
633

  
634
    def test_get(self):
635
        r = self.get_object(self.account, self.containers[1], self.obj['name'])
636
        self.assertEqual(r.status_code, 200)
637

  
638
    def test_get_invalid(self):
639
        r = self.get_object(self.account, self.containers[0], self.obj['name'])
640
        self.assertItemNotFound(r)
641

  
642
    def test_get_with_range(self):
643
        return
644
    
645
    def test_get_with_if_match(self):
646
        return
647
    
648
    def test_get_with_if_none_match(self):
649
        return
650
    
651
    def test_get_with_if_modified_since(self):
652
        return
653
    
654
    def test_get_with_if_unmodified_since(self):
655
        return
656
    
657
    def test_get_with_several_headers(self):
658
        return
659

  
660
class UploadObject(BaseTestCase):
661
    def setUp(self):
662
        BaseTestCase.setUp(self)
663
        self.account = 'test'
664
        self.container = 'c1'
665
        self.create_container(self.account, self.container)
666

  
667
    def tearDown(self):
668
        for o in get_content_splitted(self.list_objects(self.account, self.container)):
669
            self.delete_object(self.account, self.container, o)
670
        self.delete_container(self.account, self.container)
671
    
672
    def test_upload(self):
673
        filename = './api/tests.py'
674
        f = open(filename, 'r')
675
        data = f.read()
676
        hash = compute_hash(data)
677
        meta={'HTTP_ETAG':hash,
678
              'HTTP_CONTENT-LENGTH':os.path.getsize(filename),
679
              'HTTP_CONTENT_TYPE':'text/x-java',
680
              'HTTP_CONTENT_ENCODING':'us-ascii',
681
              'HTTP_X_OBJECT_MANIFEST':123,
682
              'HTTP_X_OBJECT_META_TEST':'test1'
683
              }
684
        r = self.upload_object(self.account, self.container, filename, data, content_type='plain/text',**meta)
685
        self.assertEqual(r.status_code, 201)
686
        
687
class AssertInvariant(object):
688
    def __init__(self, callable, *args, **kwargs):
689
        self.callable = callable
690
        self.args = args
691
        self.kwargs = kwargs
692

  
693
    def __enter__(self):
694
        self.value = self.callable(*self.args, **self.kwargs)
695
        return self.value
696

  
697
    def __exit__(self, type, value, tb):
698
        assert self.value == self.callable(*self.args, **self.kwargs)
699

  
700
def get_content_splitted(response):
701
    if response:
702
        return response.content.split('\n')
703

  
704
def compute_hash(data):
705
    md5 = hashlib.md5()
706
    offset = 0
707
    md5.update(data)
708
    return md5.hexdigest().lower()
1
from django.test.client import Client
2
from django.test import TestCase
3
from django.utils import simplejson as json
4
from xml.dom import minidom
5
import types
6
import hashlib
7
import os
8
import mimetypes
9
import random
10
import datetime
11

  
12
DATE_FORMATS = ["%a %b %d %H:%M:%S %Y",
13
                "%A, %d-%b-%y %H:%M:%S GMT",
14
                "%a, %d %b %Y %H:%M:%S GMT"]
15

  
16
class AaiClient(Client):
17
    def request(self, **request):
18
        request['HTTP_X_AUTH_TOKEN'] = '46e427d657b20defe352804f0eb6f8a2'
19
        return super(AaiClient, self).request(**request)
20

  
21
class BaseTestCase(TestCase):
22
    #TODO unauthorized request
23
    def setUp(self):
24
        self.client = AaiClient()
25
        self.headers = {
26
            'account':(
27
                'X-Account-Container-Count',
28
                'X-Account-Bytes-Used',
29
                'Last-Modified',
30
                'Content-Length',
31
                'Date',
32
                'Content-Type',),
33
            'container':(
34
                'X-Container-Object-Count',
35
                'X-Container-Bytes-Used',
36
                'Content-Type',
37
                'Last-Modified',
38
                'Content-Length',
39
                'Date',),
40
            'object':(
41
                'ETag',
42
                'Content-Length',
43
                'Content-Type',
44
                'Content-Encoding',
45
                'Last-Modified',
46
                'Date',
47
                'X-Object-Manifest',
48
                'Content-Range',)}
49
        self.contentTypes = {'xml':'application/xml',
50
                             'json':'application/json',
51
                             '':'text/plain'}
52
        self.extended = {
53
            'container':(
54
                'name',
55
                'count',
56
                'bytes',
57
                'last_modified'),
58
            'object':(
59
                'name',
60
                'hash',
61
                'bytes',
62
                'content_type',
63
                'content_encoding',
64
                'last_modified',)}
65
        self.return_codes = (400, 401, 404, 503,)
66
    
67
    def assertFault(self, response, status_code, name):
68
        self.assertEqual(response.status_code, status_code)
69
    
70
    def assertBadRequest(self, response):
71
        self.assertFault(response, 400, 'badRequest')
72
    
73
    def assertItemNotFound(self, response):
74
        self.assertFault(response, 404, 'itemNotFound')
75

  
76
    def assertUnauthorized(self, response):
77
        self.assertFault(response, 401, 'unauthorized')
78

  
79
    def assertServiceUnavailable(self, response):
80
        self.assertFault(response, 503, 'serviceUnavailable')
81

  
82
    def assertNonEmpty(self, response):
83
        self.assertFault(response, 409, 'nonEmpty')
84

  
85
    def assert_status(self, response, codes):
86
        l = [elem for elem in self.return_codes]
87
        if type(codes) == types.ListType:
88
            l.extend(codes)
89
        else:
90
            l.append(codes)
91
        self.assertTrue(response.status_code in l)
92

  
93
    def get_account_meta(self, account, exp_meta={}):
94
        path = '/v1/%s' % account
95
        response = self.client.head(path)
96
        self.assert_status(response, 204)
97
        self.assert_headers(response, 'account', exp_meta)
98
        return response
99

  
100
    def list_containers(self, account, limit=10000, marker='', format=''):
101
        params = locals()
102
        params.pop('self')
103
        params.pop('account')
104
        path = '/v1/%s' % account
105
        response = self.client.get(path, params)
106
        self.assert_status(response, [200, 204])
107
        response.content = response.content.strip()
108
        if format:
109
            self.assert_extended(response, format, 'container', limit)
110
        else:
111
            names = get_content_splitted(response)
112
            self.assertTrue(len(names) <= limit)
113
        return response
114

  
115
    def update_account_meta(self, account, **metadata):
116
        path = '/v1/%s' % account
117
        response = self.client.post(path, **metadata)
118
        response.content = response.content.strip()
119
        self.assert_status(response, 202)
120
        return response
121

  
122
    def get_container_meta(self, account, container, exp_meta={}):
123
        params = locals()
124
        params.pop('self')
125
        params.pop('account')
126
        params.pop('container')
127
        path = '/v1/%s/%s' %(account, container)
128
        response = self.client.head(path, params)
129
        response.content = response.content.strip()
130
        self.assert_status(response, 204)
131
        if response.status_code == 204:
132
            self.assert_headers(response, 'container', exp_meta)
133
        return response
134

  
135
    def list_objects(self, account, container, limit=10000, marker='', prefix='', format='', path='', delimiter='', meta=''):
136
        params = locals()
137
        params.pop('self')
138
        params.pop('account')
139
        params.pop('container')
140
        path = '/v1/%s/%s' % (account, container)
141
        response = self.client.get(path, params)
142
        response.content = response.content.strip()
143
        if format:
144
            self.assert_extended(response, format, 'object', limit)
145
        self.assert_status(response, [200, 204])
146
        return response
147

  
148
    def create_container(self, account, name, **meta):
149
        path = '/v1/%s/%s' %(account, name)
150
        response = self.client.put(path, **meta)
151
        response.content = response.content.strip()
152
        self.assert_status(response, [201, 202])
153
        return response
154

  
155
    def update_container_meta(self, account, name, **meta):
156
        path = '/v1/%s/%s' %(account, name)
157
        response = self.client.post(path, data={}, content_type='text/xml', follow=False, **meta)
158
        response.content = response.content.strip()
159
        self.assert_status(response, 202)
160
        return response
161

  
162
    def delete_container(self, account, container):
163
        path = '/v1/%s/%s' %(account, container)
164
        response = self.client.delete(path)
165
        response.content = response.content.strip()
166
        self.assert_status(response, [204, 409])
167
        return response
168

  
169
    def get_object_meta(self, account, container, name):
170
        path = '/v1/%s/%s/%s' %(account, container, name)
171
        response = self.client.head(path)
172
        response.content = response.content.strip()
173
        self.assert_status(response, 204)
174
        return response
175

  
176
    def get_object(self, account, container, name, exp_meta={}, **headers):
177
        path = '/v1/%s/%s/%s' %(account, container, name)
178
        response = self.client.get(path, **headers)
179
        response.content = response.content.strip()
180
        self.assert_status(response, [200, 206, 304, 412, 416])
181
        if response.status_code in [200, 206]:
182
            self.assert_headers(response, 'object')
183
        return response
184

  
185
    def upload_object(self, account, container, name, data, content_type='application/json', **headers):
186
        path = '/v1/%s/%s/%s' %(account, container, name)
187
        response = self.client.put(path, data, content_type, **headers)
188
        response.content = response.content.strip()
189
        self.assert_status(response, [201, 411, 422])
190
        if response.status_code == 201:
191
            self.assertTrue(response['Etag'])
192
        return response
193

  
194
    def copy_object(self, account, container, name, src, **headers):
195
        path = '/v1/%s/%s/%s' %(account, container, name)
196
        headers['HTTP_X_COPY_FROM'] = src
197
        response = self.client.put(path, **headers)
198
        response.content = response.content.strip()
199
        self.assert_status(response, 201)
200
        return response
201

  
202
    def move_object(self, account, container, name, src, **headers):
203
        path = '/v1/%s/%s/%s' % (account, container, name)
204
        headers['HTTP_X_MOVE_FROM'] = src
205
        response = self.client.put(path, **headers)
206
        response.content = response.content.strip()
207
        self.assert_status(response, 201)
208
        return response
209

  
210
    def update_object_meta(self, account, container, name, **headers):
211
        path = '/v1/%s/%s/%s' %(account, container, name)
212
        response = self.client.post(path, **headers)
213
        response.content = response.content.strip()
214
        self.assert_status(response, 202)
215
        return response
216

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

  
224
    def assert_headers(self, response, type, exp_meta={}):
225
        entities = ['Account', 'Container', 'Container-Object', 'Object']
226
        user_defined_meta = ['X-%s-Meta' %elem for elem in entities]
227
        headers = [item for item in response._headers.values()]
228
        system_headers = [h for h in headers if not h[0].startswith(tuple(user_defined_meta))]
229
        for h in system_headers:
230
            self.assertTrue(h[0] in self.headers[type])
231
            if exp_meta:
232
                self.assertEqual(h[1], exp_meta[h[0]])
233

  
234
    def assert_extended(self, response, format, type, size):
235
        self.assertEqual(response['Content-Type'].find(self.contentTypes[format]), 0)
236
        if format == 'xml':
237
            self.assert_xml(response, type, size)
238
        elif format == 'json':
239
            self.assert_json(response, type, size)
240

  
241
    def assert_json(self, response, type, size):
242
        convert = lambda s: s.lower()
243
        info = [convert(elem) for elem in self.extended[type]]
244
        data = json.loads(response.content)
245
        self.assertTrue(len(data) <= size)
246
        for item in info:
247
            for i in data:
248
                if 'subdir' in i.keys():
249
                    continue
250
                self.assertTrue(item in i.keys())
251

  
252
    def assert_xml(self, response, type, size):
253
        convert = lambda s: s.lower()
254
        info = [convert(elem) for elem in self.extended[type]]
255
        try:
256
            info.remove('content_encoding')
257
        except ValueError:
258
            pass
259
        xml = minidom.parseString(response.content)
260
        for item in info:
261
            nodes = xml.getElementsByTagName(item)
262
            self.assertTrue(nodes)
263
            self.assertTrue(len(nodes) <= size)
264
            
265

  
266
    def upload_os_file(self, account, container, fullpath, meta={}):
267
        try:
268
            f = open(fullpath, 'r')
269
            data = f.read()
270
            name = os.path.split(fullpath)[-1]
271
            return self.upload_data(account, container, name, data)    
272
        except IOError:
273
            return
274

  
275
    def upload_random_data(self, account, container, name, length=1024, meta={}):
276
        data = str(random.getrandbits(length))
277
        return self.upload_data(account, container, name, data, meta)
278

  
279
    def upload_data(self, account, container, name, data, meta={}):
280
        obj = {}
281
        obj['name'] = name
282
        try:
283
            obj['data'] = data
284
            obj['hash'] = compute_hash(obj['data'])
285
            meta.update({'HTTP_X_OBJECT_META_TEST':'test1',
286
                         'HTTP_ETAG':obj['hash']})
287
            meta['HTTP_CONTENT_TYPE'], enc = mimetypes.guess_type(name)
288
            if enc:
289
                meta['HTTP_CONTENT_TYPE'] = enc
290
            obj['meta'] = meta
291
            r = self.upload_object(account,
292
                               container,
293
                               obj['name'],
294
                               obj['data'],
295
                               meta['HTTP_CONTENT_TYPE'],
296
                               **meta)
297
            if r.status_code == 201:
298
                return obj
299
        except IOError:
300
            return
301

  
302
class ListContainers(BaseTestCase):
303
    def setUp(self):
304
        BaseTestCase.setUp(self)
305
        self.account = 'test'
306
        #create some containers
307
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
308
        for item in self.containers:
309
            self.create_container(self.account, item)
310

  
311
    def tearDown(self):
312
        for c in get_content_splitted(self.list_containers(self.account)):
313
            response = self.delete_container(self.account, c)
314

  
315
    def test_list(self):
316
        #list containers
317
        response = self.list_containers(self.account)
318
        containers = get_content_splitted(response)
319
        self.assertEquals(self.containers, containers)
320

  
321
    #def test_list_204(self):
322
    #    response = self.list_containers('non-existing-account')
323
    #    self.assertEqual(response.status_code, 204)
324

  
325
    def test_list_with_limit(self):
326
        limit = 2
327
        response = self.list_containers(self.account, limit=limit)
328
        containers = get_content_splitted(response)
329
        self.assertEquals(len(containers), limit)
330
        self.assertEquals(self.containers[:2], containers)
331

  
332
    def test_list_with_marker(self):
333
        limit = 2
334
        marker = 'bananas'
335
        response = self.list_containers(self.account, limit=limit, marker=marker)
336
        containers =  get_content_splitted(response)
337
        i = self.containers.index(marker) + 1
338
        self.assertEquals(self.containers[i:(i+limit)], containers)
339
        
340
        marker = 'oranges'
341
        response = self.list_containers(self.account, limit=limit, marker=marker)
342
        containers = get_content_splitted(response)
343
        i = self.containers.index(marker) + 1
344
        self.assertEquals(self.containers[i:(i+limit)], containers)
345

  
346
    #def test_extended_list(self):
347
    #    self.list_containers(self.account, limit=3, format='xml')
348
    #    self.list_containers(self.account, limit=3, format='json')
349

  
350
    def test_list_json_with_marker(self):
351
        limit = 2
352
        marker = 'bananas'
353
        response = self.list_containers(self.account, limit=limit, marker=marker, format='json')
354
        containers = json.loads(response.content)
355
        self.assertEqual(containers[0]['name'], 'kiwis')
356
        self.assertEqual(containers[1]['name'], 'oranges')
357

  
358
    def test_list_xml_with_marker(self):
359
        limit = 2
360
        marker = 'oranges'
361
        response = self.list_containers(self.account, limit=limit, marker=marker, format='xml')
362
        xml = minidom.parseString(response.content)
363
        nodes = xml.getElementsByTagName('name')
364
        self.assertEqual(len(nodes), 1)
365
        self.assertEqual(nodes[0].childNodes[0].data, 'pears')
366

  
367
class AccountMetadata(BaseTestCase):
368
    def setUp(self):
369
        BaseTestCase.setUp(self)
370
        self.account = 'test'
371
        self.containers = ['apples', 'bananas', 'kiwis', 'oranges', 'pears']
372
        for item in self.containers:
373
            self.create_container(self.account, item)
374

  
375
    def tearDown(self):
376
        for c in  get_content_splitted(self.list_containers(self.account)):
377
            self.delete_container(self.account, c)
378

  
379
    def test_get_account_meta(self):
380
        response = self.get_account_meta(self.account)
381
        r2 = self.list_containers(self.account)
382
        containers =  get_content_splitted(r2)
383
        self.assertEqual(response['X-Account-Container-Count'], str(len(containers)))
384
        size = 0
385
        for c in containers:
386
            r = self.get_container_meta(self.account, c)
387
            size = size + int(r['X-Container-Bytes-Used'])
388
        self.assertEqual(response['X-Account-Bytes-Used'], str(size))
389

  
390
    #def test_get_account_401(self):
391
    #    response = self.get_account_meta('non-existing-account')
392
    #    print response
393
    #    self.assertEqual(response.status_code, 401)
394

  
395
    def test_update_meta(self):
396
        meta = {'HTTP_X_ACCOUNT_META_TEST':'test', 'HTTP_X_ACCOUNT_META_TOST':'tost'}
397
        response = self.update_account_meta(self.account, **meta)
398
        response = self.get_account_meta(self.account)
399
        for k,v in meta.items():
400
            key = '-'.join(elem.capitalize() for elem in k.split('_')[1:])
401
            self.assertTrue(response[key])
402
            self.assertEqual(response[key], v)
403

  
404
    #def test_invalid_account_update_meta(self):
405
    #    with AssertInvariant(self.get_account_meta, self.account):
406
    #        meta = {'HTTP_X_ACCOUNT_META_TEST':'test', 'HTTP_X_ACCOUNT_META_TOST':'tost'}
407
    #        response = self.update_account_meta('non-existing-account', **meta)
408

  
409
class ListObjects(BaseTestCase):
410
    def setUp(self):
411
        BaseTestCase.setUp(self)
412
        self.account = 'test'
413
        self.container = ['pears', 'apples']
414
        for c in self.container:
415
            self.create_container(self.account, c)
416
        self.obj = []
417
        for o in o_names[:8]:
418
            self.obj.append(self.upload_random_data(self.account,
419
                                                    self.container[0],
420
                                                    o))
421
        for o in o_names[8:]:
422
            self.obj.append(self.upload_random_data(self.account,
423
                                                    self.container[1],
424
                                                    o))
425

  
426
    def tearDown(self):
427
        for c in self.container:
428
            self.delete_container_recursively(c)
429

  
430
    def delete_container_recursively(self, c):
431
        for obj in get_content_splitted(self.list_objects(self.account, c)):
432
            self.delete_object(self.account, c, obj)
433
        self.delete_container(self.account, c)
434

  
435
    def test_list_objects(self):
436
        response = self.list_objects(self.account, self.container[0])
437
        objects = get_content_splitted(response)
438
        l = [elem['name'] for elem in self.obj[:8]]
439
        l.sort()
440
        self.assertEqual(objects, l)
441

  
442
    def test_list_objects_with_limit_marker(self):
443
        response = self.list_objects(self.account, self.container[0], limit=2)
444
        objects = get_content_splitted(response)
445
        l = [elem['name'] for elem in self.obj[:8]]
446
        l.sort()
447
        self.assertEqual(objects, l[:2])
448
        
449
        markers = ['How To Win Friends And Influence People.pdf',
450
                   'moms_birthday.jpg']
451
        limit = 4
452
        for m in markers:
453
            response = self.list_objects(self.account, self.container[0], limit=limit, marker=m)
454
            objects = get_content_splitted(response)
455
            l = [elem['name'] for elem in self.obj[:8]]
456
            l.sort()
457
            start = l.index(m) + 1
458
            end = start + limit
459
            end = len(l) >= end and end or len(l)
460
            self.assertEqual(objects, l[start:end])
461

  
462
    def test_list_pseudo_hierarchical_folders(self):
463
        response = self.list_objects(self.account, self.container[1], prefix='photos', delimiter='/')
464
        objects = get_content_splitted(response)
465
        self.assertEquals(['photos/animals/', 'photos/me.jpg', 'photos/plants/'], objects)
466
        
467
        response = self.list_objects(self.account, self.container[1], prefix='photos/animals', delimiter='/')
468
        objects = get_content_splitted(response)
469
        self.assertEquals(['photos/animals/cats/', 'photos/animals/dogs/'], objects)
470
        
471
        response = self.list_objects(self.account, self.container[1], path='photos')
472
        objects = get_content_splitted(response)
473
        self.assertEquals(['photos/me.jpg'], objects)
474

  
475
    def test_extended_list_json(self):
476
        response = self.list_objects(self.account, self.container[1], format='json', limit=2, prefix='photos/animals', delimiter='/')
477
        objects = json.loads(response.content)
478
        self.assertEqual(objects[0]['subdir'], 'photos/animals/cats/')
479
        self.assertEqual(objects[1]['subdir'], 'photos/animals/dogs/')
480

  
481
    def test_extended_list_xml(self):
482
        response = self.list_objects(self.account, self.container[1], format='xml', limit=4, prefix='photos', delimiter='/')
483
        xml = minidom.parseString(response.content)
484
        dirs = xml.getElementsByTagName('subdir')
485
        self.assertEqual(len(dirs), 2)
486
        self.assertEqual(dirs[0].attributes['name'].value, 'photos/animals/')
487
        self.assertEqual(dirs[1].attributes['name'].value, 'photos/plants/')
488
        
489
        objects = xml.getElementsByTagName('name')
490
        self.assertEqual(len(objects), 1)
491
        self.assertEqual(objects[0].childNodes[0].data, 'photos/me.jpg')
492

  
493
    def test_list_using_meta(self):
494
        meta = {'HTTP_X_OBJECT_META_QUALITY':'aaa'}
495
        for o in self.obj[:2]:
496
            r = self.update_object_meta(self.account,
497
                                    self.container[0],
498
                                    o['name'],
499
                                    **meta)
500
        meta = {'HTTP_X_OBJECT_META_STOCK':'true'}
501
        for o in self.obj[3:5]:
502
            r = self.update_object_meta(self.account,
503
                                    self.container[0],
504
                                    o['name'],
505
                                    **meta)
506
            
507
        r = self.list_objects(self.account,
508
                          self.container[0],
509
                          meta='Quality')
510
        self.assertEqual(r.status_code, 200)
511
        obj = get_content_splitted(r)
512
        self.assertEqual(len(obj), 2)
513
        
514
        # test case insensitive
515
        r = self.list_objects(self.account,
516
                          self.container[0],
517
                          meta='quality')
518
        self.assertEqual(r.status_code, 200)
519
        obj = get_content_splitted(r)
520
        self.assertEqual(len(obj), 2)
521
        
522
        # test multiple matches
523
        r = self.list_objects(self.account,
524
                          self.container[0],
525
                          meta='Quality, Stock')
526
        self.assertEqual(r.status_code, 200)
527
        obj = get_content_splitted(r)
528
        self.assertEqual(len(obj), 4)
529
        
530
        # test non 1-1 multiple match
531
        r = self.list_objects(self.account,
532
                          self.container[0],
533
                          meta='Quality, aaaa')
534
        self.assertEqual(r.status_code, 200)
535
        obj = get_content_splitted(r)
536
        self.assertEqual(len(obj), 2)
537
        
538

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

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

  
570
    def test_update_meta(self):
571
        meta = {'HTTP_X_CONTAINER_META_TEST':'test33', 'HTTP_X_CONTAINER_META_TOST':'tost22'}
572
        response = self.update_container_meta(self.account, self.container, **meta)
573
        response = self.get_container_meta(self.account, self.container)
574
        for k,v in meta.items():
575
            key = '-'.join(elem.capitalize() for elem in k.split('_')[1:])
576
            self.assertTrue(response[key])
577
            self.assertEqual(response[key], v)
578

  
579
class CreateContainer(BaseTestCase):
580
    def setUp(self):
581
        BaseTestCase.setUp(self)
582
        self.account = 'test'
583
        self.containers = ['c1', 'c2']
584

  
585
    def tearDown(self):
586
        for c in self.containers:
587
            r = self.delete_container(self.account, c)
588

  
589
    def test_create(self):
590
        response = self.create_container(self.account, self.containers[0])
591
        if response.status_code == 201:
592
            response = self.list_containers(self.account)
593
            self.assertTrue(self.containers[0] in get_content_splitted(response))
594
            r = self.get_container_meta(self.account, self.containers[0])
595
            self.assertEqual(r.status_code, 204)
596

  
597
    def test_create_twice(self):
598
        response = self.create_container(self.account, self.containers[0])
599
        if response.status_code == 201:
600
            self.assertTrue(self.create_container(self.account, self.containers[0]).status_code, 202)
601

  
602
class DeleteContainer(BaseTestCase):
603
    def setUp(self):
604
        BaseTestCase.setUp(self)
605
        self.account = 'test'
606
        self.containers = ['c1', 'c2']
607
        for c in self.containers:
608
            self.create_container(self.account, c)
609
        self.upload_random_data(self.account,
610
                                self.containers[1],
611
                                'nice.jpg')
612

  
613
    def tearDown(self):
614
        for c in self.containers:
615
            for o in get_content_splitted(self.list_objects(self.account, c)):
616
                self.delete_object(self.account, c, o)
617
            self.delete_container(self.account, c)
618

  
619
    def test_delete(self):
620
        r = self.delete_container(self.account, self.containers[0])
621
        self.assertEqual(r.status_code, 204)
622

  
623
    def test_delete_non_empty(self):
624
        r = self.delete_container(self.account, self.containers[1])
625
        self.assertNonEmpty(r)
626

  
627
    def test_delete_invalid(self):
628
        self.assertItemNotFound(self.delete_container(self.account, 'c3'))
629

  
630
class GetObjects(BaseTestCase):
631
    def setUp(self):
632
        BaseTestCase.setUp(self)
633
        self.account = 'test'
634
        self.containers = ['c1', 'c2']
635
        #create some containers
636
        for c in self.containers:
637
            self.create_container(self.account, c)
638
        
639
        #upload a file
640
        self.objects = []
641
        self.objects.append(self.upload_os_file(self.account,
642
                            self.containers[1],
643
                            './api/tests.py'))
644
        self.objects.append(self.upload_os_file(self.account,
645
                            self.containers[1],
646
                            'settings.py'))
647

  
648
    def tearDown(self):
649
        for c in self.containers:
650
            for o in get_content_splitted(self.list_objects(self.account, c)):
651
                self.delete_object(self.account, c, o)
652
            self.delete_container(self.account, c)
653

  
654
    def test_get(self):
655
        #perform get
656
        r = self.get_object(self.account,
657
                            self.containers[1],
658
                            self.objects[0]['name'],
659
                            self.objects[0]['meta'])
660
        #assert success
661
        self.assertEqual(r.status_code, 200)
662

  
663
    def test_get_invalid(self):
664
        r = self.get_object(self.account,
665
                            self.containers[0],
666
                            self.objects[0]['name'])
667
        self.assertItemNotFound(r)
668

  
669
    def test_get_partial(self):
670
        #perform get with range
671
        headers = {'HTTP_RANGE':'bytes=0-499'}
672
        r = self.get_object(self.account,
673
                        self.containers[1],
674
                        self.objects[0]['name'],
675
                        **headers)
676
        
677
        #assert successful partial content
678
        self.assertEqual(r.status_code, 206)
679
        
680
        #assert content length
681
        self.assertEqual(int(r['Content-Length']), 500)
682
        
683
        #assert content
684
        self.assertEqual(self.objects[0]['data'][:500], r.content)
685

  
686
    def test_get_final_500(self):
687
        #perform get with range
688
        headers = {'HTTP_RANGE':'bytes=-500'}
689
        r = self.get_object(self.account,
690
                        self.containers[1],
691
                        self.objects[0]['name'],
692
                        **headers)
693
        
694
        #assert successful partial content
695
        self.assertEqual(r.status_code, 206)
696
        
697
        #assert content length
698
        self.assertEqual(int(r['Content-Length']), 500)
699
        
700
        #assert content
701
        self.assertTrue(self.objects[0]['data'][-500:], r.content)
702

  
703
    def test_get_rest(self):
704
        #perform get with range
705
        offset = len(self.objects[0]['data']) - 500
706
        headers = {'HTTP_RANGE':'bytes=%s-' %offset}
707
        r = self.get_object(self.account,
708
                        self.containers[1],
709
                        self.objects[0]['name'],
710
                        **headers)
711
        
712
        #assert successful partial content
713
        self.assertEqual(r.status_code, 206)
714
        
715
        #assert content length
716
        self.assertEqual(int(r['Content-Length']), 500)
717
        
718
        #assert content
719
        self.assertTrue(self.objects[0]['data'][-500:], r.content)
720
        
721
    def test_get_range_not_satisfiable(self):
722
        #perform get with range
723
        offset = len(self.objects[0]['data']) + 1
724
        headers = {'HTTP_RANGE':'bytes=0-%s' %offset}
725
        r = self.get_object(self.account,
726
                        self.containers[1],
727
                        self.objects[0]['name'],
728
                        **headers)
729
        
730
        #assert Range Not Satisfiable
731
        self.assertEqual(r.status_code, 416)
732

  
733
    def test_multiple_range(self):
734
        #perform get with multiple range
735
        ranges = ['0-499', '-500', '1000-']
736
        headers = {'HTTP_RANGE' : 'bytes=%s' % ','.join(ranges)}
737
        r = self.get_object(self.account,
738
                        self.containers[1],
739
                        self.objects[0]['name'],
740
                        **headers)
741
        
742
        # assert partial content
743
        self.assertEqual(r.status_code, 206)
744
        
745
        # assert Content-Type of the reply will be multipart/byteranges
746
        self.assertTrue(r['Content-Type'])
747
        content_type_parts = r['Content-Type'].split()
748
        self.assertEqual(content_type_parts[0], ('multipart/byteranges;'))
749
        
750
        boundary = '--%s' %content_type_parts[1].split('=')[-1:][0]
751
        cparts = r.content.split(boundary)[1:-1]
752
        
753
        # assert content parts are exactly 2
754
        self.assertEqual(len(cparts), len(ranges))
755
        
756
        # for each content part assert headers
757
        i = 0
758
        for cpart in cparts:
759
            content = cpart.split('\r\n')
760
            headers = content[1:3]
761
            content_range = headers[0].split(': ')
762
            self.assertEqual(content_range[0], 'Content-Range')
763
            
764
            r = ranges[i].split('-')
765
            if not r[0] and not r[1]:
766
                pass
767
            elif not r[0]:
768
                start = len(self.objects[0]['data']) - int(r[1])
769
                end = len(self.objects[0]['data'])
770
            elif not r[1]:
771
                start = int(r[0])
772
                end = len(self.objects[0]['data'])
773
            else:
774
                start = int(r[0])
775
                end = int(r[1]) + 1
776
            fdata = self.objects[0]['data'][start:end]
777
            sdata = '\r\n'.join(content[4:-1])
778
            self.assertEqual(len(fdata), len(sdata))
779
            self.assertEquals(fdata, sdata)
780
            i+=1
781

  
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff