diff --git a/test/unit/common/middleware/test_slo.py b/test/unit/common/middleware/test_slo.py index 929453aacd..f3c2332f2b 100644 --- a/test/unit/common/middleware/test_slo.py +++ b/test/unit/common/middleware/test_slo.py @@ -14,155 +14,15 @@ # limitations under the License. import unittest +from copy import deepcopy from mock import patch from hashlib import md5 +from swift.common import swob from swift.common.middleware import slo -from swift.common.utils import json -from swift.common.swob import Request, Response, HTTPException +from swift.common.utils import json, split_path +from swift.common.swob import Request, HTTPException -class FakeApp(object): - def __init__(self): - self.calls = 0 - self.req_method_paths = [] - - def __call__(self, env, start_response): - self.calls += 1 - if env['PATH_INFO'] == '/': - return Response(status=200, body='passed')(env, start_response) - if env['PATH_INFO'].startswith('/test_good/'): - j, v, a, cont, obj = env['PATH_INFO'].split('/') - if obj == 'a_2': - return Response(status=400)(env, start_response) - cont_len = 100 - if obj == 'small_object': - cont_len = 10 - headers = {'etag': 'etagoftheobjectsegment', - 'Content-Length': cont_len} - if obj == 'slob': - headers['X-Static-Large-Object'] = 'true' - return Response(status=200, headers=headers)(env, start_response) - if env['PATH_INFO'].startswith('/test_good_check/'): - j, v, a, cont, obj = env['PATH_INFO'].split('/') - etag, size = obj.split('_') - last_mod = 'Fri, 01 Feb 2012 20:38:36 GMT' - if obj == 'a_1': - last_mod = '' - return Response( - status=200, - headers={'etag': etag, 'Last-Modified': last_mod, - 'Content-Length': size})(env, start_response) - if env['PATH_INFO'].startswith('/test_get/'): - good_data = json.dumps( - [{'name': '/c/a_1', 'hash': 'a', 'bytes': '1'}, - {'name': '/d/b_2', 'hash': 'b', 'bytes': '2'}]) - return Response(status=200, - headers={'X-Static-Large-Object': 'True', - 'Content-Type': 'html;swift_bytes=55'}, - body=good_data)(env, start_response) - - if env['PATH_INFO'].startswith('/test_get_broke_json/'): - good_data = json.dumps( - [{'name': '/c/a_1', 'hash': 'a', 'bytes': '1'}, - {'name': '/d/b_2', 'hash': 'b', 'bytes': '2'}]) - return Response(status=200, - headers={'X-Static-Large-Object': 'True'}, - body=good_data[:-5])(env, start_response) - - if env['PATH_INFO'].startswith('/test_get_bad_json/'): - bad_data = json.dumps( - [{'name': '/c/a_1', 'something': 'a', 'bytes': '1'}, - {'name': '/d/b_2', 'bytes': '2'}]) - return Response(status=200, - headers={'X-Static-Large-Object': 'True'}, - body=bad_data)(env, start_response) - - if env['PATH_INFO'].startswith('/test_get_not_slo/'): - return Response(status=200, body='lalala')(env, start_response) - - if env['PATH_INFO'].startswith('/test_delete_404/'): - good_data = json.dumps( - [{'name': '/c/a_1', 'hash': 'a', 'bytes': '1'}, - {'name': '/d/b_2', 'hash': 'b', 'bytes': '2'}]) - self.req_method_paths.append((env['REQUEST_METHOD'], - env['PATH_INFO'])) - if env['PATH_INFO'].endswith('/c/man_404'): - return Response(status=404)(env, start_response) - if env['PATH_INFO'].endswith('/c/a_1'): - return Response(status=404)(env, start_response) - return Response(status=200, - headers={'X-Static-Large-Object': 'True'}, - body=good_data)(env, start_response) - - if env['PATH_INFO'].startswith('/test_delete/'): - good_data = json.dumps( - [{'name': '/c/a_1', 'hash': 'a', 'bytes': '1'}, - {'name': '/d/b_2', 'hash': 'b', 'bytes': '2'}]) - self.req_method_paths.append((env['REQUEST_METHOD'], - env['PATH_INFO'])) - return Response(status=200, - headers={'X-Static-Large-Object': 'True'}, - body=good_data)(env, start_response) - - if env['PATH_INFO'].startswith('/test_delete_nested/'): - nested_data = json.dumps( - [{'name': '/b/b_2', 'hash': 'a', 'bytes': '1'}, - {'name': '/c/c_3', 'hash': 'b', 'bytes': '2'}]) - good_data = json.dumps( - [{'name': '/a/a_1', 'hash': 'a', 'bytes': '1'}, - {'name': '/a/sub_nest', 'hash': 'a', 'sub_slo': True, - 'bytes': len(nested_data)}, - {'name': '/d/d_3', 'hash': 'b', 'bytes': '2'}]) - self.req_method_paths.append((env['REQUEST_METHOD'], - env['PATH_INFO'])) - if 'sub_nest' in env['PATH_INFO']: - return Response(status=200, - headers={'X-Static-Large-Object': 'True'}, - body=nested_data)(env, start_response) - else: - return Response(status=200, - headers={'X-Static-Large-Object': 'True'}, - body=good_data)(env, start_response) - - if env['PATH_INFO'].startswith('/test_delete_nested_404/'): - good_data = json.dumps( - [{'name': '/a/a_1', 'hash': 'a', 'bytes': '1'}, - {'name': '/a/sub_nest', 'hash': 'a', 'bytes': '2', - 'sub_slo': True}, - {'name': '/d/d_3', 'hash': 'b', 'bytes': '3'}]) - self.req_method_paths.append((env['REQUEST_METHOD'], - env['PATH_INFO'])) - if 'sub_nest' in env['PATH_INFO']: - return Response(status=404)(env, start_response) - else: - return Response(status=200, - headers={'X-Static-Large-Object': 'True'}, - body=good_data)(env, start_response) - - if env['PATH_INFO'].startswith('/test_delete_bad_json/'): - self.req_method_paths.append((env['REQUEST_METHOD'], - env['PATH_INFO'])) - return Response(status=200, - headers={'X-Static-Large-Object': 'True'}, - body='bad json')(env, start_response) - - if env['PATH_INFO'].startswith('/test_delete_bad_man/'): - self.req_method_paths.append((env['REQUEST_METHOD'], - env['PATH_INFO'])) - return Response(status=200, body='')(env, start_response) - - if env['PATH_INFO'].startswith('/test_delete_401/'): - good_data = json.dumps( - [{'name': '/c/a_1', 'hash': 'a', 'bytes': '1'}, - {'name': '/d/b_2', 'hash': 'b', 'bytes': '2'}]) - self.req_method_paths.append((env['REQUEST_METHOD'], - env['PATH_INFO'])) - if env['PATH_INFO'].endswith('/d/b_2'): - return Response(status=401)(env, start_response) - return Response(status=200, - headers={'X-Static-Large-Object': 'True'}, - body=good_data)(env, start_response) - test_xml_data = ''' @@ -181,25 +41,97 @@ def fake_start_response(*args, **kwargs): pass -class TestStaticLargeObject(unittest.TestCase): +class FakeSwift(object): + def __init__(self): + self.calls = [] + self.req_method_paths = [] + self.uploaded = {} + # mapping of (method, path) --> (response class, headers, body) + self._responses = {} + def __call__(self, env, start_response): + method = env['REQUEST_METHOD'] + path = env['PATH_INFO'] + _, acc, cont, obj = split_path(env['PATH_INFO'], 0, 4, + rest_with_last=True) + + self.calls.append((method, path)) + + try: + resp_class, raw_headers, body = self._responses[(method, path)] + headers = swob.HeaderKeyDict(raw_headers) + except KeyError: + if method == 'GET' and obj and path in self.uploaded: + resp_class = swob.HTTPOk + headers, body = self.uploaded[path] + else: + raise + + # simulate object PUT + if method == 'PUT' and obj: + input = env['wsgi.input'].read() + etag = md5(input).hexdigest() + headers.setdefault('Etag', etag) + headers.setdefault('Content-Length', len(input)) + + # keep it for subsequent GET requests later + self.uploaded[path] = (deepcopy(headers), input) + if "CONTENT_TYPE" in env: + self.uploaded[path][0]['Content-Type'] = env["CONTENT_TYPE"] + + return resp_class(headers=headers, body=body)(env, start_response) + + @property + def call_count(self): + return len(self.calls) + + def register(self, method, path, response_class, headers, body): + self._responses[(method, path)] = (response_class, headers, body) + + +class SloTestCase(unittest.TestCase): def setUp(self): - self.app = FakeApp() + self.app = FakeSwift() self.slo = slo.filter_factory({})(self.app) self.slo.min_segment_size = 1 - def tearDown(self): - pass + def call_app(self, req, app=None): + if app is None: + app = self.app + + status = [None] + headers = [None] + + def start_response(s, h, ei=None): + status[0] = s + headers[0] = h + + body = ''.join(app(req.environ, start_response)) + return status[0], headers[0], body + + def call_slo(self, req): + return self.call_app(req, app=self.slo) + + +class TestSloMiddleware(SloTestCase): + + def setUp(self): + super(TestSloMiddleware, self).setUp() + + self.app.register( + 'GET', '/', swob.HTTPOk, {}, 'passed') + self.app.register( + 'PUT', '/', swob.HTTPOk, {}, 'passed') def test_handle_multipart_no_obj(self): req = Request.blank('/') resp_iter = self.slo(req.environ, fake_start_response) - self.assertEquals(self.app.calls, 1) + self.assertEquals(self.app.calls, [('GET', '/')]) self.assertEquals(''.join(resp_iter), 'passed') def test_slo_header_assigned(self): req = Request.blank( - '/v/a/c/o', headers={'x-static-large-object': "true"}) + '/v1/a/c/o', headers={'x-static-large-object': "true"}) resp = self.slo(req.environ, fake_start_response) self.assert_( resp[0].startswith('X-Static-Large-Object is a reserved header')) @@ -212,11 +144,62 @@ class TestStaticLargeObject(unittest.TestCase): self.assertEquals('/cont/object', slo.parse_input(data)[0]['path']) - bad_data = json.dumps([{'path': '/cont/object', 'size_bytes': 100}]) - self.assertRaises(HTTPException, slo.parse_input, bad_data) + +class TestSloPutManifest(SloTestCase): + + def setUp(self): + super(TestSloPutManifest, self).setUp() + + self.app.register( + 'GET', '/', swob.HTTPOk, {}, 'passed') + self.app.register( + 'PUT', '/', swob.HTTPOk, {}, 'passed') + + self.app.register( + 'HEAD', '/v1/AUTH_test/cont/object', + swob.HTTPOk, + {'Content-Length': '100', 'Etag': 'etagoftheobjectsegment'}, + None) + self.app.register( + 'HEAD', '/v1/AUTH_test/cont/object\xe2\x99\xa1', + swob.HTTPOk, + {'Content-Length': '100', 'Etag': 'etagoftheobjectsegment'}, + None) + self.app.register( + 'HEAD', '/v1/AUTH_test/cont/small_object', + swob.HTTPOk, + {'Content-Length': '10', 'Etag': 'etagoftheobjectsegment'}, + None) + self.app.register( + 'PUT', '/v1/AUTH_test/c/man', swob.HTTPCreated, {}, None) + self.app.register( + 'DELETE', '/v1/AUTH_test/c/man', swob.HTTPNoContent, {}, None) + + self.app.register( + 'HEAD', '/v1/AUTH_test/checktest/a_1', + swob.HTTPOk, + {'Content-Length': '1', 'Etag': 'a'}, + None) + self.app.register( + 'HEAD', '/v1/AUTH_test/checktest/badreq', + swob.HTTPBadRequest, {}, None) + self.app.register( + 'HEAD', '/v1/AUTH_test/checktest/b_2', + swob.HTTPOk, + {'Content-Length': '2', 'Etag': 'b', + 'Last-Modified': 'Fri, 01 Feb 2012 20:38:36 GMT'}, + None) + self.app.register( + 'HEAD', '/v1/AUTH_test/checktest/slob', + swob.HTTPOk, + {'X-Static-Large-Object': 'true', 'Etag': 'slob-etag'}, + None) + + self.app.register( + 'PUT', '/v1/AUTH_test/checktest/man_3', swob.HTTPCreated, {}, None) def test_put_manifest_too_quick_fail(self): - req = Request.blank('/v/a/c/o') + req = Request.blank('/v1/a/c/o') req.content_length = self.slo.max_manifest_size + 1 try: self.slo.handle_multipart_put(req, fake_start_response) @@ -225,7 +208,7 @@ class TestStaticLargeObject(unittest.TestCase): self.assertEquals(e.status_int, 413) with patch.object(self.slo, 'max_manifest_segments', 0): - req = Request.blank('/v/a/c/o', body=test_json_data) + req = Request.blank('/v1/a/c/o', body=test_json_data) e = None try: self.slo.handle_multipart_put(req, fake_start_response) @@ -234,14 +217,14 @@ class TestStaticLargeObject(unittest.TestCase): self.assertEquals(e.status_int, 413) with patch.object(self.slo, 'min_segment_size', 1000): - req = Request.blank('/v/a/c/o', body=test_json_data) + req = Request.blank('/v1/a/c/o', body=test_json_data) try: self.slo.handle_multipart_put(req, fake_start_response) except HTTPException as e: pass self.assertEquals(e.status_int, 400) - req = Request.blank('/v/a/c/o', headers={'X-Copy-From': 'lala'}) + req = Request.blank('/v1/a/c/o', headers={'X-Copy-From': 'lala'}) try: self.slo.handle_multipart_put(req, fake_start_response) except HTTPException as e: @@ -258,7 +241,7 @@ class TestStaticLargeObject(unittest.TestCase): def test_handle_multipart_put_success(self): req = Request.blank( - '/test_good/AUTH_test/c/man?multipart-manifest=put', + '/v1/AUTH_test/c/man?multipart-manifest=put', environ={'REQUEST_METHOD': 'PUT'}, headers={'Accept': 'test'}, body=test_json_data) self.assertTrue('X-Static-Large-Object' not in req.headers) @@ -279,7 +262,7 @@ class TestStaticLargeObject(unittest.TestCase): 'etag': 'etagoftheobjectsegment', 'size_bytes': 10}]) req = Request.blank( - '/test_good/AUTH_test/c/man?multipart-manifest=put', + '/v1/AUTH_test/c/man?multipart-manifest=put', environ={'REQUEST_METHOD': 'PUT'}, headers={'Accept': 'test'}, body=test_json_data) self.assertTrue('X-Static-Large-Object' not in req.headers) @@ -291,13 +274,13 @@ class TestStaticLargeObject(unittest.TestCase): 'etag': 'etagoftheobjectsegment', 'size_bytes': 100}]) req = Request.blank( - '/test_good/AUTH_test/c/man?multipart-manifest=put', + '/v1/AUTH_test/c/man?multipart-manifest=put', environ={'REQUEST_METHOD': 'PUT'}, headers={'Accept': 'test'}, body=test_json_data) self.assertTrue('X-Static-Large-Object' not in req.headers) self.slo(req.environ, fake_start_response) self.assertTrue('X-Static-Large-Object' in req.headers) - self.assertTrue(req.environ['PATH_INFO'], '/cont/object\xe2\x99\xa4') + self.assertTrue(req.environ['PATH_INFO'], '/cont/object\xe2\x99\xa1') def test_handle_multipart_put_no_xml(self): req = Request.blank( @@ -322,9 +305,9 @@ class TestStaticLargeObject(unittest.TestCase): 'size_bytes': 100}]), json.dumps('asdf'), json.dumps(None), json.dumps(5), 'not json', '1234', None, '', json.dumps({'path': None}), - json.dumps([{'path': '/c/o', 'etag': None, + json.dumps([{'path': '/cont/object', 'etag': None, 'size_bytes': 12}]), - json.dumps([{'path': '/c/o', 'etag': 'asdf', + json.dumps([{'path': '/cont/object', 'etag': 'asdf', 'size_bytes': 'sd'}]), json.dumps([{'path': 12, 'etag': 'etagoftheobj', 'size_bytes': 100}]), @@ -335,22 +318,29 @@ class TestStaticLargeObject(unittest.TestCase): json.dumps([{'path': None, 'etag': 'etagoftheobj', 'size_bytes': 100}])]: req = Request.blank( - '/test_good/AUTH_test/c/man?multipart-manifest=put', + '/v1/AUTH_test/c/man?multipart-manifest=put', environ={'REQUEST_METHOD': 'PUT'}, body=bad_data) self.assertRaises(HTTPException, self.slo.handle_multipart_put, req, fake_start_response) def test_handle_multipart_put_check_data(self): good_data = json.dumps( - [{'path': '/c/a_1', 'etag': 'a', 'size_bytes': '1'}, - {'path': '/d/b_2', 'etag': 'b', 'size_bytes': '2'}]) + [{'path': '/checktest/a_1', 'etag': 'a', 'size_bytes': '1'}, + {'path': '/checktest/b_2', 'etag': 'b', 'size_bytes': '2'}]) req = Request.blank( - '/test_good_check/A/c/man_3?multipart-manifest=put', + '/v1/AUTH_test/checktest/man_3?multipart-manifest=put', environ={'REQUEST_METHOD': 'PUT'}, body=good_data) - self.slo.handle_multipart_put(req, fake_start_response) - self.assertEquals(self.app.calls, 3) - self.assert_(req.environ['CONTENT_TYPE'].endswith(';swift_bytes=3')) - manifest_data = json.loads(req.environ['wsgi.input'].read()) + status, headers, body = self.call_slo(req) + self.assertEquals(self.app.call_count, 3) + + # go behind SLO's back and see what actually got stored + req = Request.blank( + '/v1/AUTH_test/checktest/man_3?multipart-manifest=get', + environ={'REQUEST_METHOD': 'GET'}) + status, headers, body = self.call_app(req) + headers = dict(headers) + manifest_data = json.loads(body) + self.assert_(headers['Content-Type'].endswith(';swift_bytes=3')) self.assertEquals(len(manifest_data), 2) self.assertEquals(manifest_data[0]['hash'], 'a') self.assertEquals(manifest_data[0]['bytes'], 1) @@ -359,49 +349,156 @@ class TestStaticLargeObject(unittest.TestCase): def test_handle_multipart_put_check_data_bad(self): bad_data = json.dumps( - [{'path': '/c/a_1', 'etag': 'a', 'size_bytes': '1'}, - {'path': '/c/a_2', 'etag': 'a', 'size_bytes': '1'}, - {'path': '/d/b_2', 'etag': 'b', 'size_bytes': '2'}, - {'path': '/d/slob', 'etag': 'a', 'size_bytes': '2'}]) + [{'path': '/checktest/a_1', 'etag': 'a', 'size_bytes': '2'}, + {'path': '/checktest/badreq', 'etag': 'a', 'size_bytes': '1'}, + {'path': '/checktest/b_2', 'etag': 'not-b', 'size_bytes': '2'}, + {'path': '/checktest/slob', 'etag': 'not-slob', + 'size_bytes': '2'}]) req = Request.blank( - '/test_good/A/c/man?multipart-manifest=put', + '/v1/AUTH_test/checktest/man?multipart-manifest=put', environ={'REQUEST_METHOD': 'PUT'}, headers={'Accept': 'application/json'}, body=bad_data) - try: - self.slo.handle_multipart_put(req, fake_start_response) - except HTTPException as e: - self.assertEquals(self.app.calls, 4) - data = json.loads(e.body) - errors = data['Errors'] - self.assertEquals(errors[0][0], '/c/a_1') - self.assertEquals(errors[0][1], 'Size Mismatch') - self.assertEquals(errors[2][0], '/c/a_2') - self.assertEquals(errors[2][1], '400 Bad Request') - self.assertEquals(errors[4][0], '/d/b_2') - self.assertEquals(errors[4][1], 'Etag Mismatch') - self.assertEquals(errors[-1][0], '/d/slob') - self.assertEquals(errors[-1][1], 'Etag Mismatch') - else: - self.assert_(False) + + status, headers, body = self.call_slo(req) + self.assertEquals(self.app.call_count, 4) + errors = json.loads(body)['Errors'] + self.assertEquals(len(errors), 5) + self.assertEquals(errors[0][0], '/checktest/a_1') + self.assertEquals(errors[0][1], 'Size Mismatch') + self.assertEquals(errors[1][0], '/checktest/badreq') + self.assertEquals(errors[1][1], '400 Bad Request') + self.assertEquals(errors[2][0], '/checktest/b_2') + self.assertEquals(errors[2][1], 'Etag Mismatch') + self.assertEquals(errors[3][0], '/checktest/slob') + self.assertEquals(errors[3][1], 'Size Mismatch') + self.assertEquals(errors[4][0], '/checktest/slob') + self.assertEquals(errors[4][1], 'Etag Mismatch') + + +class TestSloDeleteManifest(SloTestCase): + + def setUp(self): + super(TestSloDeleteManifest, self).setUp() + + _submanifest_data = json.dumps( + [{'name': '/deltest/b_2', 'hash': 'a', 'bytes': '1'}, + {'name': '/deltest/c_3', 'hash': 'b', 'bytes': '2'}]) + + self.app.register( + 'GET', '/v1/AUTH_test/deltest/man_404', + swob.HTTPNotFound, {}, None) + self.app.register( + 'GET', '/v1/AUTH_test/deltest/man', + swob.HTTPOk, {'Content-Type': 'application/json', + 'X-Static-Large-Object': 'true'}, + json.dumps([{'name': '/deltest/gone', 'hash': 'a', 'bytes': '1'}, + {'name': '/deltest/b_2', 'hash': 'b', 'bytes': '2'}])) + self.app.register( + 'DELETE', '/v1/AUTH_test/deltest/man', + swob.HTTPNoContent, {}, None) + self.app.register( + 'GET', '/v1/AUTH_test/deltest/man-all-there', + swob.HTTPOk, {'Content-Type': 'application/json', + 'X-Static-Large-Object': 'true'}, + json.dumps([{'name': '/deltest/b_2', 'hash': 'a', 'bytes': '1'}, + {'name': '/deltest/c_3', 'hash': 'b', 'bytes': '2'}])) + self.app.register( + 'DELETE', '/v1/AUTH_test/deltest/gone', + swob.HTTPNotFound, {}, None) + self.app.register( + 'GET', '/v1/AUTH_test/deltest/a_1', + swob.HTTPOk, {'Content-Length': '1'}, 'a') + self.app.register( + 'DELETE', '/v1/AUTH_test/deltest/a_1', + swob.HTTPNoContent, {}, None) + self.app.register( + 'DELETE', '/v1/AUTH_test/deltest/b_2', + swob.HTTPNoContent, {}, None) + self.app.register( + 'DELETE', '/v1/AUTH_test/deltest/c_3', + swob.HTTPNoContent, {}, None) + self.app.register( + 'DELETE', '/v1/AUTH_test/deltest/d_3', + swob.HTTPNoContent, {}, None) + + self.app.register( + 'GET', '/v1/AUTH_test/deltest/manifest-with-submanifest', + swob.HTTPOk, {'Content-Type': 'application/json', + 'X-Static-Large-Object': 'true'}, + json.dumps([{'name': '/deltest/a_1', + 'hash': 'a', 'bytes': '1'}, + {'name': '/deltest/submanifest', 'sub_slo': True, + 'hash': 'submanifest-etag', + 'bytes': len(_submanifest_data)}, + {'name': '/deltest/d_3', + 'hash': 'd', 'bytes': '3'}])) + self.app.register( + 'DELETE', '/v1/AUTH_test/deltest/manifest-with-submanifest', + swob.HTTPNoContent, {}, None) + + self.app.register( + 'GET', '/v1/AUTH_test/deltest/submanifest', + swob.HTTPOk, {'Content-Type': 'application/json', + 'X-Static-Large-Object': 'true'}, + _submanifest_data) + self.app.register( + 'DELETE', '/v1/AUTH_test/deltest/submanifest', + swob.HTTPNoContent, {}, None) + + self.app.register( + 'GET', '/v1/AUTH_test/deltest/manifest-missing-submanifest', + swob.HTTPOk, {'Content-Type': 'application/json', + 'X-Static-Large-Object': 'true'}, + json.dumps([{'name': '/deltest/a_1', 'hash': 'a', 'bytes': '1'}, + {'name': '/deltest/missing-submanifest', + 'hash': 'a', 'bytes': '2', 'sub_slo': True}, + {'name': '/deltest/d_3', 'hash': 'd', 'bytes': '3'}])) + self.app.register( + 'DELETE', '/v1/AUTH_test/deltest/manifest-missing-submanifest', + swob.HTTPNoContent, {}, None) + + self.app.register( + 'GET', '/v1/AUTH_test/deltest/missing-submanifest', + swob.HTTPNotFound, {}, None) + + self.app.register( + 'GET', '/v1/AUTH_test/deltest/manifest-badjson', + swob.HTTPOk, {'Content-Type': 'application/json', + 'X-Static-Large-Object': 'true'}, + "[not {json (at ++++all") + + self.app.register( + 'GET', '/v1/AUTH_test/deltest/manifest-with-unauth-segment', + swob.HTTPOk, {'Content-Type': 'application/json', + 'X-Static-Large-Object': 'true'}, + json.dumps([{'name': '/deltest/a_1', 'hash': 'a', 'bytes': '1'}, + {'name': '/deltest-unauth/q_17', + 'hash': '11', 'bytes': '17'}])) + self.app.register( + 'DELETE', '/v1/AUTH_test/deltest/manifest-with-unauth-segment', + swob.HTTPNoContent, {}, None) + + self.app.register( + 'DELETE', '/v1/AUTH_test/deltest-unauth/q_17', + swob.HTTPUnauthorized, {}, None) def test_handle_multipart_delete_man(self): req = Request.blank( - '/test_good/A/c/man', environ={'REQUEST_METHOD': 'DELETE'}) + '/v1/AUTH_test/deltest/man', + environ={'REQUEST_METHOD': 'DELETE'}) self.slo(req.environ, fake_start_response) - self.assertEquals(self.app.calls, 1) + self.assertEquals(self.app.call_count, 1) def test_handle_multipart_delete_whole_404(self): req = Request.blank( - '/test_delete_404/A/c/man_404?multipart-manifest=delete', + '/v1/AUTH_test/deltest/man_404?multipart-manifest=delete', environ={'REQUEST_METHOD': 'DELETE', 'HTTP_ACCEPT': 'application/json'}) - app_iter = self.slo(req.environ, fake_start_response) - app_iter = list(app_iter) # iterate through whole response - resp_data = json.loads(app_iter[0]) - self.assertEquals(self.app.calls, 1) - self.assertEquals(self.app.req_method_paths, - [('GET', '/test_delete_404/A/c/man_404')]) + status, response, body = self.call_slo(req) + resp_data = json.loads(body) + self.assertEquals(self.app.calls, + [('GET', '/v1/AUTH_test/deltest/man_404')]) self.assertEquals(resp_data['Response Status'], '200 OK') self.assertEquals(resp_data['Response Body'], '') self.assertEquals(resp_data['Number Deleted'], 0) @@ -410,68 +507,66 @@ class TestStaticLargeObject(unittest.TestCase): def test_handle_multipart_delete_segment_404(self): req = Request.blank( - '/test_delete_404/A/c/man?multipart-manifest=delete', + '/v1/AUTH_test/deltest/man?multipart-manifest=delete', environ={'REQUEST_METHOD': 'DELETE', 'HTTP_ACCEPT': 'application/json'}) - app_iter = self.slo(req.environ, fake_start_response) - app_iter = list(app_iter) # iterate through whole response - resp_data = json.loads(app_iter[0]) - self.assertEquals(self.app.calls, 4) - self.assertEquals(self.app.req_method_paths, - [('GET', '/test_delete_404/A/c/man'), - ('DELETE', '/test_delete_404/A/c/a_1'), - ('DELETE', '/test_delete_404/A/d/b_2'), - ('DELETE', '/test_delete_404/A/c/man')]) + status, response, body = self.call_slo(req) + resp_data = json.loads(body) + self.assertEquals(self.app.calls, + [('GET', '/v1/AUTH_test/deltest/man'), + ('DELETE', '/v1/AUTH_test/deltest/gone'), + ('DELETE', '/v1/AUTH_test/deltest/b_2'), + ('DELETE', '/v1/AUTH_test/deltest/man')]) self.assertEquals(resp_data['Response Status'], '200 OK') self.assertEquals(resp_data['Number Deleted'], 2) self.assertEquals(resp_data['Number Not Found'], 1) def test_handle_multipart_delete_whole(self): req = Request.blank( - '/test_delete/A/c/man?multipart-manifest=delete', + '/v1/AUTH_test/deltest/man-all-there?multipart-manifest=delete', environ={'REQUEST_METHOD': 'DELETE'}) - app_iter = self.slo(req.environ, fake_start_response) - list(app_iter) # iterate through whole response - self.assertEquals(self.app.calls, 4) - self.assertEquals(self.app.req_method_paths, - [('GET', '/test_delete/A/c/man'), - ('DELETE', '/test_delete/A/c/a_1'), - ('DELETE', '/test_delete/A/d/b_2'), - ('DELETE', '/test_delete/A/c/man')]) + self.call_slo(req) + self.assertEquals(self.app.calls, + [('GET', '/v1/AUTH_test/deltest/man-all-there'), + ('DELETE', '/v1/AUTH_test/deltest/b_2'), + ('DELETE', '/v1/AUTH_test/deltest/c_3'), + ('DELETE', '/v1/AUTH_test/deltest/man-all-there')]) def test_handle_multipart_delete_nested(self): req = Request.blank( - '/test_delete_nested/A/c/man?multipart-manifest=delete', + '/v1/AUTH_test/deltest/manifest-with-submanifest?' + + 'multipart-manifest=delete', environ={'REQUEST_METHOD': 'DELETE'}) - app_iter = self.slo(req.environ, fake_start_response) - list(app_iter) # iterate through whole response - self.assertEquals(self.app.calls, 8) + self.call_slo(req) self.assertEquals( - set(self.app.req_method_paths), - set([('GET', '/test_delete_nested/A/c/man'), - ('GET', '/test_delete_nested/A/a/sub_nest'), - ('DELETE', '/test_delete_nested/A/a/a_1'), - ('DELETE', '/test_delete_nested/A/b/b_2'), - ('DELETE', '/test_delete_nested/A/c/c_3'), - ('DELETE', '/test_delete_nested/A/a/sub_nest'), - ('DELETE', '/test_delete_nested/A/d/d_3'), - ('DELETE', '/test_delete_nested/A/c/man')])) + set(self.app.calls), + set([('GET', '/v1/AUTH_test/deltest/manifest-with-submanifest'), + ('GET', '/v1/AUTH_test/deltest/submanifest'), + ('DELETE', '/v1/AUTH_test/deltest/a_1'), + ('DELETE', '/v1/AUTH_test/deltest/b_2'), + ('DELETE', '/v1/AUTH_test/deltest/c_3'), + ('DELETE', '/v1/AUTH_test/deltest/submanifest'), + ('DELETE', '/v1/AUTH_test/deltest/d_3'), + ('DELETE', '/v1/AUTH_test/deltest/' + + 'manifest-with-submanifest')])) def test_handle_multipart_delete_nested_404(self): req = Request.blank( - '/test_delete_nested_404/A/c/man?multipart-manifest=delete', + '/v1/AUTH_test/deltest/manifest-missing-submanifest' + + '?multipart-manifest=delete', environ={'REQUEST_METHOD': 'DELETE', 'HTTP_ACCEPT': 'application/json'}) - app_iter = self.slo(req.environ, fake_start_response) - app_iter = list(app_iter) # iterate through whole response - resp_data = json.loads(app_iter[0]) - self.assertEquals(self.app.calls, 5) - self.assertEquals(self.app.req_method_paths, - [('GET', '/test_delete_nested_404/A/c/man'), - ('DELETE', '/test_delete_nested_404/A/a/a_1'), - ('GET', '/test_delete_nested_404/A/a/sub_nest'), - ('DELETE', '/test_delete_nested_404/A/d/d_3'), - ('DELETE', '/test_delete_nested_404/A/c/man')]) + status, response, body = self.call_slo(req) + resp_data = json.loads(body) + self.assertEquals(self.app.calls, + [('GET', '/v1/AUTH_test/deltest/' + + 'manifest-missing-submanifest'), + ('DELETE', '/v1/AUTH_test/deltest/a_1'), + ('GET', '/v1/AUTH_test/deltest/' + + 'missing-submanifest'), + ('DELETE', '/v1/AUTH_test/deltest/d_3'), + ('DELETE', '/v1/AUTH_test/deltest/' + + 'manifest-missing-submanifest')]) self.assertEquals(resp_data['Response Status'], '200 OK') self.assertEquals(resp_data['Response Body'], '') self.assertEquals(resp_data['Number Deleted'], 3) @@ -480,60 +575,59 @@ class TestStaticLargeObject(unittest.TestCase): def test_handle_multipart_delete_not_a_manifest(self): req = Request.blank( - '/test_delete_bad_man/A/c/man?multipart-manifest=delete', + '/v1/AUTH_test/deltest/a_1?multipart-manifest=delete', environ={'REQUEST_METHOD': 'DELETE', 'HTTP_ACCEPT': 'application/json'}) - app_iter = self.slo(req.environ, fake_start_response) - app_iter = list(app_iter) # iterate through whole response - resp_data = json.loads(app_iter[0]) - self.assertEquals(self.app.calls, 1) - self.assertEquals(self.app.req_method_paths, - [('GET', '/test_delete_bad_man/A/c/man')]) + status, response, body = self.call_slo(req) + resp_data = json.loads(body) + self.assertEquals(self.app.calls, + [('GET', '/v1/AUTH_test/deltest/a_1')]) self.assertEquals(resp_data['Response Status'], '400 Bad Request') self.assertEquals(resp_data['Response Body'], '') self.assertEquals(resp_data['Number Deleted'], 0) self.assertEquals(resp_data['Number Not Found'], 0) self.assertEquals(resp_data['Errors'], - [['/c/man', 'Not an SLO manifest']]) + [['/deltest/a_1', 'Not an SLO manifest']]) def test_handle_multipart_delete_bad_json(self): req = Request.blank( - '/test_delete_bad_json/A/c/man?multipart-manifest=delete', + '/v1/AUTH_test/deltest/manifest-badjson?multipart-manifest=delete', environ={'REQUEST_METHOD': 'DELETE', 'HTTP_ACCEPT': 'application/json'}) - app_iter = self.slo(req.environ, fake_start_response) - app_iter = list(app_iter) # iterate through whole response - resp_data = json.loads(app_iter[0]) - self.assertEquals(self.app.calls, 1) - self.assertEquals(self.app.req_method_paths, - [('GET', '/test_delete_bad_json/A/c/man')]) + status, response, body = self.call_slo(req) + resp_data = json.loads(body) + self.assertEquals(self.app.calls, + [('GET', '/v1/AUTH_test/deltest/manifest-badjson')]) self.assertEquals(resp_data['Response Status'], '400 Bad Request') self.assertEquals(resp_data['Response Body'], '') self.assertEquals(resp_data['Number Deleted'], 0) self.assertEquals(resp_data['Number Not Found'], 0) self.assertEquals(resp_data['Errors'], - [['/c/man', 'Unable to load SLO manifest']]) + [['/deltest/manifest-badjson', + 'Unable to load SLO manifest']]) def test_handle_multipart_delete_401(self): req = Request.blank( - '/test_delete_401/A/c/man?multipart-manifest=delete', + '/v1/AUTH_test/deltest/manifest-with-unauth-segment' + + '?multipart-manifest=delete', environ={'REQUEST_METHOD': 'DELETE', 'HTTP_ACCEPT': 'application/json'}) - app_iter = self.slo(req.environ, fake_start_response) - app_iter = list(app_iter) # iterate through whole response - resp_data = json.loads(app_iter[0]) - self.assertEquals(self.app.calls, 4) - self.assertEquals(self.app.req_method_paths, - [('GET', '/test_delete_401/A/c/man'), - ('DELETE', '/test_delete_401/A/c/a_1'), - ('DELETE', '/test_delete_401/A/d/b_2'), - ('DELETE', '/test_delete_401/A/c/man')]) + status, response, body = self.call_slo(req) + resp_data = json.loads(body) + self.assertEquals(self.app.calls, + [('GET', '/v1/AUTH_test/deltest/' + + 'manifest-with-unauth-segment'), + ('DELETE', '/v1/AUTH_test/deltest/a_1'), + ('DELETE', '/v1/AUTH_test/deltest-unauth/q_17'), + ('DELETE', '/v1/AUTH_test/deltest/' + + 'manifest-with-unauth-segment')]) self.assertEquals(resp_data['Response Status'], '400 Bad Request') self.assertEquals(resp_data['Response Body'], '') self.assertEquals(resp_data['Number Deleted'], 2) self.assertEquals(resp_data['Number Not Found'], 0) self.assertEquals(resp_data['Errors'], - [['/d/b_2', '401 Unauthorized']]) + [['/deltest-unauth/q_17', '401 Unauthorized']]) + if __name__ == '__main__': unittest.main()