diff --git a/google/cloud/storage/bucket.py b/google/cloud/storage/bucket.py index 0d1f9192b..1621f879e 100644 --- a/google/cloud/storage/bucket.py +++ b/google/cloud/storage/bucket.py @@ -41,6 +41,7 @@ from google.cloud.storage._opentelemetry_tracing import create_trace_span from google.cloud.storage.acl import BucketACL from google.cloud.storage.acl import DefaultObjectACL +from google.cloud.storage.blob import _quote from google.cloud.storage.blob import Blob from google.cloud.storage.constants import _DEFAULT_TIMEOUT from google.cloud.storage.constants import ARCHIVE_STORAGE_CLASS @@ -2360,7 +2361,10 @@ def move_blob( ) new_blob = Blob(bucket=self, name=new_name) - api_path = blob.path + "/moveTo/o/" + new_blob.name + api_path = "{blob_path}/moveTo/o/{new_name}".format( + blob_path=blob.path, new_name=_quote(new_blob.name) + ) + move_result = client._post_resource( api_path, None, diff --git a/tests/unit/test_bucket.py b/tests/unit/test_bucket.py index 809b572e0..8e4132edf 100644 --- a/tests/unit/test_bucket.py +++ b/tests/unit/test_bucket.py @@ -18,6 +18,7 @@ import mock import pytest +from google.cloud.storage.blob import _quote from google.cloud.storage.retry import DEFAULT_RETRY from google.cloud.storage.retry import DEFAULT_RETRY_IF_ETAG_IN_JSON from google.cloud.storage.retry import DEFAULT_RETRY_IF_GENERATION_SPECIFIED @@ -2320,6 +2321,37 @@ def test_move_blob_w_no_retry_timeout_and_generation_match(self): _target_object=new_blob, ) + def test_move_blob_needs_url_encoding(self): + source_name = "source" + blob_name = "blob-name" + new_name = "new/name" + api_response = {} + client = mock.Mock(spec=["_post_resource"]) + client._post_resource.return_value = api_response + source = self._make_one(client=client, name=source_name) + blob = self._make_blob(source_name, blob_name) + + new_blob = source.move_blob( + blob, new_name, if_generation_match=0, retry=None, timeout=30 + ) + + self.assertIs(new_blob.bucket, source) + self.assertEqual(new_blob.name, new_name) + + expected_path = "/b/{}/o/{}/moveTo/o/{}".format( + source_name, blob_name, _quote(new_name) + ) + expected_data = None + expected_query_params = {"ifGenerationMatch": 0} + client._post_resource.assert_called_once_with( + expected_path, + expected_data, + query_params=expected_query_params, + timeout=30, + retry=None, + _target_object=new_blob, + ) + def test_move_blob_w_user_project(self): source_name = "source" blob_name = "blob-name"