Files
swift/test/unit/common/middleware/test_helpers.py
Alistair Coles a740591883 Tighten up encrypter tests re. request footers
The encrypter middleware uses an update_footers callback to send
request footers. Previously, FakeSwift combined footers with captured
request headers in a single dict. Tests could not therefore
specifically assert that *footers* had been captured rather than
headers.

This patch modifies FakeSwift to capture footers separately for each
request. Footers are still merged with the request headers in order to
synthesise GET or HEAD response headers when a previously uploaded
object is returned.

Unfortunately the change cannot be as simple as adding another
attribute to the FakeSwiftCall namedtuple. A list of these namedtuples
is returned by FakeSwift.calls_with_headers. Some tests cast the
namedtuples to 3-tuples and will break if the length of the namedtuple
changes.  Other tests access the attributes of the namedtuples by name
and will break if the list values are changed to plain 3-tuples.

Some test churn is therefore inevitable:

* FakeSwiftCall is changed from a namedtuple to a class. This prevents
  future tests assuming it is a fixed length tuple. It also supports a
  headers_and_footers property to return the combination of uploaded
  headers and footer that was previously (confusingly) returned by
  FakeSwiftCall.headers.

* A new property FakeSwift.call_list has been added which returns a
  list of FakeSwiftCalls.

* FakeSwift.calls_with_headers now returns a 3-tuple. Tests that
  previously assumed this was a namedtuple have been changed to use
  FakeSwift.call_list instead, which gives them objects with the same
  named attributes as the previous namedtuple. Tests that previously
  treated the namedtuple as a 3-tuple do not need to be changed.

* Tests that access the 'private' FakeSwift._calls have been changed
  to use FakeSwift.call_list.

Change-Id: If24b6fa50f1d67a7bbbf9a1794c70d37c41971f7
2025-02-11 10:30:52 +00:00

