summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorMarek Kaleta <Marek.Kaleta@firma.seznam.cz>2016-02-15 12:14:17 +0100
committerTim Burke <tim.burke@gmail.com>2016-08-23 14:37:11 -0700
commit4a2465fb12ff7287b62b6941fb8ae43e100adc25 (patch)
tree1b01f6d6bd044019f671fea1808e87b3646982d9 /tests
parente05464fbfa03ee6d938c145ede111f4b1e828d58 (diff)
downloadpython-swiftclient-4a2465fb12ff7287b62b6941fb8ae43e100adc25.tar.gz
Add copy object method
Implement copy object method in swiftclient Connection, Service and CLI. Although COPY functionality can be accomplished with 'X-Copy-From' header in PUT request, using copy is more convenient especially when using copy for updating object metadata non-destructively. Closes-Bug: 1474939 Change-Id: I1338ac411f418f4adb3d06753d044a484a7f32a4
Diffstat (limited to 'tests')
-rw-r--r--tests/functional/test_swiftclient.py40
-rw-r--r--tests/unit/test_service.py106
-rw-r--r--tests/unit/test_shell.py133
-rw-r--r--tests/unit/test_swiftclient.py104
4 files changed, 383 insertions, 0 deletions
diff --git a/tests/functional/test_swiftclient.py b/tests/functional/test_swiftclient.py
index 7a77c07..6e19abd 100644
--- a/tests/functional/test_swiftclient.py
+++ b/tests/functional/test_swiftclient.py
@@ -405,6 +405,46 @@ class TestFunctional(unittest.TestCase):
headers = self.conn.head_object(self.containername, self.objectname)
self.assertEqual('Something', headers.get('x-object-meta-color'))
+ def test_copy_object(self):
+ self.conn.put_object(
+ self.containername, self.objectname, self.test_data)
+ self.conn.copy_object(self.containername,
+ self.objectname,
+ headers={'x-object-meta-color': 'Something'})
+
+ headers = self.conn.head_object(self.containername, self.objectname)
+ self.assertEqual('Something', headers.get('x-object-meta-color'))
+
+ self.conn.copy_object(self.containername,
+ self.objectname,
+ headers={'x-object-meta-taste': 'Second'})
+
+ headers = self.conn.head_object(self.containername, self.objectname)
+ self.assertEqual('Something', headers.get('x-object-meta-color'))
+ self.assertEqual('Second', headers.get('x-object-meta-taste'))
+
+ destination = "/%s/%s" % (self.containername, self.objectname_2)
+ self.conn.copy_object(self.containername,
+ self.objectname,
+ destination,
+ headers={'x-object-meta-taste': 'Second'})
+ headers, data = self.conn.get_object(self.containername,
+ self.objectname_2)
+ self.assertEqual(self.test_data, data)
+ self.assertEqual('Something', headers.get('x-object-meta-color'))
+ self.assertEqual('Second', headers.get('x-object-meta-taste'))
+
+ destination = "/%s/%s" % (self.containername, self.objectname_2)
+ self.conn.copy_object(self.containername,
+ self.objectname,
+ destination,
+ headers={'x-object-meta-color': 'Else'},
+ fresh_metadata=True)
+
+ headers = self.conn.head_object(self.containername, self.objectname_2)
+ self.assertEqual('Else', headers.get('x-object-meta-color'))
+ self.assertIsNone(headers.get('x-object-meta-taste'))
+
def test_get_capabilities(self):
resp = self.conn.get_capabilities()
self.assertTrue(resp.get('swift'))
diff --git a/tests/unit/test_service.py b/tests/unit/test_service.py
index cce8c7a..5b04c44 100644
--- a/tests/unit/test_service.py
+++ b/tests/unit/test_service.py
@@ -69,6 +69,42 @@ class TestSwiftPostObject(unittest.TestCase):
self.assertRaises(SwiftError, self.spo, 1)
+class TestSwiftCopyObject(unittest.TestCase):
+
+ def setUp(self):
+ super(TestSwiftCopyObject, self).setUp()
+ self.sco = swiftclient.service.SwiftCopyObject
+
+ def test_create(self):
+ sco = self.sco('obj_name')
+
+ self.assertEqual(sco.object_name, 'obj_name')
+ self.assertIsNone(sco.destination)
+ self.assertFalse(sco.fresh_metadata)
+
+ sco = self.sco('obj_name',
+ {'destination': '/dest', 'fresh_metadata': True})
+
+ self.assertEqual(sco.object_name, 'obj_name')
+ self.assertEqual(sco.destination, '/dest/obj_name')
+ self.assertTrue(sco.fresh_metadata)
+
+ sco = self.sco('obj_name',
+ {'destination': '/dest/new_obj/a',
+ 'fresh_metadata': False})
+
+ self.assertEqual(sco.object_name, 'obj_name')
+ self.assertEqual(sco.destination, '/dest/new_obj/a')
+ self.assertFalse(sco.fresh_metadata)
+
+ def test_create_with_invalid_name(self):
+ # empty strings are not allowed as names
+ self.assertRaises(SwiftError, self.sco, '')
+
+ # names cannot be anything but strings
+ self.assertRaises(SwiftError, self.sco, 1)
+
+
class TestSwiftReader(unittest.TestCase):
def setUp(self):
@@ -2344,3 +2380,73 @@ class TestServicePost(_TestServiceBase):
res_iter.assert_called_with(
[tm_instance.object_uu_pool.submit()] * len(calls))
+
+
+class TestServiceCopy(_TestServiceBase):
+
+ def setUp(self):
+ super(TestServiceCopy, self).setUp()
+ self.opts = swiftclient.service._default_local_options.copy()
+
+ @mock.patch('swiftclient.service.MultiThreadingManager')
+ @mock.patch('swiftclient.service.interruptable_as_completed')
+ def test_object_copy(self, inter_compl, thread_manager):
+ """
+ Check copy method translates strings and objects to _copy_object_job
+ calls correctly
+ """
+ tm_instance = Mock()
+ thread_manager.return_value = tm_instance
+
+ self.opts.update({'meta': ["meta1:test1"], "header": ["hdr1:test1"]})
+ sco = swiftclient.service.SwiftCopyObject(
+ "test_sco",
+ options={'meta': ["meta1:test2"], "header": ["hdr1:test2"],
+ 'destination': "/cont_new/test_sco"})
+
+ res = SwiftService().copy('test_c', ['test_o', sco], self.opts)
+ res = list(res)
+
+ calls = [
+ mock.call(
+ SwiftService._create_container_job, 'cont_new', headers={}),
+ ]
+ tm_instance.container_pool.submit.assert_has_calls(calls,
+ any_order=True)
+ self.assertEqual(
+ tm_instance.container_pool.submit.call_count, len(calls))
+
+ calls = [
+ mock.call(
+ SwiftService._copy_object_job, 'test_c', 'test_o',
+ None,
+ {
+ "X-Object-Meta-Meta1": "test1",
+ "Hdr1": "test1"},
+ False),
+ mock.call(
+ SwiftService._copy_object_job, 'test_c', 'test_sco',
+ '/cont_new/test_sco',
+ {
+ "X-Object-Meta-Meta1": "test2",
+ "Hdr1": "test2"},
+ False),
+ ]
+ tm_instance.object_uu_pool.submit.assert_has_calls(calls)
+ self.assertEqual(
+ tm_instance.object_uu_pool.submit.call_count, len(calls))
+
+ inter_compl.assert_called_with(
+ [tm_instance.object_uu_pool.submit()] * len(calls))
+
+ def test_object_copy_fail_dest(self):
+ """
+ Destination in incorrect format and destination with object
+ used when multiple objects are copied raises SwiftError
+ """
+ with self.assertRaises(SwiftError):
+ list(SwiftService().copy('test_c', ['test_o'],
+ {'destination': 'cont'}))
+ with self.assertRaises(SwiftError):
+ list(SwiftService().copy('test_c', ['test_o', 'test_o2'],
+ {'destination': '/cont/obj'}))
diff --git a/tests/unit/test_shell.py b/tests/unit/test_shell.py
index d82def6..407076b 100644
--- a/tests/unit/test_shell.py
+++ b/tests/unit/test_shell.py
@@ -1244,6 +1244,139 @@ class TestShell(unittest.TestCase):
self.assertTrue(output.err != '')
self.assertTrue(output.err.startswith('Usage'))
+ @mock.patch('swiftclient.service.Connection')
+ def test_copy_object_no_destination(self, connection):
+ argv = ["", "copy", "container", "object",
+ "--meta", "Color:Blue",
+ "--header", "content-type:text/plain"
+ ]
+ with CaptureOutput() as output:
+ swiftclient.shell.main(argv)
+ connection.return_value.copy_object.assert_called_with(
+ 'container', 'object', destination=None, fresh_metadata=False,
+ headers={
+ 'Content-Type': 'text/plain',
+ 'X-Object-Meta-Color': 'Blue'}, response_dict={})
+ self.assertEqual(output.out, 'container/object copied to <self>\n')
+
+ @mock.patch('swiftclient.service.Connection')
+ def test_copy_object(self, connection):
+ argv = ["", "copy", "container", "object",
+ "--meta", "Color:Blue",
+ "--header", "content-type:text/plain",
+ "--destination", "/c/o"
+ ]
+ with CaptureOutput() as output:
+ swiftclient.shell.main(argv)
+ connection.return_value.copy_object.assert_called_with(
+ 'container', 'object', destination="/c/o",
+ fresh_metadata=False,
+ headers={
+ 'Content-Type': 'text/plain',
+ 'X-Object-Meta-Color': 'Blue'}, response_dict={})
+ self.assertEqual(
+ output.out,
+ 'created container c\ncontainer/object copied to /c/o\n'
+ )
+
+ @mock.patch('swiftclient.service.Connection')
+ def test_copy_object_fresh_metadata(self, connection):
+ argv = ["", "copy", "container", "object",
+ "--meta", "Color:Blue", "--fresh-metadata",
+ "--header", "content-type:text/plain",
+ "--destination", "/c/o"
+ ]
+ swiftclient.shell.main(argv)
+ connection.return_value.copy_object.assert_called_with(
+ 'container', 'object', destination="/c/o", fresh_metadata=True,
+ headers={
+ 'Content-Type': 'text/plain',
+ 'X-Object-Meta-Color': 'Blue'}, response_dict={})
+
+ @mock.patch('swiftclient.service.Connection')
+ def test_copy_two_objects(self, connection):
+ argv = ["", "copy", "container", "object", "object2",
+ "--meta", "Color:Blue"]
+ connection.return_value.copy_object.return_value = None
+ swiftclient.shell.main(argv)
+ calls = [
+ mock.call(
+ 'container', 'object', destination=None,
+ fresh_metadata=False, headers={'X-Object-Meta-Color': 'Blue'},
+ response_dict={}),
+ mock.call(
+ 'container', 'object2', destination=None,
+ fresh_metadata=False, headers={'X-Object-Meta-Color': 'Blue'},
+ response_dict={})
+ ]
+ for call in calls:
+ self.assertIn(call, connection.return_value.copy_object.mock_calls)
+ self.assertEqual(len(connection.return_value.copy_object.mock_calls),
+ len(calls))
+
+ @mock.patch('swiftclient.service.Connection')
+ def test_copy_two_objects_destination(self, connection):
+ argv = ["", "copy", "container", "object", "object2",
+ "--meta", "Color:Blue", "--destination", "/c"]
+ swiftclient.shell.main(argv)
+ calls = [
+ mock.call(
+ 'container', 'object', destination="/c/object",
+ fresh_metadata=False, headers={'X-Object-Meta-Color': 'Blue'},
+ response_dict={}),
+ mock.call(
+ 'container', 'object2', destination="/c/object2",
+ fresh_metadata=False, headers={'X-Object-Meta-Color': 'Blue'},
+ response_dict={})
+ ]
+ connection.return_value.copy_object.assert_has_calls(calls)
+
+ @mock.patch('swiftclient.service.Connection')
+ def test_copy_two_objects_bad_destination(self, connection):
+ argv = ["", "copy", "container", "object", "object2",
+ "--meta", "Color:Blue", "--destination", "/c/o"]
+
+ with CaptureOutput() as output:
+ with self.assertRaises(SystemExit):
+ swiftclient.shell.main(argv)
+
+ self.assertEqual(
+ output.err,
+ 'Combination of multiple objects and destination '
+ 'including object is invalid\n')
+
+ @mock.patch('swiftclient.service.Connection')
+ def test_copy_object_bad_auth(self, connection):
+ argv = ["", "copy", "container", "object"]
+ connection.return_value.copy_object.side_effect = \
+ swiftclient.ClientException("bad auth")
+
+ with CaptureOutput() as output:
+ with self.assertRaises(SystemExit):
+ swiftclient.shell.main(argv)
+
+ self.assertEqual(output.err, 'bad auth\n')
+
+ def test_copy_object_not_enough_args(self):
+ argv = ["", "copy", "container"]
+
+ with CaptureOutput() as output:
+ with self.assertRaises(SystemExit):
+ swiftclient.shell.main(argv)
+
+ self.assertTrue(output.err != '')
+ self.assertTrue(output.err.startswith('Usage'))
+
+ def test_copy_bad_container(self):
+ argv = ["", "copy", "cont/ainer", "object"]
+
+ with CaptureOutput() as output:
+ with self.assertRaises(SystemExit):
+ swiftclient.shell.main(argv)
+
+ self.assertTrue(output.err != '')
+ self.assertTrue(output.err.startswith('WARN'))
+
@mock.patch('swiftclient.shell.generate_temp_url', return_value='')
def test_temp_url(self, temp_url):
argv = ["", "tempurl", "GET", "60", "/v1/AUTH_account/c/o",
diff --git a/tests/unit/test_swiftclient.py b/tests/unit/test_swiftclient.py
index cbb95db..cc6a4ac 100644
--- a/tests/unit/test_swiftclient.py
+++ b/tests/unit/test_swiftclient.py
@@ -1398,6 +1398,109 @@ class TestPostObject(MockHttpTest):
])
+class TestCopyObject(MockHttpTest):
+
+ def test_server_error(self):
+ c.http_connection = self.fake_http_connection(500)
+ self.assertRaises(
+ c.ClientException, c.copy_object,
+ 'http://www.test.com/v1/AUTH', 'asdf', 'asdf', 'asdf')
+
+ def test_ok(self):
+ c.http_connection = self.fake_http_connection(200)
+ c.copy_object(
+ 'http://www.test.com/v1/AUTH', 'token', 'container', 'obj',
+ destination='/container2/obj')
+ self.assertRequests([
+ ('COPY', 'http://www.test.com/v1/AUTH/container/obj', '', {
+ 'X-Auth-Token': 'token',
+ 'Destination': '/container2/obj',
+ }),
+ ])
+
+ def test_service_token(self):
+ c.http_connection = self.fake_http_connection(200)
+ c.copy_object('http://www.test.com/v1/AUTH', None, 'container',
+ 'obj', destination='/container2/obj',
+ service_token="TOKEN")
+ self.assertRequests([
+ ('COPY', 'http://www.test.com/v1/AUTH/container/obj', '', {
+ 'X-Service-Token': 'TOKEN',
+ 'Destination': '/container2/obj',
+
+ }),
+ ])
+
+ def test_headers(self):
+ c.http_connection = self.fake_http_connection(200)
+ c.copy_object(
+ 'http://www.test.com/v1/AUTH', 'token', 'container', 'obj',
+ destination='/container2/obj',
+ headers={'some-hdr': 'a', 'other-hdr': 'b'})
+ self.assertRequests([
+ ('COPY', 'http://www.test.com/v1/AUTH/container/obj', '', {
+ 'X-Auth-Token': 'token',
+ 'Destination': '/container2/obj',
+ 'some-hdr': 'a',
+ 'other-hdr': 'b',
+ }),
+ ])
+
+ def test_fresh_metadata_default(self):
+ c.http_connection = self.fake_http_connection(200)
+ c.copy_object(
+ 'http://www.test.com/v1/AUTH', 'token', 'container', 'obj',
+ '/container2/obj', {'x-fresh-metadata': 'hdr-value'})
+ self.assertRequests([
+ ('COPY', 'http://www.test.com/v1/AUTH/container/obj', '', {
+ 'X-Auth-Token': 'token',
+ 'Destination': '/container2/obj',
+ 'X-Fresh-Metadata': 'hdr-value',
+ }),
+ ])
+
+ def test_fresh_metadata_true(self):
+ c.http_connection = self.fake_http_connection(200)
+ c.copy_object(
+ 'http://www.test.com/v1/AUTH', 'token', 'container', 'obj',
+ destination='/container2/obj',
+ headers={'x-fresh-metadata': 'hdr-value'},
+ fresh_metadata=True)
+ self.assertRequests([
+ ('COPY', 'http://www.test.com/v1/AUTH/container/obj', '', {
+ 'X-Auth-Token': 'token',
+ 'Destination': '/container2/obj',
+ 'X-Fresh-Metadata': 'true',
+ }),
+ ])
+
+ def test_fresh_metadata_false(self):
+ c.http_connection = self.fake_http_connection(200)
+ c.copy_object(
+ 'http://www.test.com/v1/AUTH', 'token', 'container', 'obj',
+ destination='/container2/obj',
+ headers={'x-fresh-metadata': 'hdr-value'},
+ fresh_metadata=False)
+ self.assertRequests([
+ ('COPY', 'http://www.test.com/v1/AUTH/container/obj', '', {
+ 'x-auth-token': 'token',
+ 'Destination': '/container2/obj',
+ 'X-Fresh-Metadata': 'false',
+ }),
+ ])
+
+ def test_no_destination(self):
+ c.http_connection = self.fake_http_connection(200)
+ c.copy_object(
+ 'http://www.test.com/v1/AUTH', 'token', 'container', 'obj')
+ self.assertRequests([
+ ('COPY', 'http://www.test.com/v1/AUTH/container/obj', '', {
+ 'x-auth-token': 'token',
+ 'Destination': '/container/obj',
+ }),
+ ])
+
+
class TestDeleteObject(MockHttpTest):
def test_ok(self):
@@ -2246,6 +2349,7 @@ class TestResponseDict(MockHttpTest):
('delete_container', 'c'),
('post_object', 'c', 'o', {}),
('put_object', 'c', 'o', 'body'),
+ ('copy_object', 'c', 'o'),
('delete_object', 'c', 'o')]
def fake_get_auth(*args, **kwargs):