932 lines
39 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (c) 2023 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import unittest
from swift.common.storage_policy import POLICIES
from swift.common.swob import Request, HTTPOk, HTTPNotFound, \
HTTPCreated, HeaderKeyDict
from swift.common import request_helpers as rh
from swift.common.middleware.s3api.utils import sysmeta_header
from test.unit.common.middleware.helpers import FakeSwift, FakeSwiftCall
class TestFakeSwiftCall(unittest.TestCase):
def test_init(self):
req = Request.blank('/v1/a/c/o', headers={'Content-Length': '123'})
call = FakeSwiftCall(req)
self.assertEqual('GET', call.method)
self.assertEqual('/v1/a/c/o', call.path)
self.assertEqual({'Host': 'localhost:80',
'Content-Length': '123'},
call.headers)
self.assertIsInstance(call.footers, HeaderKeyDict)
self.assertEqual({}, call.footers)
def test_header_mutation(self):
orig_headers = {'foo': 'bar'}
req = Request.blank('/v1/a/c/o',
headers=orig_headers)
call = FakeSwiftCall(req)
self.assertEqual('bar', call.headers['foo'])
req.headers['foo'] = 'baz'
self.assertEqual('bar', call.headers['foo'])
class TestFakeSwift(unittest.TestCase):
def test_call_accessors(self):
swift = FakeSwift()
swift.register('GET', '/v1/a/c/o', HTTPOk, {'X-Foo': 'Bar'}, b'stuff')
req = Request.blank('/v1/a/c/o', headers={'Host': 'localhost:80'})
req.method = 'GET'
req.get_response(swift)
self.assertEqual([('GET', '/v1/a/c/o')], swift.calls)
self.assertEqual([{'Host': 'localhost:80'}], swift.headers)
self.assertEqual([('GET', '/v1/a/c/o', {'Host': 'localhost:80'})],
swift.calls_with_headers)
def test_allowed_methods(self):
def do_test(swift, method, exp_status):
path = '/v1/a/c/o'
swift.register(method, path, HTTPOk, {}, None)
req = Request.blank(path)
req.method = method
self.assertEqual(exp_status, req.get_response(swift).status_int)
for method in ('PUT', 'POST', 'DELETE', 'GET', 'HEAD', 'OPTIONS',
'REPLICATE', 'SSYNC', 'UPDATE'):
do_test(FakeSwift(), method, 200)
do_test(FakeSwift(), 'TEST', 405)
do_test(FakeSwift(), 'get', 405)
def test_not_registered(self):
swift = FakeSwift()
def do_test(method):
req = Request.blank('/v1/a/c/o')
req.method = method
with self.assertRaises(KeyError):
req.get_response(swift)
do_test('GET')
do_test('HEAD')
do_test('POST')
do_test('PUT')
do_test('DELETE')
def test_capture_unexpected_calls(self):
req = Request.blank('/v1/a/c/o')
req.method = 'GET'
swift = FakeSwift()
with self.assertRaises(KeyError):
req.get_response(swift)
self.assertEqual([('GET', '/v1/a/c/o')], swift.calls)
req = Request.blank('/v1/a/c/o')
req.method = 'GET'
swift = FakeSwift(capture_unexpected_calls=True)
with self.assertRaises(KeyError):
req.get_response(swift)
self.assertEqual([('GET', '/v1/a/c/o')], swift.calls)
req = Request.blank('/v1/a/c/o')
req.method = 'GET'
swift = FakeSwift(capture_unexpected_calls=False)
with self.assertRaises(KeyError):
req.get_response(swift)
self.assertEqual([], swift.calls)
def test_GET_registered(self):
# verify that a single registered GET response is sufficient to handle
# GETs and HEADS, with and without query strings
swift = FakeSwift()
swift.register('GET', '/v1/a/c/o', HTTPOk, {'X-Foo': 'Bar'}, b'stuff')
req = Request.blank('/v1/a/c/o')
req.method = 'GET'
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Content-Type': 'text/html; charset=UTF-8',
'X-Foo': 'Bar'},
resp.headers)
self.assertEqual(b'stuff', resp.body)
self.assertEqual(1, swift.call_count)
self.assertEqual(('GET', '/v1/a/c/o'), swift.calls[-1])
req = Request.blank('/v1/a/c/o')
req.method = 'GET'
req.query_string = 'p=q'
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Content-Type': 'text/html; charset=UTF-8',
'X-Foo': 'Bar'},
resp.headers)
self.assertEqual(b'stuff', resp.body)
self.assertEqual(2, swift.call_count)
self.assertEqual(('GET', '/v1/a/c/o?p=q'), swift.calls[-1])
req = Request.blank('/v1/a/c/o')
req.method = 'HEAD'
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual({'Content-Type': 'text/html; charset=UTF-8',
'X-Foo': 'Bar'},
resp.headers)
self.assertEqual(b'', resp.body)
self.assertEqual(3, swift.call_count)
self.assertEqual(('HEAD', '/v1/a/c/o'), swift.calls[-1])
req = Request.blank('/v1/a/c/o')
req.method = 'HEAD'
req.query_string = 'p=q'
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual({'Content-Type': 'text/html; charset=UTF-8',
'X-Foo': 'Bar'},
resp.headers)
self.assertEqual(b'', resp.body)
self.assertEqual(4, swift.call_count)
self.assertEqual(('HEAD', '/v1/a/c/o?p=q'), swift.calls[-1])
def test_GET_registered_with_query_string(self):
# verify that a registered GET response with query string only matches
# a request with that query string
swift = FakeSwift()
swift.register('GET', '/v1/a/c/o?p=q', HTTPOk,
{'X-Foo': 'Bar'}, b'stuff')
req = Request.blank('/v1/a/c/o') # no query string
req.method = 'GET'
with self.assertRaises(KeyError):
req.get_response(swift)
self.assertEqual(1, swift.call_count)
self.assertEqual([('GET', '/v1/a/c/o')], swift.calls)
req.query_string = 'p=q'
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Content-Type': 'text/html; charset=UTF-8',
'X-Foo': 'Bar'},
resp.headers)
self.assertEqual(b'stuff', resp.body)
self.assertEqual(2, swift.call_count)
self.assertEqual([('GET', '/v1/a/c/o'),
('GET', '/v1/a/c/o?p=q')],
swift.calls)
req.query_string = 'p=z'
with self.assertRaises(KeyError):
req.get_response(swift)
self.assertEqual(3, swift.call_count)
self.assertEqual([('GET', '/v1/a/c/o'),
('GET', '/v1/a/c/o?p=q'),
('GET', '/v1/a/c/o?p=z')],
swift.calls)
def test_GET_and_HEAD_registered(self):
# verify that a registered HEAD response will be preferred over GET for
# HEAD request
swift = FakeSwift()
swift.register('GET', '/v1/a/c/o', HTTPOk, {'X-Foo': 'Bar'}, b'stuff')
swift.register('HEAD', '/v1/a/c/o', HTTPNotFound, {}, b'')
req = Request.blank('/v1/a/c/o')
req.method = 'GET'
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Content-Type': 'text/html; charset=UTF-8',
'X-Foo': 'Bar'},
resp.headers)
self.assertEqual(b'stuff', resp.body)
self.assertEqual(1, swift.call_count)
self.assertEqual(('GET', '/v1/a/c/o'), swift.calls[-1])
req = Request.blank('/v1/a/c/o')
req.method = 'HEAD'
resp = req.get_response(swift)
self.assertEqual(404, resp.status_int)
self.assertEqual({'Content-Type': 'text/html; charset=UTF-8'},
resp.headers)
self.assertEqual(b'', resp.body)
self.assertEqual(2, swift.call_count)
self.assertEqual(('HEAD', '/v1/a/c/o'), swift.calls[-1])
req = Request.blank('/v1/a/c/o')
req.method = 'HEAD'
req.query_string = 'p=q'
resp = req.get_response(swift)
self.assertEqual(404, resp.status_int)
self.assertEqual({'Content-Type': 'text/html; charset=UTF-8'},
resp.headers)
self.assertEqual(b'', resp.body)
self.assertEqual(3, swift.call_count)
self.assertEqual(('HEAD', '/v1/a/c/o?p=q'), swift.calls[-1])
def test_PUT_uploaded(self):
# verify an uploaded object is sufficient to handle GETs and HEADS,
# with and without query strings
swift = FakeSwift()
swift.register('PUT', '/v1/a/c/o', HTTPCreated, {}, None)
req = Request.blank('/v1/a/c/o', body=b'stuff')
req.method = 'PUT'
resp = req.get_response(swift)
self.assertEqual(201, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Etag': 'c13d88cb4cb02003daedb8a84e5d272a',
'Content-Type': 'text/html; charset=UTF-8'},
resp.headers)
self.assertEqual(b'', resp.body)
self.assertEqual(1, swift.call_count)
self.assertEqual(('PUT', '/v1/a/c/o'), swift.calls[-1])
req = Request.blank('/v1/a/c/o')
req.method = 'GET'
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Content-Type': 'text/html; charset=UTF-8',
'Host': 'localhost:80'},
resp.headers)
self.assertEqual(b'stuff', resp.body)
self.assertEqual(2, swift.call_count)
self.assertEqual(('GET', '/v1/a/c/o'), swift.calls[-1])
req = Request.blank('/v1/a/c/o')
req.method = 'GET'
req.query_string = 'p=q'
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Content-Type': 'text/html; charset=UTF-8',
'Host': 'localhost:80'},
resp.headers)
self.assertEqual(b'stuff', resp.body)
self.assertEqual(3, swift.call_count)
self.assertEqual(('GET', '/v1/a/c/o?p=q'), swift.calls[-1])
req = Request.blank('/v1/a/c/o')
req.method = 'HEAD'
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Content-Type': 'text/html; charset=UTF-8',
'Host': 'localhost:80'},
resp.headers)
self.assertEqual(b'', resp.body)
self.assertEqual(4, swift.call_count)
self.assertEqual(('HEAD', '/v1/a/c/o'), swift.calls[-1])
req = Request.blank('/v1/a/c/o')
req.method = 'HEAD'
req.query_string = 'p=q'
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Content-Type': 'text/html; charset=UTF-8',
'Host': 'localhost:80'},
resp.headers)
self.assertEqual(b'', resp.body)
self.assertEqual(5, swift.call_count)
self.assertEqual(('HEAD', '/v1/a/c/o?p=q'), swift.calls[-1])
def test_PUT_uploaded_with_query_string(self):
# verify an uploaded object with query string is sufficient to handle
# GETs and HEADS, with and without query strings
swift = FakeSwift()
swift.register('PUT', '/v1/a/c/o', HTTPCreated, {}, None)
req = Request.blank('/v1/a/c/o', body=b'stuff')
req.method = 'PUT'
req.query_string = 'multipart-manifest=put'
resp = req.get_response(swift)
self.assertEqual(201, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Etag': 'c13d88cb4cb02003daedb8a84e5d272a',
'Content-Type': 'text/html; charset=UTF-8'},
resp.headers)
self.assertEqual(b'', resp.body)
self.assertEqual(1, swift.call_count)
self.assertEqual(('PUT', '/v1/a/c/o?multipart-manifest=put'),
swift.calls[-1])
# note: query string is not included in uploaded key
self.assertEqual(
{'/v1/a/c/o': ({'Host': 'localhost:80',
'Content-Length': '5'},
b'stuff')},
swift.uploaded)
req = Request.blank('/v1/a/c/o')
req.method = 'GET'
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Content-Type': 'text/html; charset=UTF-8',
'Host': 'localhost:80'},
resp.headers)
self.assertEqual(b'stuff', resp.body)
self.assertEqual(2, swift.call_count)
self.assertEqual(('GET', '/v1/a/c/o'), swift.calls[-1])
req = Request.blank('/v1/a/c/o')
req.method = 'GET'
req.query_string = 'p=q' # note: differs from PUT query string
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Content-Type': 'text/html; charset=UTF-8',
'Host': 'localhost:80'},
resp.headers)
self.assertEqual(b'stuff', resp.body)
self.assertEqual(3, swift.call_count)
self.assertEqual(('GET', '/v1/a/c/o?p=q'), swift.calls[-1])
req = Request.blank('/v1/a/c/o')
req.method = 'HEAD'
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Content-Type': 'text/html; charset=UTF-8',
'Host': 'localhost:80'},
resp.headers)
self.assertEqual(b'', resp.body)
self.assertEqual(4, swift.call_count)
self.assertEqual(('HEAD', '/v1/a/c/o'), swift.calls[-1])
req = Request.blank('/v1/a/c/o')
req.method = 'HEAD'
req.query_string = 'p=q'
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Content-Type': 'text/html; charset=UTF-8',
'Host': 'localhost:80'},
resp.headers)
self.assertEqual(b'', resp.body)
self.assertEqual(5, swift.call_count)
self.assertEqual(('HEAD', '/v1/a/c/o?p=q'), swift.calls[-1])
def test_PUT_POST(self):
# verify an uploaded object is updated by a POST
swift = FakeSwift()
swift.register('PUT', '/v1/a/c/o', HTTPCreated, {}, None)
# Note: the POST must be registered
swift.register('POST', '/v1/a/c/o', HTTPCreated, {}, None)
req = Request.blank('/v1/a/c/o', body=b'stuff',
headers={'X-Object-Meta-Foo': 'Bar'})
req.method = 'PUT'
resp = req.get_response(swift)
self.assertEqual(201, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Etag': 'c13d88cb4cb02003daedb8a84e5d272a',
'Content-Type': 'text/html; charset=UTF-8'},
resp.headers)
self.assertEqual(b'', resp.body)
self.assertEqual(1, swift.call_count)
self.assertEqual(('PUT', '/v1/a/c/o'), swift.calls[-1])
self.assertEqual(
{'/v1/a/c/o': ({'Host': 'localhost:80',
'Content-Length': '5',
'X-Object-Meta-Foo': 'Bar'},
b'stuff')},
swift.uploaded)
# POST should update the uploaded object
req = Request.blank('/v1/a/c/o', body=b'stuff',
headers={'X-Object-Meta-Foo': 'Baz'})
req.method = 'POST'
resp = req.get_response(swift)
self.assertEqual(201, resp.status_int)
self.assertEqual({'Content-Type': 'text/html; charset=UTF-8'},
resp.headers)
self.assertEqual(b'', resp.body)
self.assertEqual(2, swift.call_count)
self.assertEqual(('POST', '/v1/a/c/o'), swift.calls[-1])
self.assertEqual(
{'/v1/a/c/o': ({'Host': 'localhost:80',
'Content-Length': '5',
'X-Object-Meta-Foo': 'Baz'},
b'stuff')},
swift.uploaded)
def test_PUT_with_query_string_POST(self):
# verify an uploaded object with query string is updated by a POST
swift = FakeSwift()
swift.register('PUT', '/v1/a/c/o', HTTPCreated, {}, None)
# Note: the POST must be registered
swift.register('POST', '/v1/a/c/o', HTTPCreated, {}, None)
req = Request.blank('/v1/a/c/o', body=b'stuff',
headers={'X-Object-Meta-Foo': 'Bar'})
req.method = 'PUT'
req.query_string = 'p=q'
resp = req.get_response(swift)
self.assertEqual(201, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Etag': 'c13d88cb4cb02003daedb8a84e5d272a',
'Content-Type': 'text/html; charset=UTF-8'},
resp.headers)
self.assertEqual(b'', resp.body)
self.assertEqual(1, swift.call_count)
self.assertEqual(('PUT', '/v1/a/c/o?p=q'), swift.calls[-1])
# note: query string is not included in uploaded key
self.assertEqual(
{'/v1/a/c/o': ({'Host': 'localhost:80',
'Content-Length': '5',
'X-Object-Meta-Foo': 'Bar'},
b'stuff')},
swift.uploaded)
# POST without query string should update the uploaded object
req = Request.blank('/v1/a/c/o', body=b'stuff',
headers={'X-Object-Meta-Foo': 'Baz'})
req.method = 'POST'
resp = req.get_response(swift)
self.assertEqual(201, resp.status_int)
self.assertEqual({'Content-Type': 'text/html; charset=UTF-8'},
resp.headers)
self.assertEqual(b'', resp.body)
self.assertEqual(2, swift.call_count)
self.assertEqual(('POST', '/v1/a/c/o'), swift.calls[-1])
self.assertEqual(
{'/v1/a/c/o': ({'Host': 'localhost:80',
'Content-Length': '5',
'X-Object-Meta-Foo': 'Baz'},
b'stuff')},
swift.uploaded)
# POST with different query string should update the uploaded object
req = Request.blank('/v1/a/c/o', body=b'stuff',
headers={'X-Object-Meta-Foo': 'Bof'})
req.method = 'POST'
req.query_string = 'x=y'
resp = req.get_response(swift)
self.assertEqual(201, resp.status_int)
self.assertEqual({'Content-Type': 'text/html; charset=UTF-8'},
resp.headers)
self.assertEqual(b'', resp.body)
self.assertEqual(3, swift.call_count)
self.assertEqual(('POST', '/v1/a/c/o?x=y'), swift.calls[-1])
self.assertEqual(
{'/v1/a/c/o': ({'Host': 'localhost:80',
'Content-Length': '5',
'X-Object-Meta-Foo': 'Bof'},
b'stuff')},
swift.uploaded)
req = Request.blank('/v1/a/c/o')
req.method = 'GET'
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Content-Type': 'text/html; charset=UTF-8',
'Host': 'localhost:80',
'X-Object-Meta-Foo': 'Bof'},
resp.headers)
self.assertEqual(b'stuff', resp.body)
self.assertEqual(4, swift.call_count)
self.assertEqual(('GET', '/v1/a/c/o'), swift.calls[-1])
def test_PUT_with_footers_callback(self):
def footers_callback(footers):
footers['x-object-sysmeta-foo'] = 'bar'
swift = FakeSwift()
swift.register('PUT', '/v1/a/c/o', HTTPCreated, {}, None)
# Note: the POST must be registered
swift.register('POST', '/v1/a/c/o', HTTPCreated, {}, None)
req = Request.blank('/v1/a/c/o', body=b'stuff',
headers={'X-Object-Meta-Foo': 'Bar'})
req.method = 'PUT'
req.environ['swift.callback.update_footers'] = footers_callback
resp = req.get_response(swift)
self.assertEqual(201, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Etag': 'c13d88cb4cb02003daedb8a84e5d272a',
'Content-Type': 'text/html; charset=UTF-8'},
resp.headers)
self.assertEqual(b'', resp.body)
self.assertEqual(1, swift.call_count)
self.assertEqual(('PUT', '/v1/a/c/o'), swift.calls[0])
self.assertEqual({'Host': 'localhost:80',
'Content-Length': '5',
'X-Object-Meta-Foo': 'Bar'},
swift.call_list[0].headers)
self.assertEqual({'X-Object-Sysmeta-Foo': 'bar'},
swift.call_list[0].footers)
req = Request.blank('/v1/a/c/o')
req.method = 'GET'
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
# resp has headers and footers
self.assertEqual({'Content-Length': '5',
'Content-Type': 'text/html; charset=UTF-8',
'Host': 'localhost:80',
'X-Object-Sysmeta-Foo': 'bar',
'X-Object-Meta-Foo': 'Bar'},
resp.headers)
def test_GET_registered_overrides_uploaded(self):
swift = FakeSwift()
swift.register('PUT', '/v1/a/c/o', HTTPCreated, {}, None)
swift.register('GET', '/v1/a/c/o', HTTPOk, {}, b'not stuff')
req = Request.blank('/v1/a/c/o', body=b'stuff',
headers={'X-Object-Meta-Foo': 'Bar'})
req.method = 'PUT'
resp = req.get_response(swift)
self.assertEqual(201, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Etag': 'c13d88cb4cb02003daedb8a84e5d272a',
'Content-Type': 'text/html; charset=UTF-8'},
resp.headers)
self.assertEqual(b'', resp.body)
self.assertEqual(1, swift.call_count)
self.assertEqual(('PUT', '/v1/a/c/o'), swift.calls[-1])
self.assertEqual(
{'/v1/a/c/o': ({'Host': 'localhost:80',
'Content-Length': '5',
'X-Object-Meta-Foo': 'Bar'},
b'stuff')},
swift.uploaded)
req = Request.blank('/v1/a/c/o')
req.method = 'GET'
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual({'Content-Length': '9',
'Content-Type': 'text/html; charset=UTF-8'},
resp.headers)
self.assertEqual(b'not stuff', resp.body)
self.assertEqual(2, swift.call_count)
self.assertEqual(('GET', '/v1/a/c/o'), swift.calls[-1])
def test_range(self):
swift = FakeSwift()
swift.register('GET', '/v1/a/c/o', HTTPOk, {}, b'stuff')
req = Request.blank('/v1/a/c/o', headers={'Range': 'bytes=0-2'})
resp = req.get_response(swift)
self.assertEqual(206, resp.status_int)
self.assertEqual(b'stu', resp.body)
self.assertEqual('bytes 0-2/5', resp.headers['Content-Range'])
self.assertEqual('bytes=0-2', req.headers.get('Range'))
self.assertEqual('bytes=0-2',
swift.call_list[-1].headers.get('Range'))
def test_range_ignore_range_header(self):
swift = FakeSwift()
swift.register('GET', '/v1/a/c/o', HTTPOk, {
# the value of the matching header doesn't matter
'X-Object-Sysmeta-Magic': 'False'
}, b'stuff')
req = Request.blank('/v1/a/c/o', headers={'Range': 'bytes=0-2'})
rh.update_ignore_range_header(req, 'X-Object-Sysmeta-Magic')
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual(b'stuff', resp.body)
self.assertNotIn('Content-Range', resp.headers)
self.assertEqual('bytes=0-2', req.headers.get('Range'))
self.assertEqual('bytes=0-2',
swift.call_list[-1].headers.get('Range'))
def test_range_ignore_range_header_old_swift(self):
swift = FakeSwift()
swift.can_ignore_range = False
swift.register('GET', '/v1/a/c/o', HTTPOk, {
# the value of the matching header doesn't matter
'X-Object-Sysmeta-Magic': 'False'
}, b'stuff')
req = Request.blank('/v1/a/c/o', headers={'Range': 'bytes=0-2'})
rh.update_ignore_range_header(req, 'X-Object-Sysmeta-Magic')
resp = req.get_response(swift)
self.assertEqual(206, resp.status_int)
self.assertEqual(b'stu', resp.body)
self.assertEqual('bytes 0-2/5', resp.headers['Content-Range'])
self.assertEqual('bytes=0-2', req.headers.get('Range'))
self.assertEqual('bytes=0-2',
swift.call_list[-1].headers.get('Range'))
def test_range_ignore_range_header_ignored(self):
swift = FakeSwift()
# range is only ignored if registered response has matching metadata
swift.register('GET', '/v1/a/c/o', HTTPOk, {}, b'stuff')
req = Request.blank('/v1/a/c/o', headers={'Range': 'bytes=0-2'})
rh.update_ignore_range_header(req, 'X-Object-Sysmeta-Magic')
resp = req.get_response(swift)
self.assertEqual(206, resp.status_int)
self.assertEqual(b'stu', resp.body)
self.assertEqual('bytes 0-2/5', resp.headers['Content-Range'])
self.assertEqual('bytes=0-2', req.headers.get('Range'))
self.assertEqual('bytes=0-2',
swift.call_list[-1].headers.get('Range'))
def test_object_GET_updated_with_storage_policy(self):
swift = FakeSwift()
swift.register('GET', '/v1/a/c/o', HTTPOk, {}, body=b'stuff')
req = Request.blank('/v1/a/c/o')
req.method = 'GET'
self.assertNotIn('X-Backend-Storage-Policy-Index', req.headers)
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Content-Type': 'text/html; charset=UTF-8'},
resp.headers)
self.assertEqual(b'stuff', resp.body)
self.assertEqual(1, swift.call_count)
self.assertEqual(('GET', '/v1/a/c/o'), swift.calls[0])
self.assertEqual(('GET', '/v1/a/c/o',
{'Host': 'localhost:80'}), # from swob
swift.calls_with_headers[0])
# default storage policy is applied...
self.assertEqual(str(int(POLICIES.default)),
req.headers.get('X-Backend-Storage-Policy-Index'))
# register a container with storage policy 99...
swift.register('HEAD', '/v1/a/c', HTTPOk,
{'X-Backend-Storage-Policy-Index': '99'}, None)
req = Request.blank('/v1/a/c/o')
req.method = 'GET'
self.assertNotIn('X-Backend-Storage-Policy-Index', req.headers)
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual({'Content-Length': '5',
'Content-Type': 'text/html; charset=UTF-8'},
resp.headers)
self.assertEqual(b'stuff', resp.body)
self.assertEqual(2, swift.call_count)
self.assertEqual(('GET', '/v1/a/c/o'), swift.calls[1])
self.assertEqual(('GET', '/v1/a/c/o',
{'Host': 'localhost:80'}), # from swob
swift.calls_with_headers[1])
self.assertEqual(
'99', req.headers.get('X-Backend-Storage-Policy-Index'))
class TestFakeSwiftMultipleResponses(unittest.TestCase):
def test_register_response_is_forever(self):
swift = FakeSwift()
swift.register('GET', '/v1/a/c/o',
HTTPOk, {'X-Foo': 'Bar'}, b'stuff')
req = Request.blank('/v1/a/c/o')
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual('Bar', resp.headers['X-Foo'])
# you can get this response as much as you want
for i in range(10):
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual('Bar', resp.headers['X-Foo'])
def test_register_response_is_last_response_wins(self):
swift = FakeSwift()
swift.register('GET', '/v1/a/c/o',
HTTPOk, {'X-Foo': 'Bar'}, b'stuff')
req = Request.blank('/v1/a/c/o')
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual('Bar', resp.headers['X-Foo'])
swift.register('GET', '/v1/a/c/o',
HTTPOk, {'X-Foo': 'Baz'}, b'other')
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual('Baz', resp.headers['X-Foo'])
# you can get this new response as much as you want
for i in range(10):
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual('Baz', resp.headers['X-Foo'])
def test_register_next_response_is_last_response_wins(self):
swift = FakeSwift()
swift.register(
'GET', '/v1/a/c/o',
HTTPOk, {'X-Foo': 'Bar'}, b'stuff')
swift.register_next_response(
'GET', '/v1/a/c/o',
HTTPOk, {'X-Foo': 'Baz'}, b'other')
req = Request.blank('/v1/a/c/o')
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual('Bar', resp.headers['X-Foo'])
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual('Baz', resp.headers['X-Foo'])
# you can get this new response as much as you want
for i in range(10):
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual('Baz', resp.headers['X-Foo'])
def test_register_next_response_keeps_current_registered_response(self):
# we expect test authors will typically 'd register ALL their responses
# before you start calling FakeSwift
swift = FakeSwift()
swift.register(
'GET', '/v1/a/c/o',
HTTPOk, {'X-Foo': 'Bar'}, b'stuff')
req = Request.blank('/v1/a/c/o')
# we get the registered response, obviously
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual('Bar', resp.headers['X-Foo'])
# because before calling register_next_response, no resp are consumed
swift.register_next_response(
'GET', '/v1/a/c/o',
HTTPOk, {'X-Foo': 'Baz'}, b'other')
# so, this is the "current" response, not the *next* response
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual('Bar', resp.headers['X-Foo'])
# the *next* response is the next response
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual('Baz', resp.headers['X-Foo'])
def test_register_next_response_first(self):
# you can just use register_next_response
swift = FakeSwift()
swift.register_next_response(
'GET', '/v1/a/c/o',
HTTPOk, {'X-Foo': 'Bar'}, b'stuff')
swift.register_next_response(
'GET', '/v1/a/c/o',
HTTPOk, {'X-Foo': 'Baz'}, b'other')
req = Request.blank('/v1/a/c/o')
# it works just like you'd called register
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual('Bar', resp.headers['X-Foo'])
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual('Baz', resp.headers['X-Foo'])
# you can get this new response as much as you want
for i in range(10):
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual('Baz', resp.headers['X-Foo'])
def test_register_resets(self):
swift = FakeSwift()
swift.register_next_response(
'GET', '/v1/a/c/o',
HTTPOk, {'X-Foo': 'Bar'}, b'stuff')
req = Request.blank('/v1/a/c/o')
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual('Bar', resp.headers['X-Foo'])
# you can get this response as much as you want
for i in range(10):
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual('Bar', resp.headers['X-Foo'])
# if you call register mid test you immediately reset the resp
swift.register(
'GET', '/v1/a/c/o',
HTTPOk, {'X-Foo': 'Baz'}, b'other')
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual('Baz', resp.headers['X-Foo'])
# you can get this new response as much as you want
for i in range(10):
resp = req.get_response(swift)
self.assertEqual(200, resp.status_int)
self.assertEqual('Baz', resp.headers['X-Foo'])
class TestFakeSwiftStickyHeaders(unittest.TestCase):
def setUp(self):
self.swift = FakeSwift()
self.path = '/v1/AUTH_test/bucket'
def _check_headers(self, method, path, exp_headers):
captured_headers = {}
def start_response(status, resp_headers):
self.assertEqual(status, '200 OK')
captured_headers.update(resp_headers)
env = {'REQUEST_METHOD': method, 'PATH_INFO': path}
body_iter = self.swift(env, start_response)
b''.join(body_iter)
captured_headers.pop('Content-Type')
self.assertEqual(exp_headers, captured_headers)
def test_sticky_headers(self):
sticky_headers = HeaderKeyDict({
sysmeta_header('container', 'acl'): 'test',
'x-container-meta-foo': 'bar',
})
self.swift.update_sticky_response_headers(self.path, sticky_headers)
# register a response for this path with no headers
self.swift.register('GET', self.path, HTTPOk, {}, None)
self._check_headers('HEAD', self.path, sticky_headers)
self._check_headers('GET', self.path, sticky_headers)
# sticky headers are not applied to PUT, POST, DELETE
self.swift.register('PUT', self.path, HTTPOk, {}, None)
self._check_headers('PUT', self.path, {})
self.swift.register('POST', self.path, HTTPOk, {}, None)
self._check_headers('POST', self.path, {})
self.swift.register('DELETE', self.path, HTTPOk, {}, None)
self._check_headers('DELETE', self.path, {})
def test_sticky_headers_match_path(self):
other_path = self.path + '-other'
sticky_headers = HeaderKeyDict({
sysmeta_header('container', 'acl'): 'test',
'x-container-meta-foo': 'bar',
})
sticky_headers_other = HeaderKeyDict({
'x-container-meta-foo': 'other',
})
self.swift.update_sticky_response_headers(self.path, sticky_headers)
self.swift.update_sticky_response_headers(other_path,
sticky_headers_other)
self.swift.register('GET', self.path, HTTPOk, {}, None)
self.swift.register('GET', other_path, HTTPOk, {}, None)
self._check_headers('HEAD', self.path, sticky_headers)
self._check_headers('GET', other_path, sticky_headers_other)
def test_sticky_headers_update(self):
sticky_headers = HeaderKeyDict({
sysmeta_header('container', 'acl'): 'test',
'x-container-meta-foo': 'bar'
})
exp_headers = sticky_headers.copy()
self.swift.update_sticky_response_headers(self.path, sticky_headers)
self.swift.register('HEAD', self.path, HTTPOk, {}, None)
self._check_headers('HEAD', self.path, exp_headers)
# check that FakeSwift made a *copy*
sticky_headers['x-container-meta-foo'] = 'changed'
self._check_headers('HEAD', self.path, exp_headers)
# check existing are updated not replaced
sticky_headers = HeaderKeyDict({
sysmeta_header('container', 'acl'): 'test-modified',
'x-container-meta-bar': 'foo'
})
exp_headers.update(sticky_headers)
self.swift.update_sticky_response_headers(self.path, sticky_headers)
self._check_headers('HEAD', self.path, exp_headers)
def test_sticky_headers_add_to_response_headers(self):
sticky_headers = HeaderKeyDict({
'x-container-meta-foo': 'bar',
})
self.swift.update_sticky_response_headers(self.path, sticky_headers)
# register a response with another header
self.swift.register('HEAD', self.path, HTTPOk, {
'x-backend-storage-policy-index': '1',
}, None)
self._check_headers('HEAD', self.path, HeaderKeyDict({
'x-container-meta-foo': 'bar',
'x-backend-storage-policy-index': '1',
}))
def test_sticky_headers_overwritten_by_response_header(self):
sticky_headers = HeaderKeyDict({
'x-container-meta-foo': 'bar',
'x-backend-storage-policy-index': '0',
})
self.swift.update_sticky_response_headers(self.path, sticky_headers)
# register a response with a different value for a sticky header
self.swift.register('HEAD', self.path, HTTPOk, {
'x-container-meta-foo': 'different',
}, None)
self._check_headers('HEAD', self.path, HeaderKeyDict({
'x-container-meta-foo': 'different',
'x-backend-storage-policy-index': '0',
}))
if __name__ == '__main__':
unittest.main()