From 40eac284c30dad49706d0157ea431cfe97c1881d Mon Sep 17 00:00:00 2001 From: mahendra-google Date: Fri, 21 Mar 2025 03:54:20 -0700 Subject: [PATCH 01/29] initial commit for moveObject feature --- .../MoveObjectOptionsTest.cs | 110 ++++++++++++ .../MoveObjectOptions.cs | 162 ++++++++++++++++++ .../StorageClient.MoveObject.cs | 63 +++++++ .../StorageClientImpl.MoveObject.cs | 66 +++++++ 4 files changed, 401 insertions(+) create mode 100644 apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/MoveObjectOptionsTest.cs create mode 100644 apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/MoveObjectOptions.cs create mode 100644 apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs create mode 100644 apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/MoveObjectOptionsTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/MoveObjectOptionsTest.cs new file mode 100644 index 000000000000..6c70be44b39a --- /dev/null +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/MoveObjectOptionsTest.cs @@ -0,0 +1,110 @@ +// Copyright 2025 Google Inc. All Rights Reserved. +// +// 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. + +using System; +using Xunit; +using static Google.Apis.Storage.v1.ObjectsResource; + +namespace Google.Cloud.Storage.V1.Tests; +public class MoveObjectOptionsTest +{ + [Fact] + public void ModifyRequest_DefaultOptions() + { + var request = new MoveRequest(null, "sourceBucket", "sourceObject", "destObject"); + var options = new MoveObjectOptions(); + options.ModifyRequest(request); + Assert.Null(request.IfGenerationMatch); + Assert.Null(request.IfGenerationNotMatch); + Assert.Null(request.IfMetagenerationMatch); + Assert.Null(request.IfMetagenerationNotMatch); + Assert.Null(request.IfSourceGenerationMatch); + Assert.Null(request.IfSourceGenerationNotMatch); + Assert.Null(request.IfSourceMetagenerationMatch); + Assert.Null(request.IfSourceMetagenerationNotMatch); + Assert.Null(request.UserProject); + } + + [Fact] + public void ModifyRequest_AllOptions_PositiveMatch() + { + var request = new MoveRequest(null, "sourceBucket", "sourceObject", "destObject"); + var options = new MoveObjectOptions + { + IfGenerationMatch = 1L, + IfMetagenerationMatch = 2L, + IfSourceGenerationMatch = 3L, + IfSourceMetagenerationMatch = 4L, + UserProject = "proj" + }; + options.ModifyRequest(request); + Assert.Equal(1L, request.IfGenerationMatch); + Assert.Null(request.IfGenerationNotMatch); + Assert.Equal(2L, request.IfMetagenerationMatch); + Assert.Null(request.IfMetagenerationNotMatch); + Assert.Equal(3L, request.IfSourceGenerationMatch); + Assert.Null(request.IfSourceGenerationNotMatch); + Assert.Equal(4L, request.IfSourceMetagenerationMatch); + Assert.Null(request.IfSourceMetagenerationNotMatch); + Assert.Equal("proj", request.UserProject); + } + + [Fact] + public void ModifyRequest_AllOptions_NegativeMatch() + { + var request = new MoveRequest(null, "sourceBucket", "sourceObject", "destObject"); + var options = new MoveObjectOptions + { + IfGenerationNotMatch = 1L, + IfMetagenerationNotMatch = 2L, + IfSourceGenerationNotMatch = 3L, + IfSourceMetagenerationNotMatch = 4L, + }; + options.ModifyRequest(request); + Assert.Null(request.IfGenerationMatch); + Assert.Equal(1L, request.IfGenerationNotMatch); + Assert.Null(request.IfMetagenerationMatch); + Assert.Equal(2L, request.IfMetagenerationNotMatch); + Assert.Null(request.IfSourceGenerationMatch); + Assert.Equal(3L, request.IfSourceGenerationNotMatch); + Assert.Null(request.IfSourceMetagenerationMatch); + Assert.Equal(4L, request.IfSourceMetagenerationNotMatch); + } + + [Fact] + public void ModifyRequest_MatchNotMatchConflicts() + { + var request = new MoveRequest(null, "sourceBucket", "sourceObject", "destObject"); + Assert.Throws(() => + { + var options = new MoveObjectOptions { IfGenerationMatch = 1L, IfGenerationNotMatch = 2L }; + options.ModifyRequest(request); + }); + Assert.Throws(() => + { + var options = new MoveObjectOptions { IfMetagenerationMatch = 1L, IfMetagenerationNotMatch = 2L }; + options.ModifyRequest(request); + }); + Assert.Throws(() => + { + var options = new MoveObjectOptions { IfSourceGenerationMatch = 1L, IfSourceGenerationNotMatch = 2L }; + options.ModifyRequest(request); + }); + Assert.Throws(() => + { + var options = new MoveObjectOptions { IfSourceMetagenerationMatch = 1L, IfSourceMetagenerationNotMatch = 2L }; + options.ModifyRequest(request); + }); + } +} diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/MoveObjectOptions.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/MoveObjectOptions.cs new file mode 100644 index 000000000000..5d4492bbd724 --- /dev/null +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/MoveObjectOptions.cs @@ -0,0 +1,162 @@ +// Copyright 2025 Google Inc. All Rights Reserved. +// +// 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. + +using System; +using static Google.Apis.Storage.v1.ObjectsResource; +using Object = Google.Apis.Storage.v1.Data.Object; + +namespace Google.Cloud.Storage.V1; + +/// +/// Options for MoveObject operations. +/// +public sealed class MoveObjectOptions +{ + + /// + /// Precondition for moving: the object is only moved if the existing destination object's + /// generation matches the given value. + /// + public long? IfGenerationMatch { get; set; } + + /// + /// Precondition for moving: the object is only moved if the existing destination object's + /// generation does not match the given value. + /// + public long? IfGenerationNotMatch { get; set; } + + /// + /// Precondition for moving: the object is only moved if the existing destination object's + /// meta-generation matches the given value. + /// + public long? IfMetagenerationMatch { get; set; } + + /// + /// Precondition for moving: the object is only moved if the existing destination object's + /// meta-generation does not match the given value. + /// + public long? IfMetagenerationNotMatch { get; set; } + + /// + /// Precondition for moving: the object is only moved if the source object's + /// generation matches the given value. + /// + public long? IfSourceGenerationMatch { get; set; } + + /// + /// Precondition for moving: the object is only moved if the source object's + /// generation does not match the given value. + /// + public long? IfSourceGenerationNotMatch { get; set; } + + /// + /// Precondition for moving: the object is only moved if the source object's + /// meta-generation matches the given value. + /// + public long? IfSourceMetagenerationMatch { get; set; } + + /// + /// Precondition for moving: the object is only moved if the source object's + /// meta-generation does not match the given value. + /// + public long? IfSourceMetagenerationNotMatch { get; set; } + + /// + /// Additional object metadata for the new object. This can be used to specify the storage + /// class of the new object, the content type etc. If this property is not set, the existing + /// object metadata will be used unchanged. + /// + public Object ExtraMetadata { get; set; } + + /// + /// The encryption key to use for this operation. If this property is null, the + /// will be used instead. Use to remove encryption headers from this request. + /// + public EncryptionKey EncryptionKey { get; set; } + + /// + /// The encryption key to use for the source of the copy. If this property is null, the + /// will be used instead. Use if the source is not encrypted. + /// + public EncryptionKey SourceEncryptionKey { get; set; } + + /// + /// If set, this is the ID of the project which will be billed for the request. + /// The caller must have suitable permissions for the project being billed. + /// + public string UserProject { get; set; } + + /// + /// Options to pass custom retry configuration for each API request. + /// + public RetryOptions RetryOptions { get; set; } + + internal void ModifyRequest(MoveRequest request) + { + // Note the use of ArgumentException here, as this will basically be the result of invalid + // options being passed to a public method. + if (IfGenerationMatch != null && IfGenerationNotMatch != null) + { + throw new ArgumentException($"Cannot specify {nameof(IfGenerationMatch)} and {nameof(IfGenerationNotMatch)} in the same options", "options"); + } + if (IfMetagenerationMatch != null && IfMetagenerationNotMatch != null) + { + throw new ArgumentException($"Cannot specify {nameof(IfMetagenerationMatch)} and {nameof(IfMetagenerationNotMatch)} in the same options", "options"); + } + if (IfSourceGenerationMatch != null && IfSourceGenerationNotMatch != null) + { + throw new ArgumentException($"Cannot specify {nameof(IfSourceGenerationMatch)} and {nameof(IfSourceGenerationNotMatch)} in the same options", "options"); + } + if (IfSourceMetagenerationMatch != null && IfSourceMetagenerationNotMatch != null) + { + throw new ArgumentException($"Cannot specify {nameof(IfSourceMetagenerationMatch)} and {nameof(IfSourceMetagenerationNotMatch)} in the same options", "options"); + } + if (IfGenerationMatch != null) + { + request.IfGenerationMatch = IfGenerationMatch; + } + if (IfGenerationNotMatch != null) + { + request.IfGenerationNotMatch = IfGenerationNotMatch; + } + if (IfMetagenerationMatch != null) + { + request.IfMetagenerationMatch = IfMetagenerationMatch; + } + if (IfMetagenerationNotMatch != null) + { + request.IfMetagenerationNotMatch = IfMetagenerationNotMatch; + } + if (IfSourceGenerationMatch != null) + { + request.IfSourceGenerationMatch = IfSourceGenerationMatch; + } + if (IfSourceGenerationNotMatch != null) + { + request.IfSourceGenerationNotMatch = IfSourceGenerationNotMatch; + } + if (IfSourceMetagenerationMatch != null) + { + request.IfSourceMetagenerationMatch = IfSourceMetagenerationMatch; + } + if (IfSourceMetagenerationNotMatch != null) + { + request.IfSourceMetagenerationNotMatch = IfSourceMetagenerationNotMatch; + } + if (UserProject != null) + { + request.UserProject = UserProject; + } + } +} diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs new file mode 100644 index 000000000000..032d84f526e0 --- /dev/null +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs @@ -0,0 +1,63 @@ +// Copyright 2025 Google Inc. All Rights Reserved. +// +// 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. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Object = Google.Apis.Storage.v1.Data.Object; + +namespace Google.Cloud.Storage.V1; +public abstract partial class StorageClient +{ + /// + /// Moves an object within a bucket with hierarchical namespace enabled. This method uses the + /// moveObject underlying API operation for more flexibility and reliability. + /// + /// Name of the bucket containing the object you want to move. Must not be null. + /// The name of the source object to move within the bucket. Must not be null. + /// The name of the new object to move within the bucket. Must not be null. + /// Additional options for the move operation. May be null, in which case appropriate + /// defaults will be used. + /// The representation of the new storage object resulting from the move. + public virtual Object MoveObject( + string sourceBucket, + string sourceObjectName, + string destinationObjectName, + MoveObjectOptions options = null) + { + throw new NotImplementedException(); + } + + /// + /// Moves an object within a bucket with hierarchical namespace enabled. This method uses the + /// moveObject underlying API operation for more flexibility and reliability. + /// + /// Name of the bucket containing the object you want to move. Must not be null. + /// The name of the source object to move within the bucket. Must not be null. + /// The name of the new object to move within the bucket. Must not be null. + /// Additional options for the copy operation. May be null, in which case appropriate + /// defaults will be used. + /// The token to monitor for cancellation requests. + /// A task representing the asynchronous operation, with a result returning the + /// representation of the new storage object resulting from the move. + public virtual Task MoveObjectAsync( + string sourceBucket, + string sourceObjectName, + string destinationObjectName = null, + MoveObjectOptions options = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } +} diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs new file mode 100644 index 000000000000..d7931d39f113 --- /dev/null +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs @@ -0,0 +1,66 @@ +// Copyright 2025 Google Inc. All Rights Reserved. +// +// 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. + +using Google.Api.Gax; +using Google.Apis.Storage.v1; +using System.Threading; +using System.Threading.Tasks; +using Object = Google.Apis.Storage.v1.Data.Object; + +namespace Google.Cloud.Storage.V1; +public sealed partial class StorageClientImpl : StorageClient +{ + /// + public override Object MoveObject( + string sourceBucket, + string sourceObjectName, + string destinationObjectName, + MoveObjectOptions options = null) + { + var request = CreateMoveObjectRequest(sourceBucket, sourceObjectName, destinationObjectName, options); + var response = request.Execute(); + return response; + } + + /// + public override async Task MoveObjectAsync( + string sourceBucket, + string sourceObjectName, + string destinationObjectName, + MoveObjectOptions options = null, + CancellationToken cancellationToken = default) + { + var request = CreateMoveObjectRequest(sourceBucket, sourceObjectName, destinationObjectName, options); + var response = await request.ExecuteAsync(cancellationToken).ConfigureAwait(false); + return response; + } + + private ObjectsResource.MoveRequest CreateMoveObjectRequest( + string sourceBucket, + string sourceObjectName, + string destinationObjectName, + MoveObjectOptions options) + { + GaxPreconditions.CheckNotNull(sourceBucket, nameof(sourceBucket)); + GaxPreconditions.CheckNotNull(sourceObjectName, nameof(sourceObjectName)); + GaxPreconditions.CheckNotNull(destinationObjectName, nameof(destinationObjectName)); + Object obj = options?.ExtraMetadata ?? new Object(); + var request = Service.Objects.Move(sourceBucket, sourceObjectName, destinationObjectName); + options?.ModifyRequest(request); + RetryOptions retryOptions = options?.RetryOptions ?? RetryOptions.MaybeIdempotent(options?.IfGenerationMatch); + MarkAsRetriable(request, retryOptions); + request.ModifyRequest += (options?.SourceEncryptionKey ?? EncryptionKey).ModifyRequest; + return request; + } +} From 0384a8cc9b0e315504cb5279a2580cbce234407b Mon Sep 17 00:00:00 2001 From: mahendra-google Date: Fri, 21 Mar 2025 04:11:00 -0700 Subject: [PATCH 02/29] storage fixture changes for move object --- .../StorageFixture.cs | 26 +++++++++++++++++++ .../StorageClientImpl.MoveObject.cs | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs index 9973b04e598b..df125f150b41 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs @@ -61,6 +61,11 @@ public sealed class StorageFixture : CloudProjectFixtureBase, ICollectionFixture /// public string SoftDeleteBucket => BucketPrefix + "-soft-delete"; + /// + /// Name of a bucket with hierarchical namespace enabled + /// + public string HnsBucket => BucketPrefix + "hns"; + /// /// A small amount of content. Do not mutate the array. /// @@ -167,6 +172,7 @@ public StorageFixture() CreateBucket(LabelsTestBucket, multiVersion: false); CreateBucket(InitiallyEmptyBucket, multiVersion: false); CreateBucket(SoftDeleteBucket, multiVersion: false, softDelete: true); + CreateAndPopulateHnsBucket(HnsBucket); RequesterPaysClient = CreateRequesterPaysClient(); if (RequesterPaysClient != null) @@ -267,6 +273,26 @@ internal Bucket CreateBucket(string name, bool multiVersion, bool softDelete = f return bucket; } + internal Bucket CreateAndPopulateHnsBucket(string name) + { + var storage = StorageClient.Create(); + var bucket = storage.CreateBucket(ProjectId, + new Bucket + { + Name = name, + IamConfiguration = new Bucket.IamConfigurationData + { + UniformBucketLevelAccess = new Bucket.IamConfigurationData.UniformBucketLevelAccessData { Enabled = true } + }, + HierarchicalNamespace = new Bucket.HierarchicalNamespaceData { Enabled = true } + }); + Client.UploadObject(name, SmallThenLargeObject, "text/plain", new MemoryStream(LargeContent)); + + SleepAfterBucketCreateDelete(); + RegisterBucketToDelete(name); + return bucket; + } + internal string GenerateBucketName() => IdGenerator.FromGuid(prefix: BucketPrefix, separator: "", maxLength: 63); private void CreateAndPopulateReadBucket() diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs index d7931d39f113..5d0c4f539763 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs @@ -29,7 +29,7 @@ public override Object MoveObject( MoveObjectOptions options = null) { var request = CreateMoveObjectRequest(sourceBucket, sourceObjectName, destinationObjectName, options); - var response = request.Execute(); + var response = request.Execute(); return response; } From 0f96e2a89ab28ad58a2e24f051912a26f46a9faf Mon Sep 17 00:00:00 2001 From: mahendra-google Date: Fri, 21 Mar 2025 04:49:59 -0700 Subject: [PATCH 03/29] moveobject test added --- .../MoveObjectTest.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs new file mode 100644 index 000000000000..e899a7a24984 --- /dev/null +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs @@ -0,0 +1,47 @@ +// Copyright 2025 Google Inc. All Rights Reserved. +// +// 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. + +using Google.Cloud.ClientTesting; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; +using System.Threading.Tasks; +using Xunit; +using static Google.Cloud.Storage.V1.IntegrationTests.TestHelpers; +using Object = Google.Apis.Storage.v1.Data.Object; + +namespace Google.Cloud.Storage.V1.IntegrationTests; + +[Collection(nameof(StorageFixture))] +public class MoveObjectTest +{ + private readonly StorageFixture _fixture; + + public MoveObjectTest(StorageFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public async Task MoveObjectAsync() + { + await _fixture.Client.MoveObjectAsync( + _fixture.HnsBucket, _fixture.SmallThenLargeObject, + _fixture.LargeObject); + using (var stream = new MemoryStream()) + { + await _fixture.Client.DownloadObjectAsync(_fixture.HnsBucket, _fixture.LargeObject, stream); + Assert.Equal(_fixture.LargeContent, stream.ToArray()); + } + } +} From 0b12a11396337e8c34f32ea3f3c87b84db061a80 Mon Sep 17 00:00:00 2001 From: mahendra-google Date: Fri, 21 Mar 2025 07:09:52 -0700 Subject: [PATCH 04/29] Google.Apis.Storage.v1 package upgrade in storage project file --- .../Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.csproj b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.csproj index 01cd97a63efe..50a83676cf70 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.csproj +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.csproj @@ -10,7 +10,7 @@ - + From 137731efe2816499a5bfc7d060d3395cb975fb77 Mon Sep 17 00:00:00 2001 From: mahendra-google Date: Mon, 24 Mar 2025 01:08:15 -0700 Subject: [PATCH 05/29] move object test case updated --- .../MoveObjectTest.cs | 57 ++++++++++++++++--- .../StorageFixture.cs | 5 ++ .../StorageClient.MoveObject.cs | 1 + .../StorageClientImpl.MoveObject.cs | 1 + 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs index e899a7a24984..3a599b57a019 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs @@ -12,13 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Google.Cloud.ClientTesting; using System.IO; -using System.Runtime.Serialization.Formatters.Binary; +using System.Linq; +using System.Net; using System.Threading.Tasks; using Xunit; -using static Google.Cloud.Storage.V1.IntegrationTests.TestHelpers; -using Object = Google.Apis.Storage.v1.Data.Object; namespace Google.Cloud.Storage.V1.IntegrationTests; @@ -32,16 +30,57 @@ public MoveObjectTest(StorageFixture fixture) _fixture = fixture; } + // Moves the source object to the destination object within a bucket with hierarchical namespace enabled. [Fact] public async Task MoveObjectAsync() { + var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, _fixture.SmallThenLargeObject); await _fixture.Client.MoveObjectAsync( _fixture.HnsBucket, _fixture.SmallThenLargeObject, _fixture.LargeObject); - using (var stream = new MemoryStream()) - { - await _fixture.Client.DownloadObjectAsync(_fixture.HnsBucket, _fixture.LargeObject, stream); - Assert.Equal(_fixture.LargeContent, stream.ToArray()); - } + using var stream = new MemoryStream(); + var expected = await _fixture.Client.DownloadObjectAsync(_fixture.HnsBucket, _fixture.LargeObject, stream); + Assert.Equal(_fixture.LargeContent, stream.ToArray()); + // Assert that the generation of the destination object is different from the source object indicating a new object at destination is created. + Assert.NotEqual(actual.Generation, expected.Generation); + var objects = _fixture.Client.ListObjects(_fixture.HnsBucket).ToList(); + // Assert that the destination object exists after the move. + Assert.Contains(objects, obj => obj.Name == expected.Name && obj.Generation == expected.Generation); + // Assert that the source object does not exist after the move. + Assert.DoesNotContain(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); + } + + // Move the object to a folders and subfolders by creating folders and subfolders if necessary. + [Fact] + public async Task MoveObjectToDirectorySubDirectoryAsync() + { + var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, _fixture.SmallObject); + await _fixture.Client.MoveObjectAsync( + _fixture.HnsBucket, _fixture.SmallObject, + $"folder/subfolder/" + _fixture.SmallThenLargeObject); + using var stream = new MemoryStream(); + var expected = await _fixture.Client.DownloadObjectAsync(_fixture.HnsBucket, $"folder/subfolder/" + _fixture.SmallThenLargeObject, stream); + Assert.Equal(_fixture.SmallContent, stream.ToArray()); + // Assert that the generation of the destination object is different from the source object indicating a new object at destination is created. + Assert.NotEqual(actual.Generation, expected.Generation); + var objects = _fixture.Client.ListObjects(_fixture.HnsBucket).ToList(); + // Assert that the destination object exists after the move. + Assert.Contains(objects, obj => obj.Name == expected.Name && obj.Generation == expected.Generation); + // Assert that the source object does not exist after the move. + Assert.DoesNotContain(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); + } + + // Prevent overwriting of an existing destination object using preconditions. + [Fact] + public async Task PreventMoveObjectWithPreconditionsAsync() + { + var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, _fixture.SmallThenLargeObject); + var exception = Assert.Throws(() => _fixture.Client.MoveObject( + _fixture.HnsBucket, _fixture.SmallThenLargeObject, "small_then_large.txt", + new MoveObjectOptions { IfGenerationMatch = 0 })); + Assert.Equal(HttpStatusCode.BadRequest, exception.HttpStatusCode); + var objects = _fixture.Client.ListObjects(_fixture.HnsBucket).ToList(); + // Assert that the source object is not deleted and destination object is not overwritten. + Assert.Contains(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); } } diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs index df125f150b41..a8d467086aa8 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs @@ -287,6 +287,11 @@ internal Bucket CreateAndPopulateHnsBucket(string name) HierarchicalNamespace = new Bucket.HierarchicalNamespaceData { Enabled = true } }); Client.UploadObject(name, SmallThenLargeObject, "text/plain", new MemoryStream(LargeContent)); + Client.UploadObject(name, SmallObject, "text/plain", new MemoryStream(SmallContent)); + foreach (var nameoObjectsInFolder in s_objectsInFolders) + { + Client.UploadObject(name, nameoObjectsInFolder, "text/plain", new MemoryStream(SmallContent)); + } SleepAfterBucketCreateDelete(); RegisterBucketToDelete(name); diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs index 032d84f526e0..aacabc0151a4 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs @@ -18,6 +18,7 @@ using Object = Google.Apis.Storage.v1.Data.Object; namespace Google.Cloud.Storage.V1; + public abstract partial class StorageClient { /// diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs index 5d0c4f539763..2d7a93f35dae 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs @@ -19,6 +19,7 @@ using Object = Google.Apis.Storage.v1.Data.Object; namespace Google.Cloud.Storage.V1; + public sealed partial class StorageClientImpl : StorageClient { /// From 72a4f84e2a05535210e48f49c41b25a62bb5a34b Mon Sep 17 00:00:00 2001 From: mahendra-google Date: Mon, 24 Mar 2025 04:56:14 -0700 Subject: [PATCH 06/29] MoveObject Test changes --- .../MoveObjectTest.cs | 61 ++++++++++++++----- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs index 3a599b57a019..fcdddff0ca71 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs @@ -12,11 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Google.Apis.Storage.v1.Data; using System.IO; using System.Linq; using System.Net; +using System.Text; using System.Threading.Tasks; using Xunit; +using static Google.Cloud.Storage.V1.IntegrationTests.TestHelpers; namespace Google.Cloud.Storage.V1.IntegrationTests; @@ -24,10 +27,15 @@ namespace Google.Cloud.Storage.V1.IntegrationTests; public class MoveObjectTest { private readonly StorageFixture _fixture; + private string SmallNewObject { get; } = "smallNew.txt"; + private string LargeNewObject { get; } = "largeNew.txt"; + private string SmallThenLargeObject { get; } = "smallThenLargeNew.txt"; public MoveObjectTest(StorageFixture fixture) { _fixture = fixture; + _fixture.Client.UploadObject(_fixture.HnsBucket, SmallNewObject, "text/plain", new MemoryStream(_fixture.SmallContent)); + _fixture.Client.UploadObject(_fixture.HnsBucket, LargeNewObject, "text/plain", new MemoryStream(_fixture.LargeContent)); } // Moves the source object to the destination object within a bucket with hierarchical namespace enabled. @@ -35,11 +43,9 @@ public MoveObjectTest(StorageFixture fixture) public async Task MoveObjectAsync() { var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, _fixture.SmallThenLargeObject); - await _fixture.Client.MoveObjectAsync( - _fixture.HnsBucket, _fixture.SmallThenLargeObject, - _fixture.LargeObject); + await _fixture.Client.MoveObjectAsync(_fixture.HnsBucket, _fixture.SmallThenLargeObject, LargeNewObject); using var stream = new MemoryStream(); - var expected = await _fixture.Client.DownloadObjectAsync(_fixture.HnsBucket, _fixture.LargeObject, stream); + var expected = await _fixture.Client.DownloadObjectAsync(_fixture.HnsBucket, LargeNewObject, stream); Assert.Equal(_fixture.LargeContent, stream.ToArray()); // Assert that the generation of the destination object is different from the source object indicating a new object at destination is created. Assert.NotEqual(actual.Generation, expected.Generation); @@ -55,11 +61,9 @@ await _fixture.Client.MoveObjectAsync( public async Task MoveObjectToDirectorySubDirectoryAsync() { var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, _fixture.SmallObject); - await _fixture.Client.MoveObjectAsync( - _fixture.HnsBucket, _fixture.SmallObject, - $"folder/subfolder/" + _fixture.SmallThenLargeObject); + await _fixture.Client.MoveObjectAsync(_fixture.HnsBucket, _fixture.SmallObject, $"folder/subfolder/" + SmallNewObject); using var stream = new MemoryStream(); - var expected = await _fixture.Client.DownloadObjectAsync(_fixture.HnsBucket, $"folder/subfolder/" + _fixture.SmallThenLargeObject, stream); + var expected = await _fixture.Client.DownloadObjectAsync(_fixture.HnsBucket, $"folder/subfolder/" + SmallNewObject, stream); Assert.Equal(_fixture.SmallContent, stream.ToArray()); // Assert that the generation of the destination object is different from the source object indicating a new object at destination is created. Assert.NotEqual(actual.Generation, expected.Generation); @@ -70,17 +74,46 @@ await _fixture.Client.MoveObjectAsync( Assert.DoesNotContain(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); } - // Prevent overwriting of an existing destination object using preconditions. + // Prevent moving the source object to the destination object using bad preconditions (wrong source generation) set. [Fact] - public async Task PreventMoveObjectWithPreconditionsAsync() + public async Task PreventMoveObject_With_Wrong_GenerationAsync() { - var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, _fixture.SmallThenLargeObject); - var exception = Assert.Throws(() => _fixture.Client.MoveObject( - _fixture.HnsBucket, _fixture.SmallThenLargeObject, "small_then_large.txt", + var bucketUploadedObject = _fixture.Client.UploadObject(_fixture.HnsBucket, "file.txt", null, GenerateData(128)); + var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, bucketUploadedObject.Name); + var exception = Assert.Throws(() => _fixture.Client.MoveObject(_fixture.HnsBucket, bucketUploadedObject.Name, _fixture.SmallObject, new MoveObjectOptions { IfGenerationMatch = 0 })); - Assert.Equal(HttpStatusCode.BadRequest, exception.HttpStatusCode); + Assert.Equal(HttpStatusCode.PreconditionFailed, exception.HttpStatusCode); var objects = _fixture.Client.ListObjects(_fixture.HnsBucket).ToList(); // Assert that the source object is not deleted and destination object is not overwritten. Assert.Contains(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); } + + // Prevent moving the source object to the destination object using bad preconditions (wrong source metageneration) set. + [Fact] + public async Task PreventMoveObject_With_Wrong_SourceMetaGenerationAsync() + { + var bucketUploadedObject = _fixture.Client.UploadObject(_fixture.HnsBucket, "sourceFile.txt", null, GenerateData(128)); + var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, bucketUploadedObject.Name); + var exception = Assert.Throws(() => _fixture.Client.MoveObject(_fixture.HnsBucket, bucketUploadedObject.Name, _fixture.SmallObject, + new MoveObjectOptions { IfSourceMetagenerationMatch = 0 })); + Assert.Equal(HttpStatusCode.PreconditionFailed, exception.HttpStatusCode); + var objects = _fixture.Client.ListObjects(_fixture.HnsBucket).ToList(); + // Assert that the source object is not deleted and destination object is not overwritten. + Assert.Contains(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); + } + + // Move the source object to the destination object using correct preconditions (source metageneration and generation match) set. + [Fact] + public async Task MoveObject_With_Correct_PreconditionsAsync() + { + var bucketUploadedObject = _fixture.Client.UploadObject(_fixture.HnsBucket, "sourceTestFile.txt", null, GenerateData(128)); + var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, bucketUploadedObject.Name); + var expected = _fixture.Client.MoveObject(_fixture.HnsBucket, bucketUploadedObject.Name, SmallThenLargeObject, + new MoveObjectOptions { IfSourceMetagenerationMatch = actual.Metageneration, IfSourceGenerationMatch = actual.Generation }); + var objects = _fixture.Client.ListObjects(_fixture.HnsBucket).ToList(); + // Assert that the destination object exists after the move. + Assert.Contains(objects, obj => obj.Name == expected.Name && obj.Generation == expected.Generation); + // Assert that the source object does not exist after the move. + Assert.DoesNotContain(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); + } } From 5e83b6c9ecc3b1e63e1778e5932f544a83130eb0 Mon Sep 17 00:00:00 2001 From: mahendra-google Date: Mon, 24 Mar 2025 06:41:55 -0700 Subject: [PATCH 07/29] Move Test updated --- .../MoveObjectTest.cs | 31 ++++++++++++++++--- .../StorageFixture.cs | 1 - 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs index fcdddff0ca71..634e2bdf4792 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs @@ -30,25 +30,46 @@ public class MoveObjectTest private string SmallNewObject { get; } = "smallNew.txt"; private string LargeNewObject { get; } = "largeNew.txt"; private string SmallThenLargeObject { get; } = "smallThenLargeNew.txt"; + private Bucket HnsBucket { get; } public MoveObjectTest(StorageFixture fixture) { _fixture = fixture; + HnsBucket = _fixture.CreateAndPopulateHnsBucket(_fixture.HnsBucket); _fixture.Client.UploadObject(_fixture.HnsBucket, SmallNewObject, "text/plain", new MemoryStream(_fixture.SmallContent)); _fixture.Client.UploadObject(_fixture.HnsBucket, LargeNewObject, "text/plain", new MemoryStream(_fixture.LargeContent)); } // Moves the source object to the destination object within a bucket with hierarchical namespace enabled. [Fact] - public async Task MoveObjectAsync() + public async Task MoveObjectDefaultAsync() { var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, _fixture.SmallThenLargeObject); - await _fixture.Client.MoveObjectAsync(_fixture.HnsBucket, _fixture.SmallThenLargeObject, LargeNewObject); + var expected = await _fixture.Client.MoveObjectAsync(_fixture.HnsBucket, _fixture.SmallThenLargeObject, LargeNewObject); using var stream = new MemoryStream(); - var expected = await _fixture.Client.DownloadObjectAsync(_fixture.HnsBucket, LargeNewObject, stream); + await _fixture.Client.DownloadObjectAsync(_fixture.HnsBucket, LargeNewObject, stream); Assert.Equal(_fixture.LargeContent, stream.ToArray()); // Assert that the generation of the destination object is different from the source object indicating a new object at destination is created. Assert.NotEqual(actual.Generation, expected.Generation); + // Assert that the id of the destination object is different from the source object indicating a new object at destination is created. + Assert.NotEqual(actual.Id, expected.Id); + Assert.NotEqual(actual.TimeCreatedRaw, expected.TimeCreatedRaw); + Assert.NotEqual(actual.TimeFinalizedRaw, expected.TimeFinalizedRaw); + Assert.NotEqual(actual.UpdatedRaw, expected.UpdatedRaw); + Assert.NotEqual(actual.TimeStorageClassUpdatedRaw, expected.TimeStorageClassUpdatedRaw); + if (HnsBucket.Autoclass != null) + { + if (expected != null) + { + Assert.Equal(expected.StorageClass, actual.StorageClass); + Assert.Equal(expected.StorageClass, StorageClasses.Standard); + } + } + else + { + Assert.Equal(expected.StorageClass, actual.StorageClass); + } + var objects = _fixture.Client.ListObjects(_fixture.HnsBucket).ToList(); // Assert that the destination object exists after the move. Assert.Contains(objects, obj => obj.Name == expected.Name && obj.Generation == expected.Generation); @@ -61,9 +82,9 @@ public async Task MoveObjectAsync() public async Task MoveObjectToDirectorySubDirectoryAsync() { var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, _fixture.SmallObject); - await _fixture.Client.MoveObjectAsync(_fixture.HnsBucket, _fixture.SmallObject, $"folder/subfolder/" + SmallNewObject); + var expected = await _fixture.Client.MoveObjectAsync(_fixture.HnsBucket, _fixture.SmallObject, $"folder/subfolder/" + SmallNewObject); using var stream = new MemoryStream(); - var expected = await _fixture.Client.DownloadObjectAsync(_fixture.HnsBucket, $"folder/subfolder/" + SmallNewObject, stream); + await _fixture.Client.DownloadObjectAsync(_fixture.HnsBucket, $"folder/subfolder/" + SmallNewObject, stream); Assert.Equal(_fixture.SmallContent, stream.ToArray()); // Assert that the generation of the destination object is different from the source object indicating a new object at destination is created. Assert.NotEqual(actual.Generation, expected.Generation); diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs index a8d467086aa8..30f608fd4320 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs @@ -172,7 +172,6 @@ public StorageFixture() CreateBucket(LabelsTestBucket, multiVersion: false); CreateBucket(InitiallyEmptyBucket, multiVersion: false); CreateBucket(SoftDeleteBucket, multiVersion: false, softDelete: true); - CreateAndPopulateHnsBucket(HnsBucket); RequesterPaysClient = CreateRequesterPaysClient(); if (RequesterPaysClient != null) From 8d854b95ddb1d448ecaa6389578e221c1cc30383 Mon Sep 17 00:00:00 2001 From: mahendra-google Date: Tue, 25 Mar 2025 03:26:44 -0700 Subject: [PATCH 08/29] move object test update --- .../MoveObjectTest.cs | 12 ++++++------ .../StorageFixture.cs | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs index 634e2bdf4792..ac855866951a 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs @@ -30,14 +30,10 @@ public class MoveObjectTest private string SmallNewObject { get; } = "smallNew.txt"; private string LargeNewObject { get; } = "largeNew.txt"; private string SmallThenLargeObject { get; } = "smallThenLargeNew.txt"; - private Bucket HnsBucket { get; } public MoveObjectTest(StorageFixture fixture) { _fixture = fixture; - HnsBucket = _fixture.CreateAndPopulateHnsBucket(_fixture.HnsBucket); - _fixture.Client.UploadObject(_fixture.HnsBucket, SmallNewObject, "text/plain", new MemoryStream(_fixture.SmallContent)); - _fixture.Client.UploadObject(_fixture.HnsBucket, LargeNewObject, "text/plain", new MemoryStream(_fixture.LargeContent)); } // Moves the source object to the destination object within a bucket with hierarchical namespace enabled. @@ -45,6 +41,7 @@ public MoveObjectTest(StorageFixture fixture) public async Task MoveObjectDefaultAsync() { var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, _fixture.SmallThenLargeObject); + _fixture.Client.UploadObject(_fixture.HnsBucket, LargeNewObject, "text/plain", new MemoryStream(_fixture.LargeContent)); var expected = await _fixture.Client.MoveObjectAsync(_fixture.HnsBucket, _fixture.SmallThenLargeObject, LargeNewObject); using var stream = new MemoryStream(); await _fixture.Client.DownloadObjectAsync(_fixture.HnsBucket, LargeNewObject, stream); @@ -57,12 +54,12 @@ public async Task MoveObjectDefaultAsync() Assert.NotEqual(actual.TimeFinalizedRaw, expected.TimeFinalizedRaw); Assert.NotEqual(actual.UpdatedRaw, expected.UpdatedRaw); Assert.NotEqual(actual.TimeStorageClassUpdatedRaw, expected.TimeStorageClassUpdatedRaw); - if (HnsBucket.Autoclass != null) + if (_fixture.Client.GetBucket(_fixture.HnsBucket).Autoclass != null) { if (expected != null) { Assert.Equal(expected.StorageClass, actual.StorageClass); - Assert.Equal(expected.StorageClass, StorageClasses.Standard); + Assert.Equal(StorageClasses.Standard, expected.StorageClass); } } else @@ -82,6 +79,7 @@ public async Task MoveObjectDefaultAsync() public async Task MoveObjectToDirectorySubDirectoryAsync() { var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, _fixture.SmallObject); + _fixture.Client.UploadObject(_fixture.HnsBucket, SmallNewObject, "text/plain", new MemoryStream(_fixture.SmallContent)); var expected = await _fixture.Client.MoveObjectAsync(_fixture.HnsBucket, _fixture.SmallObject, $"folder/subfolder/" + SmallNewObject); using var stream = new MemoryStream(); await _fixture.Client.DownloadObjectAsync(_fixture.HnsBucket, $"folder/subfolder/" + SmallNewObject, stream); @@ -129,6 +127,8 @@ public async Task MoveObject_With_Correct_PreconditionsAsync() { var bucketUploadedObject = _fixture.Client.UploadObject(_fixture.HnsBucket, "sourceTestFile.txt", null, GenerateData(128)); var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, bucketUploadedObject.Name); + _fixture.Client.UploadObject(_fixture.HnsBucket, SmallThenLargeObject, "text/plain", + new MemoryStream(_fixture.LargeContent)); var expected = _fixture.Client.MoveObject(_fixture.HnsBucket, bucketUploadedObject.Name, SmallThenLargeObject, new MoveObjectOptions { IfSourceMetagenerationMatch = actual.Metageneration, IfSourceGenerationMatch = actual.Generation }); var objects = _fixture.Client.ListObjects(_fixture.HnsBucket).ToList(); diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs index 30f608fd4320..a8d467086aa8 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs @@ -172,6 +172,7 @@ public StorageFixture() CreateBucket(LabelsTestBucket, multiVersion: false); CreateBucket(InitiallyEmptyBucket, multiVersion: false); CreateBucket(SoftDeleteBucket, multiVersion: false, softDelete: true); + CreateAndPopulateHnsBucket(HnsBucket); RequesterPaysClient = CreateRequesterPaysClient(); if (RequesterPaysClient != null) From 74308d7270580b8fe019aa847301dbb959f3b205 Mon Sep 17 00:00:00 2001 From: mahendra-google Date: Tue, 25 Mar 2025 05:31:16 -0700 Subject: [PATCH 09/29] storage client implementation changes --- .../MoveObjectTest.cs | 5 ++--- .../Google.Cloud.Storage.V1/MoveObjectOptions.cs | 13 ------------- .../StorageClientImpl.MoveObject.cs | 1 - 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs index ac855866951a..37e7d3e31c8b 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs @@ -56,9 +56,8 @@ public async Task MoveObjectDefaultAsync() Assert.NotEqual(actual.TimeStorageClassUpdatedRaw, expected.TimeStorageClassUpdatedRaw); if (_fixture.Client.GetBucket(_fixture.HnsBucket).Autoclass != null) { - if (expected != null) + if (_fixture.Client.GetBucket(_fixture.HnsBucket).Autoclass.Enabled == true) { - Assert.Equal(expected.StorageClass, actual.StorageClass); Assert.Equal(StorageClasses.Standard, expected.StorageClass); } } @@ -93,7 +92,7 @@ public async Task MoveObjectToDirectorySubDirectoryAsync() Assert.DoesNotContain(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); } - // Prevent moving the source object to the destination object using bad preconditions (wrong source generation) set. + // Prevent moving the source object to the destination object using bad preconditions (wrong destination generation) set. [Fact] public async Task PreventMoveObject_With_Wrong_GenerationAsync() { diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/MoveObjectOptions.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/MoveObjectOptions.cs index 5d4492bbd724..1fb4540eadaa 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/MoveObjectOptions.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/MoveObjectOptions.cs @@ -23,7 +23,6 @@ namespace Google.Cloud.Storage.V1; /// public sealed class MoveObjectOptions { - /// /// Precondition for moving: the object is only moved if the existing destination object's /// generation matches the given value. @@ -79,18 +78,6 @@ public sealed class MoveObjectOptions /// public Object ExtraMetadata { get; set; } - /// - /// The encryption key to use for this operation. If this property is null, the - /// will be used instead. Use to remove encryption headers from this request. - /// - public EncryptionKey EncryptionKey { get; set; } - - /// - /// The encryption key to use for the source of the copy. If this property is null, the - /// will be used instead. Use if the source is not encrypted. - /// - public EncryptionKey SourceEncryptionKey { get; set; } - /// /// If set, this is the ID of the project which will be billed for the request. /// The caller must have suitable permissions for the project being billed. diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs index 2d7a93f35dae..48d1cf152095 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs @@ -61,7 +61,6 @@ private ObjectsResource.MoveRequest CreateMoveObjectRequest( options?.ModifyRequest(request); RetryOptions retryOptions = options?.RetryOptions ?? RetryOptions.MaybeIdempotent(options?.IfGenerationMatch); MarkAsRetriable(request, retryOptions); - request.ModifyRequest += (options?.SourceEncryptionKey ?? EncryptionKey).ModifyRequest; return request; } } From 57adb0fe3abd0de2f94bdac21e64c9e5deeee057 Mon Sep 17 00:00:00 2001 From: mahendra-google Date: Tue, 25 Mar 2025 22:49:01 -0700 Subject: [PATCH 10/29] linter changes --- .../MoveObjectTest.cs | 4 +--- .../Google.Cloud.Storage.V1.Tests/MoveObjectOptionsTest.cs | 1 + .../Google.Cloud.Storage.V1/StorageClient.MoveObject.cs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs index 37e7d3e31c8b..2d7f851e9e52 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs @@ -12,11 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Google.Apis.Storage.v1.Data; using System.IO; using System.Linq; using System.Net; -using System.Text; using System.Threading.Tasks; using Xunit; using static Google.Cloud.Storage.V1.IntegrationTests.TestHelpers; @@ -36,7 +34,7 @@ public MoveObjectTest(StorageFixture fixture) _fixture = fixture; } - // Moves the source object to the destination object within a bucket with hierarchical namespace enabled. + // Moves the object within a bucket with hierarchical namespace enabled. [Fact] public async Task MoveObjectDefaultAsync() { diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/MoveObjectOptionsTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/MoveObjectOptionsTest.cs index 6c70be44b39a..acc05c624bb1 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/MoveObjectOptionsTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/MoveObjectOptionsTest.cs @@ -17,6 +17,7 @@ using static Google.Apis.Storage.v1.ObjectsResource; namespace Google.Cloud.Storage.V1.Tests; + public class MoveObjectOptionsTest { [Fact] diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs index aacabc0151a4..653132f27fc7 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs @@ -47,7 +47,7 @@ public virtual Object MoveObject( /// Name of the bucket containing the object you want to move. Must not be null. /// The name of the source object to move within the bucket. Must not be null. /// The name of the new object to move within the bucket. Must not be null. - /// Additional options for the copy operation. May be null, in which case appropriate + /// Additional options for the move operation. May be null, in which case appropriate /// defaults will be used. /// The token to monitor for cancellation requests. /// A task representing the asynchronous operation, with a result returning the From dc443da60fd3a8e44ca69d1332ea0cb3af71dbd9 Mon Sep 17 00:00:00 2001 From: mahendra-google Date: Wed, 26 Mar 2025 03:36:08 -0700 Subject: [PATCH 11/29] move object test changes --- .../MoveObjectTest.cs | 125 +++++++++++++----- 1 file changed, 91 insertions(+), 34 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs index 2d7f851e9e52..980c31cd707e 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs @@ -34,36 +34,17 @@ public MoveObjectTest(StorageFixture fixture) _fixture = fixture; } - // Moves the object within a bucket with hierarchical namespace enabled. + // Moves an object within a hierarchical namespace enabled bucket. [Fact] - public async Task MoveObjectDefaultAsync() + public async Task MoveObjectAsync() { var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, _fixture.SmallThenLargeObject); _fixture.Client.UploadObject(_fixture.HnsBucket, LargeNewObject, "text/plain", new MemoryStream(_fixture.LargeContent)); var expected = await _fixture.Client.MoveObjectAsync(_fixture.HnsBucket, _fixture.SmallThenLargeObject, LargeNewObject); using var stream = new MemoryStream(); await _fixture.Client.DownloadObjectAsync(_fixture.HnsBucket, LargeNewObject, stream); + // Assert that the content of the destination object is the same as the source object. Assert.Equal(_fixture.LargeContent, stream.ToArray()); - // Assert that the generation of the destination object is different from the source object indicating a new object at destination is created. - Assert.NotEqual(actual.Generation, expected.Generation); - // Assert that the id of the destination object is different from the source object indicating a new object at destination is created. - Assert.NotEqual(actual.Id, expected.Id); - Assert.NotEqual(actual.TimeCreatedRaw, expected.TimeCreatedRaw); - Assert.NotEqual(actual.TimeFinalizedRaw, expected.TimeFinalizedRaw); - Assert.NotEqual(actual.UpdatedRaw, expected.UpdatedRaw); - Assert.NotEqual(actual.TimeStorageClassUpdatedRaw, expected.TimeStorageClassUpdatedRaw); - if (_fixture.Client.GetBucket(_fixture.HnsBucket).Autoclass != null) - { - if (_fixture.Client.GetBucket(_fixture.HnsBucket).Autoclass.Enabled == true) - { - Assert.Equal(StorageClasses.Standard, expected.StorageClass); - } - } - else - { - Assert.Equal(expected.StorageClass, actual.StorageClass); - } - var objects = _fixture.Client.ListObjects(_fixture.HnsBucket).ToList(); // Assert that the destination object exists after the move. Assert.Contains(objects, obj => obj.Name == expected.Name && obj.Generation == expected.Generation); @@ -71,7 +52,7 @@ public async Task MoveObjectDefaultAsync() Assert.DoesNotContain(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); } - // Move the object to a folders and subfolders by creating folders and subfolders if necessary. + // Move an object to a folders and subfolders by creating folders and subfolders if necessary. [Fact] public async Task MoveObjectToDirectorySubDirectoryAsync() { @@ -80,10 +61,15 @@ public async Task MoveObjectToDirectorySubDirectoryAsync() var expected = await _fixture.Client.MoveObjectAsync(_fixture.HnsBucket, _fixture.SmallObject, $"folder/subfolder/" + SmallNewObject); using var stream = new MemoryStream(); await _fixture.Client.DownloadObjectAsync(_fixture.HnsBucket, $"folder/subfolder/" + SmallNewObject, stream); + // Assert that the content of the destination object is the same as the source object. Assert.Equal(_fixture.SmallContent, stream.ToArray()); // Assert that the generation of the destination object is different from the source object indicating a new object at destination is created. Assert.NotEqual(actual.Generation, expected.Generation); - var objects = _fixture.Client.ListObjects(_fixture.HnsBucket).ToList(); + var options = new ListObjectsOptions + { + MatchGlob = "folder/subfolder/*.txt" + }; + var objects = _fixture.Client.ListObjects(_fixture.HnsBucket, null, options).ToList(); // Assert that the destination object exists after the move. Assert.Contains(objects, obj => obj.Name == expected.Name && obj.Generation == expected.Generation); // Assert that the source object does not exist after the move. @@ -94,9 +80,8 @@ public async Task MoveObjectToDirectorySubDirectoryAsync() [Fact] public async Task PreventMoveObject_With_Wrong_GenerationAsync() { - var bucketUploadedObject = _fixture.Client.UploadObject(_fixture.HnsBucket, "file.txt", null, GenerateData(128)); - var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, bucketUploadedObject.Name); - var exception = Assert.Throws(() => _fixture.Client.MoveObject(_fixture.HnsBucket, bucketUploadedObject.Name, _fixture.SmallObject, + var actual = await _fixture.Client.UploadObjectAsync(_fixture.HnsBucket, "file.txt", null, GenerateData(128)); + var exception = Assert.Throws(() => _fixture.Client.MoveObject(_fixture.HnsBucket, actual.Name, _fixture.SmallObject, new MoveObjectOptions { IfGenerationMatch = 0 })); Assert.Equal(HttpStatusCode.PreconditionFailed, exception.HttpStatusCode); var objects = _fixture.Client.ListObjects(_fixture.HnsBucket).ToList(); @@ -108,9 +93,8 @@ public async Task PreventMoveObject_With_Wrong_GenerationAsync() [Fact] public async Task PreventMoveObject_With_Wrong_SourceMetaGenerationAsync() { - var bucketUploadedObject = _fixture.Client.UploadObject(_fixture.HnsBucket, "sourceFile.txt", null, GenerateData(128)); - var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, bucketUploadedObject.Name); - var exception = Assert.Throws(() => _fixture.Client.MoveObject(_fixture.HnsBucket, bucketUploadedObject.Name, _fixture.SmallObject, + var actual = await _fixture.Client.UploadObjectAsync(_fixture.HnsBucket, "sourceFile.txt", null, GenerateData(128)); + var exception = Assert.Throws(() => _fixture.Client.MoveObject(_fixture.HnsBucket, actual.Name, _fixture.SmallObject, new MoveObjectOptions { IfSourceMetagenerationMatch = 0 })); Assert.Equal(HttpStatusCode.PreconditionFailed, exception.HttpStatusCode); var objects = _fixture.Client.ListObjects(_fixture.HnsBucket).ToList(); @@ -118,15 +102,14 @@ public async Task PreventMoveObject_With_Wrong_SourceMetaGenerationAsync() Assert.Contains(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); } - // Move the source object to the destination object using correct preconditions (source metageneration and generation match) set. + // Move the source object to the destination object using correct preconditions (source metageneration and source generation match) set. [Fact] public async Task MoveObject_With_Correct_PreconditionsAsync() { - var bucketUploadedObject = _fixture.Client.UploadObject(_fixture.HnsBucket, "sourceTestFile.txt", null, GenerateData(128)); - var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, bucketUploadedObject.Name); + var actual = _fixture.Client.UploadObject(_fixture.HnsBucket, "sourceTestFile.txt", null, GenerateData(128)); _fixture.Client.UploadObject(_fixture.HnsBucket, SmallThenLargeObject, "text/plain", new MemoryStream(_fixture.LargeContent)); - var expected = _fixture.Client.MoveObject(_fixture.HnsBucket, bucketUploadedObject.Name, SmallThenLargeObject, + var expected = await _fixture.Client.MoveObjectAsync(_fixture.HnsBucket, actual.Name, SmallThenLargeObject, new MoveObjectOptions { IfSourceMetagenerationMatch = actual.Metageneration, IfSourceGenerationMatch = actual.Generation }); var objects = _fixture.Client.ListObjects(_fixture.HnsBucket).ToList(); // Assert that the destination object exists after the move. @@ -134,4 +117,78 @@ public async Task MoveObject_With_Correct_PreconditionsAsync() // Assert that the source object does not exist after the move. Assert.DoesNotContain(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); } + + [Fact] + public async Task MetadataReturned() + { + var actual = await _fixture.Client.UploadObjectAsync(_fixture.HnsBucket, "sourceMeta.txt", "text/plain", GenerateData(128)); + var destUploadedObject = await _fixture.Client.UploadObjectAsync(_fixture.HnsBucket, "destMeta.txt", "text/plain", + new MemoryStream(_fixture.LargeContent)); + var expected = _fixture.Client.MoveObject(_fixture.HnsBucket, actual.Name, destUploadedObject.Name); + + // Assert that the generation of the destination object is different from the source object indicating a new object at destination is created. + Assert.NotEqual(actual.Generation, expected.Generation); + // Assert that the id of the destination object is different from the source object indicating a new object at destination is created. + Assert.NotEqual(actual.Id, expected.Id); + // Just test the values we expect to be updated and not equal after move operation is performed. + Assert.NotEqual(actual.TimeCreatedRaw, expected.TimeCreatedRaw); + Assert.NotEqual(actual.TimeFinalizedRaw, expected.TimeFinalizedRaw); + Assert.NotEqual(actual.UpdatedRaw, expected.UpdatedRaw); + Assert.NotEqual(actual.TimeStorageClassUpdatedRaw, expected.TimeStorageClassUpdatedRaw); + // Assert that the storage class of the destination object is set to Standard if Autoclass is enabled otherwise no change in storage class. + if (_fixture.Client.GetBucket(_fixture.HnsBucket).Autoclass != null) + { + if (_fixture.Client.GetBucket(_fixture.HnsBucket).Autoclass.Enabled == true) + { + Assert.Equal(StorageClasses.Standard, expected.StorageClass); + } + } + else + { + Assert.Equal(expected.StorageClass, actual.StorageClass); + } + } + + [Fact] + public void MoveObjectFromInvalidBucket() + { + var exception = Assert.Throws(() => _fixture.Client.MoveObject("!!!", _fixture.LargeObject, _fixture.SmallObject)); + Assert.Equal(HttpStatusCode.NotFound, exception.HttpStatusCode); + } + + [Fact] + public void MoveObjectWithInvalidSourceObject() + { + var exception = Assert.Throws(() => _fixture.Client.MoveObject(_fixture.HnsBucket, "!!!", _fixture.SmallObject)); + Assert.Equal(HttpStatusCode.NotFound, exception.HttpStatusCode); + } + + // The move operation can only be performed for HNS-enabled buckets (can change in the future). + [Fact] + public void MoveObjectFromNonHnsBucket() + { + var exception = Assert.Throws(() => _fixture.Client.MoveObject(_fixture.ReadBucket, _fixture.LargeObject, _fixture.SmallObject)); + Assert.Equal(HttpStatusCode.Conflict, exception.HttpStatusCode); + } + + // The move operation can only be performed with different source and destination object id. + [Fact] + public void MoveObjectWithSameObjectId() + { + var exception = Assert.Throws(() => _fixture.Client.MoveObject(_fixture.HnsBucket, _fixture.SmallObject, _fixture.SmallObject)); + Assert.Equal(HttpStatusCode.BadRequest, exception.HttpStatusCode); + } + + // Move operation can be performed on live objects only. + [Fact] + public void MoveObjectWithSoftDeletedObject() + { + var sourceUploadedObject = _fixture.Client.UploadObject(_fixture.HnsBucket, "sourcelive.txt", "text/plain", GenerateData(128)); + _fixture.Client.DeleteObject(_fixture.HnsBucket, sourceUploadedObject.Name); + var actual = _fixture.Client.GetObject(_fixture.HnsBucket, sourceUploadedObject.Name, new GetObjectOptions { SoftDeletedOnly = true, Generation = sourceUploadedObject.Generation }); + var destUploadedObject = _fixture.Client.UploadObject(_fixture.HnsBucket, "dest.txt", "text/plain", + new MemoryStream(_fixture.LargeContent)); + var exception = Assert.Throws(() => _fixture.Client.MoveObject(_fixture.HnsBucket, actual.Name, destUploadedObject.Name)); + Assert.Equal(HttpStatusCode.NotFound, exception.HttpStatusCode); + } } From f6fe52f945e165fe08eff0327cc0fa20714beabe Mon Sep 17 00:00:00 2001 From: Mahendra Date: Thu, 3 Apr 2025 10:14:55 +0530 Subject: [PATCH 12/29] Update apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs Co-authored-by: Amanda Tarafa Mas <14878252+amanda-tarafa@users.noreply.github.com> --- .../Google.Cloud.Storage.V1/StorageClient.MoveObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs index 653132f27fc7..06e76820f810 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs @@ -44,7 +44,7 @@ public virtual Object MoveObject( /// Moves an object within a bucket with hierarchical namespace enabled. This method uses the /// moveObject underlying API operation for more flexibility and reliability. /// - /// Name of the bucket containing the object you want to move. Must not be null. + /// Name of the bucket containing the object to move. Must not be null. /// The name of the source object to move within the bucket. Must not be null. /// The name of the new object to move within the bucket. Must not be null. /// Additional options for the move operation. May be null, in which case appropriate From c9bd462d5f5ff907dde8c9c44ca6f5ed106197eb Mon Sep 17 00:00:00 2001 From: Mahendra Date: Thu, 3 Apr 2025 10:19:20 +0530 Subject: [PATCH 13/29] Update apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs Co-authored-by: Amanda Tarafa Mas <14878252+amanda-tarafa@users.noreply.github.com> --- .../Google.Cloud.Storage.V1/StorageClient.MoveObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs index 06e76820f810..852845947140 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs @@ -27,7 +27,7 @@ public abstract partial class StorageClient /// /// Name of the bucket containing the object you want to move. Must not be null. /// The name of the source object to move within the bucket. Must not be null. - /// The name of the new object to move within the bucket. Must not be null. + /// The name of the new object to move to within the bucket. Must not be null. /// Additional options for the move operation. May be null, in which case appropriate /// defaults will be used. /// The representation of the new storage object resulting from the move. From 7e287f4ba199d5b828349cb200c8d4b1bac05e67 Mon Sep 17 00:00:00 2001 From: Mahendra Date: Thu, 3 Apr 2025 10:20:23 +0530 Subject: [PATCH 14/29] Update apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs Co-authored-by: Amanda Tarafa Mas <14878252+amanda-tarafa@users.noreply.github.com> --- .../Google.Cloud.Storage.V1/StorageClient.MoveObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs index 852845947140..53d49d5f426c 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs @@ -46,7 +46,7 @@ public virtual Object MoveObject( /// /// Name of the bucket containing the object to move. Must not be null. /// The name of the source object to move within the bucket. Must not be null. - /// The name of the new object to move within the bucket. Must not be null. + /// The name of the new object to move to within the bucket. Must not be null. /// Additional options for the move operation. May be null, in which case appropriate /// defaults will be used. /// The token to monitor for cancellation requests. From 71baf65ea36431026362164d72b70373e76edc8a Mon Sep 17 00:00:00 2001 From: Mahendra Date: Thu, 3 Apr 2025 10:41:53 +0530 Subject: [PATCH 15/29] Update apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs Update apis.json --- .../Google.Cloud.Storage.V1/StorageClient.MoveObject.cs | 2 +- generator-input/apis.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs index 53d49d5f426c..502628eda6be 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs @@ -55,7 +55,7 @@ public virtual Object MoveObject( public virtual Task MoveObjectAsync( string sourceBucket, string sourceObjectName, - string destinationObjectName = null, + string destinationObjectName, MoveObjectOptions options = null, CancellationToken cancellationToken = default) { diff --git a/generator-input/apis.json b/generator-input/apis.json index bb3955102090..8034cffa99a5 100644 --- a/generator-input/apis.json +++ b/generator-input/apis.json @@ -5249,7 +5249,7 @@ "description": "Recommended Google client library to access the Google Cloud Storage API. It wraps the Google.Apis.Storage.v1 client library, making common operations simpler in client code. Google Cloud Storage stores and retrieves potentially large, immutable data objects.", "dependencies": { "Google.Api.Gax.Rest": "default", - "Google.Apis.Storage.v1": "1.68.0.3604" + "Google.Apis.Storage.v1": "1.69.0.3707" }, "testDependencies": { "Google.Api.Gax.Testing": "default", @@ -6728,4 +6728,4 @@ ] } ] -} \ No newline at end of file +} From eb34f0602be3dc1616c0ae409e8ff29319ad28fb Mon Sep 17 00:00:00 2001 From: Mahendra Date: Thu, 3 Apr 2025 20:58:44 +0530 Subject: [PATCH 16/29] fixture changes --- .../StorageFixture.cs | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs index a8d467086aa8..93f1e1d0a363 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs @@ -172,7 +172,7 @@ public StorageFixture() CreateBucket(LabelsTestBucket, multiVersion: false); CreateBucket(InitiallyEmptyBucket, multiVersion: false); CreateBucket(SoftDeleteBucket, multiVersion: false, softDelete: true); - CreateAndPopulateHnsBucket(HnsBucket); + CreateAndPopulateHnsBucket(); RequesterPaysClient = CreateRequesterPaysClient(); if (RequesterPaysClient != null) @@ -255,7 +255,7 @@ void CreateObject() } - internal Bucket CreateBucket(string name, bool multiVersion, bool softDelete = false, bool registerForDeletion = true) + internal Bucket CreateBucket(string name, bool multiVersion, bool softDelete = false, bool registerForDeletion = true, bool hnsEnabled = false) { var bucket = Client.CreateBucket(ProjectId, new Bucket @@ -264,6 +264,11 @@ internal Bucket CreateBucket(string name, bool multiVersion, bool softDelete = f Versioning = new Bucket.VersioningData { Enabled = multiVersion }, // The minimum allowed for soft delete is 7 days. SoftDeletePolicy = softDelete ? new Bucket.SoftDeletePolicyData { RetentionDurationSeconds = (int) TimeSpan.FromDays(7).TotalSeconds } : null, + IamConfiguration = hnsEnabled ? new Bucket.IamConfigurationData + { + UniformBucketLevelAccess = new Bucket.IamConfigurationData.UniformBucketLevelAccessData { Enabled = true } + } : null, + HierarchicalNamespace = hnsEnabled ? new Bucket.HierarchicalNamespaceData { Enabled = true } : null, }); SleepAfterBucketCreateDelete(); if (registerForDeletion) @@ -273,29 +278,15 @@ internal Bucket CreateBucket(string name, bool multiVersion, bool softDelete = f return bucket; } - internal Bucket CreateAndPopulateHnsBucket(string name) + private void CreateAndPopulateHnsBucket() { - var storage = StorageClient.Create(); - var bucket = storage.CreateBucket(ProjectId, - new Bucket - { - Name = name, - IamConfiguration = new Bucket.IamConfigurationData - { - UniformBucketLevelAccess = new Bucket.IamConfigurationData.UniformBucketLevelAccessData { Enabled = true } - }, - HierarchicalNamespace = new Bucket.HierarchicalNamespaceData { Enabled = true } - }); + CreateBucket(HnsBucket, multiVersion: false, hnsEnabled: true); Client.UploadObject(name, SmallThenLargeObject, "text/plain", new MemoryStream(LargeContent)); Client.UploadObject(name, SmallObject, "text/plain", new MemoryStream(SmallContent)); foreach (var nameoObjectsInFolder in s_objectsInFolders) { Client.UploadObject(name, nameoObjectsInFolder, "text/plain", new MemoryStream(SmallContent)); } - - SleepAfterBucketCreateDelete(); - RegisterBucketToDelete(name); - return bucket; } internal string GenerateBucketName() => IdGenerator.FromGuid(prefix: BucketPrefix, separator: "", maxLength: 63); From 370c40b678344e530658bab39892b527ea45dd77 Mon Sep 17 00:00:00 2001 From: Mahendra Date: Thu, 3 Apr 2025 21:15:03 +0530 Subject: [PATCH 17/29] Update StorageFixture.cs --- .../StorageFixture.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs index 93f1e1d0a363..636cadd93072 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs @@ -281,11 +281,11 @@ internal Bucket CreateBucket(string name, bool multiVersion, bool softDelete = f private void CreateAndPopulateHnsBucket() { CreateBucket(HnsBucket, multiVersion: false, hnsEnabled: true); - Client.UploadObject(name, SmallThenLargeObject, "text/plain", new MemoryStream(LargeContent)); - Client.UploadObject(name, SmallObject, "text/plain", new MemoryStream(SmallContent)); + Client.UploadObject(HnsBucket, SmallThenLargeObject, "text/plain", new MemoryStream(LargeContent)); + Client.UploadObject(HnsBucket, SmallObject, "text/plain", new MemoryStream(SmallContent)); foreach (var nameoObjectsInFolder in s_objectsInFolders) { - Client.UploadObject(name, nameoObjectsInFolder, "text/plain", new MemoryStream(SmallContent)); + Client.UploadObject(HnsBucket, nameoObjectsInFolder, "text/plain", new MemoryStream(SmallContent)); } } From 0d6641a5a5e77c27e7724af7fc2d03c7765ddc03 Mon Sep 17 00:00:00 2001 From: mahendra-google Date: Fri, 4 Apr 2025 02:38:59 -0700 Subject: [PATCH 18/29] move object fixture and test recommended changes --- .../MoveObjectTest.cs | 102 ++++++++++-------- .../StorageFixture.cs | 13 +-- 2 files changed, 60 insertions(+), 55 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs index 980c31cd707e..b69307c3f4dc 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Google.Cloud.ClientTesting; using System.IO; using System.Linq; using System.Net; @@ -25,27 +26,38 @@ namespace Google.Cloud.Storage.V1.IntegrationTests; public class MoveObjectTest { private readonly StorageFixture _fixture; - private string SmallNewObject { get; } = "smallNew.txt"; - private string LargeNewObject { get; } = "largeNew.txt"; - private string SmallThenLargeObject { get; } = "smallThenLargeNew.txt"; + private readonly string _bucket; + private readonly string _name1; + private readonly string _name2; + private readonly string _contentType1; + private readonly string _contentType2; + private readonly MemoryStream _source1; + private readonly MemoryStream _source2; public MoveObjectTest(StorageFixture fixture) { _fixture = fixture; + _bucket = _fixture.HnsBucket; + _name1 = IdGenerator.FromGuid(); + _name2 = IdGenerator.FromGuid(); + _contentType1 = "application/octet-stream"; + _contentType2 = "application/x-replaced"; + _source1 = GenerateData(100); + _source2 = GenerateData(50); } // Moves an object within a hierarchical namespace enabled bucket. [Fact] public async Task MoveObjectAsync() { - var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, _fixture.SmallThenLargeObject); - _fixture.Client.UploadObject(_fixture.HnsBucket, LargeNewObject, "text/plain", new MemoryStream(_fixture.LargeContent)); - var expected = await _fixture.Client.MoveObjectAsync(_fixture.HnsBucket, _fixture.SmallThenLargeObject, LargeNewObject); + var actual = await _fixture.Client.UploadObjectAsync(_bucket, _name1, _contentType1, _source1); + _fixture.Client.UploadObject(_bucket, _name2, _contentType2, _source2); + var expected = await _fixture.Client.MoveObjectAsync(_bucket, actual.Name, _name2); using var stream = new MemoryStream(); - await _fixture.Client.DownloadObjectAsync(_fixture.HnsBucket, LargeNewObject, stream); + await _fixture.Client.DownloadObjectAsync(_bucket, _name2, stream); // Assert that the content of the destination object is the same as the source object. - Assert.Equal(_fixture.LargeContent, stream.ToArray()); - var objects = _fixture.Client.ListObjects(_fixture.HnsBucket).ToList(); + Assert.Equal(_source1.ToArray(), stream.ToArray()); + var objects = _fixture.Client.ListObjects(_bucket).ToList(); // Assert that the destination object exists after the move. Assert.Contains(objects, obj => obj.Name == expected.Name && obj.Generation == expected.Generation); // Assert that the source object does not exist after the move. @@ -56,20 +68,20 @@ public async Task MoveObjectAsync() [Fact] public async Task MoveObjectToDirectorySubDirectoryAsync() { - var actual = await _fixture.Client.GetObjectAsync(_fixture.HnsBucket, _fixture.SmallObject); - _fixture.Client.UploadObject(_fixture.HnsBucket, SmallNewObject, "text/plain", new MemoryStream(_fixture.SmallContent)); - var expected = await _fixture.Client.MoveObjectAsync(_fixture.HnsBucket, _fixture.SmallObject, $"folder/subfolder/" + SmallNewObject); + var actual = await _fixture.Client.UploadObjectAsync(_bucket, _name1, _contentType1, _source1); + _fixture.Client.UploadObject(_bucket, _name2, _contentType2, _source2); + var expected = await _fixture.Client.MoveObjectAsync(_bucket, _name1, $"folder/subfolder/" + _name2); using var stream = new MemoryStream(); - await _fixture.Client.DownloadObjectAsync(_fixture.HnsBucket, $"folder/subfolder/" + SmallNewObject, stream); + await _fixture.Client.DownloadObjectAsync(_bucket, $"folder/subfolder/" + _name2, stream); // Assert that the content of the destination object is the same as the source object. - Assert.Equal(_fixture.SmallContent, stream.ToArray()); + Assert.Equal(_source1.ToArray(), stream.ToArray()); // Assert that the generation of the destination object is different from the source object indicating a new object at destination is created. Assert.NotEqual(actual.Generation, expected.Generation); var options = new ListObjectsOptions { - MatchGlob = "folder/subfolder/*.txt" + MatchGlob = "folder/subfolder/*" }; - var objects = _fixture.Client.ListObjects(_fixture.HnsBucket, null, options).ToList(); + var objects = _fixture.Client.ListObjects(_bucket, null, options).ToList(); // Assert that the destination object exists after the move. Assert.Contains(objects, obj => obj.Name == expected.Name && obj.Generation == expected.Generation); // Assert that the source object does not exist after the move. @@ -80,11 +92,12 @@ public async Task MoveObjectToDirectorySubDirectoryAsync() [Fact] public async Task PreventMoveObject_With_Wrong_GenerationAsync() { - var actual = await _fixture.Client.UploadObjectAsync(_fixture.HnsBucket, "file.txt", null, GenerateData(128)); - var exception = Assert.Throws(() => _fixture.Client.MoveObject(_fixture.HnsBucket, actual.Name, _fixture.SmallObject, + var actual = await _fixture.Client.UploadObjectAsync(_bucket, _name1, _contentType1, _source1); + await _fixture.Client.UploadObjectAsync(_bucket, _name2, _contentType2, _source2); + var exception = Assert.Throws(() => _fixture.Client.MoveObject(_bucket, actual.Name, _name2, new MoveObjectOptions { IfGenerationMatch = 0 })); Assert.Equal(HttpStatusCode.PreconditionFailed, exception.HttpStatusCode); - var objects = _fixture.Client.ListObjects(_fixture.HnsBucket).ToList(); + var objects = _fixture.Client.ListObjects(_bucket).ToList(); // Assert that the source object is not deleted and destination object is not overwritten. Assert.Contains(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); } @@ -93,11 +106,11 @@ public async Task PreventMoveObject_With_Wrong_GenerationAsync() [Fact] public async Task PreventMoveObject_With_Wrong_SourceMetaGenerationAsync() { - var actual = await _fixture.Client.UploadObjectAsync(_fixture.HnsBucket, "sourceFile.txt", null, GenerateData(128)); - var exception = Assert.Throws(() => _fixture.Client.MoveObject(_fixture.HnsBucket, actual.Name, _fixture.SmallObject, + var actual = await _fixture.Client.UploadObjectAsync(_bucket, _name1, _contentType1, _source1); + var exception = Assert.Throws(() => _fixture.Client.MoveObject(_bucket, actual.Name, _name2, new MoveObjectOptions { IfSourceMetagenerationMatch = 0 })); Assert.Equal(HttpStatusCode.PreconditionFailed, exception.HttpStatusCode); - var objects = _fixture.Client.ListObjects(_fixture.HnsBucket).ToList(); + var objects = _fixture.Client.ListObjects(_bucket).ToList(); // Assert that the source object is not deleted and destination object is not overwritten. Assert.Contains(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); } @@ -106,12 +119,11 @@ public async Task PreventMoveObject_With_Wrong_SourceMetaGenerationAsync() [Fact] public async Task MoveObject_With_Correct_PreconditionsAsync() { - var actual = _fixture.Client.UploadObject(_fixture.HnsBucket, "sourceTestFile.txt", null, GenerateData(128)); - _fixture.Client.UploadObject(_fixture.HnsBucket, SmallThenLargeObject, "text/plain", - new MemoryStream(_fixture.LargeContent)); - var expected = await _fixture.Client.MoveObjectAsync(_fixture.HnsBucket, actual.Name, SmallThenLargeObject, + var actual = await _fixture.Client.UploadObjectAsync(_bucket, _name1, _contentType1, _source1); + _fixture.Client.UploadObject(_bucket, _name2, _contentType2, _source2); + var expected = await _fixture.Client.MoveObjectAsync(_bucket, actual.Name, _name2, new MoveObjectOptions { IfSourceMetagenerationMatch = actual.Metageneration, IfSourceGenerationMatch = actual.Generation }); - var objects = _fixture.Client.ListObjects(_fixture.HnsBucket).ToList(); + var objects = _fixture.Client.ListObjects(_bucket).ToList(); // Assert that the destination object exists after the move. Assert.Contains(objects, obj => obj.Name == expected.Name && obj.Generation == expected.Generation); // Assert that the source object does not exist after the move. @@ -121,10 +133,9 @@ public async Task MoveObject_With_Correct_PreconditionsAsync() [Fact] public async Task MetadataReturned() { - var actual = await _fixture.Client.UploadObjectAsync(_fixture.HnsBucket, "sourceMeta.txt", "text/plain", GenerateData(128)); - var destUploadedObject = await _fixture.Client.UploadObjectAsync(_fixture.HnsBucket, "destMeta.txt", "text/plain", - new MemoryStream(_fixture.LargeContent)); - var expected = _fixture.Client.MoveObject(_fixture.HnsBucket, actual.Name, destUploadedObject.Name); + var actual = await _fixture.Client.UploadObjectAsync(_bucket, _name1, _contentType1, _source1); + var uploadedObject = await _fixture.Client.UploadObjectAsync(_bucket, _name2, _contentType2, _source2); + var expected = _fixture.Client.MoveObject(_bucket, actual.Name, uploadedObject.Name); // Assert that the generation of the destination object is different from the source object indicating a new object at destination is created. Assert.NotEqual(actual.Generation, expected.Generation); @@ -136,9 +147,9 @@ public async Task MetadataReturned() Assert.NotEqual(actual.UpdatedRaw, expected.UpdatedRaw); Assert.NotEqual(actual.TimeStorageClassUpdatedRaw, expected.TimeStorageClassUpdatedRaw); // Assert that the storage class of the destination object is set to Standard if Autoclass is enabled otherwise no change in storage class. - if (_fixture.Client.GetBucket(_fixture.HnsBucket).Autoclass != null) + if (_fixture.Client.GetBucket(_bucket).Autoclass != null) { - if (_fixture.Client.GetBucket(_fixture.HnsBucket).Autoclass.Enabled == true) + if (_fixture.Client.GetBucket(_bucket).Autoclass.Enabled == true) { Assert.Equal(StorageClasses.Standard, expected.StorageClass); } @@ -152,14 +163,17 @@ public async Task MetadataReturned() [Fact] public void MoveObjectFromInvalidBucket() { - var exception = Assert.Throws(() => _fixture.Client.MoveObject("!!!", _fixture.LargeObject, _fixture.SmallObject)); + _fixture.Client.UploadObject(_bucket, _name1, _contentType1, _source1); + _fixture.Client.UploadObject(_bucket, _name2, _contentType2, _source2); + var exception = Assert.Throws(() => _fixture.Client.MoveObject("!!!", _name1, _name2)); Assert.Equal(HttpStatusCode.NotFound, exception.HttpStatusCode); } [Fact] public void MoveObjectWithInvalidSourceObject() { - var exception = Assert.Throws(() => _fixture.Client.MoveObject(_fixture.HnsBucket, "!!!", _fixture.SmallObject)); + _fixture.Client.UploadObject(_bucket, _name1, _contentType1, _source1); + var exception = Assert.Throws(() => _fixture.Client.MoveObject(_bucket, "!!!", _name1)); Assert.Equal(HttpStatusCode.NotFound, exception.HttpStatusCode); } @@ -167,7 +181,9 @@ public void MoveObjectWithInvalidSourceObject() [Fact] public void MoveObjectFromNonHnsBucket() { - var exception = Assert.Throws(() => _fixture.Client.MoveObject(_fixture.ReadBucket, _fixture.LargeObject, _fixture.SmallObject)); + _fixture.Client.UploadObject(_bucket, _name1, _contentType1, _source1); + _fixture.Client.UploadObject(_bucket, _name2, _contentType2, _source2); + var exception = Assert.Throws(() => _fixture.Client.MoveObject(_fixture.ReadBucket, _name1, _name2)); Assert.Equal(HttpStatusCode.Conflict, exception.HttpStatusCode); } @@ -175,7 +191,8 @@ public void MoveObjectFromNonHnsBucket() [Fact] public void MoveObjectWithSameObjectId() { - var exception = Assert.Throws(() => _fixture.Client.MoveObject(_fixture.HnsBucket, _fixture.SmallObject, _fixture.SmallObject)); + _fixture.Client.UploadObject(_bucket, _name1, _contentType1, _source1); + var exception = Assert.Throws(() => _fixture.Client.MoveObject(_bucket, _name1, _name1)); Assert.Equal(HttpStatusCode.BadRequest, exception.HttpStatusCode); } @@ -183,12 +200,11 @@ public void MoveObjectWithSameObjectId() [Fact] public void MoveObjectWithSoftDeletedObject() { - var sourceUploadedObject = _fixture.Client.UploadObject(_fixture.HnsBucket, "sourcelive.txt", "text/plain", GenerateData(128)); - _fixture.Client.DeleteObject(_fixture.HnsBucket, sourceUploadedObject.Name); - var actual = _fixture.Client.GetObject(_fixture.HnsBucket, sourceUploadedObject.Name, new GetObjectOptions { SoftDeletedOnly = true, Generation = sourceUploadedObject.Generation }); - var destUploadedObject = _fixture.Client.UploadObject(_fixture.HnsBucket, "dest.txt", "text/plain", - new MemoryStream(_fixture.LargeContent)); - var exception = Assert.Throws(() => _fixture.Client.MoveObject(_fixture.HnsBucket, actual.Name, destUploadedObject.Name)); + var sourceUploadedObject = _fixture.Client.UploadObject(_bucket, _name1, _contentType1, _source1); + _fixture.Client.DeleteObject(_bucket, sourceUploadedObject.Name); + var actual = _fixture.Client.GetObject(_bucket, sourceUploadedObject.Name, new GetObjectOptions { SoftDeletedOnly = true, Generation = sourceUploadedObject.Generation }); + var destinationUploadedObject = _fixture.Client.UploadObject(_bucket, _name2, _contentType2, _source2); + var exception = Assert.Throws(() => _fixture.Client.MoveObject(_bucket, actual.Name, destinationUploadedObject.Name)); Assert.Equal(HttpStatusCode.NotFound, exception.HttpStatusCode); } } diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs index 636cadd93072..1c3b799dcb06 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs @@ -172,7 +172,7 @@ public StorageFixture() CreateBucket(LabelsTestBucket, multiVersion: false); CreateBucket(InitiallyEmptyBucket, multiVersion: false); CreateBucket(SoftDeleteBucket, multiVersion: false, softDelete: true); - CreateAndPopulateHnsBucket(); + CreateBucket(HnsBucket, multiVersion: false, hnsEnabled: true); RequesterPaysClient = CreateRequesterPaysClient(); if (RequesterPaysClient != null) @@ -278,17 +278,6 @@ internal Bucket CreateBucket(string name, bool multiVersion, bool softDelete = f return bucket; } - private void CreateAndPopulateHnsBucket() - { - CreateBucket(HnsBucket, multiVersion: false, hnsEnabled: true); - Client.UploadObject(HnsBucket, SmallThenLargeObject, "text/plain", new MemoryStream(LargeContent)); - Client.UploadObject(HnsBucket, SmallObject, "text/plain", new MemoryStream(SmallContent)); - foreach (var nameoObjectsInFolder in s_objectsInFolders) - { - Client.UploadObject(HnsBucket, nameoObjectsInFolder, "text/plain", new MemoryStream(SmallContent)); - } - } - internal string GenerateBucketName() => IdGenerator.FromGuid(prefix: BucketPrefix, separator: "", maxLength: 63); private void CreateAndPopulateReadBucket() From c3bade94854690b19633edf47c39707f0d01189e Mon Sep 17 00:00:00 2001 From: mahendra-google Date: Fri, 4 Apr 2025 05:12:31 -0700 Subject: [PATCH 19/29] Extrametadata removed from move object --- .../Google.Cloud.Storage.V1/MoveObjectOptions.cs | 7 ------- .../StorageClientImpl.MoveObject.cs | 1 - 2 files changed, 8 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/MoveObjectOptions.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/MoveObjectOptions.cs index 1fb4540eadaa..ddb9928ffc6f 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/MoveObjectOptions.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/MoveObjectOptions.cs @@ -71,13 +71,6 @@ public sealed class MoveObjectOptions /// public long? IfSourceMetagenerationNotMatch { get; set; } - /// - /// Additional object metadata for the new object. This can be used to specify the storage - /// class of the new object, the content type etc. If this property is not set, the existing - /// object metadata will be used unchanged. - /// - public Object ExtraMetadata { get; set; } - /// /// If set, this is the ID of the project which will be billed for the request. /// The caller must have suitable permissions for the project being billed. diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs index 48d1cf152095..84ed16d2958d 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClientImpl.MoveObject.cs @@ -56,7 +56,6 @@ private ObjectsResource.MoveRequest CreateMoveObjectRequest( GaxPreconditions.CheckNotNull(sourceBucket, nameof(sourceBucket)); GaxPreconditions.CheckNotNull(sourceObjectName, nameof(sourceObjectName)); GaxPreconditions.CheckNotNull(destinationObjectName, nameof(destinationObjectName)); - Object obj = options?.ExtraMetadata ?? new Object(); var request = Service.Objects.Move(sourceBucket, sourceObjectName, destinationObjectName); options?.ModifyRequest(request); RetryOptions retryOptions = options?.RetryOptions ?? RetryOptions.MaybeIdempotent(options?.IfGenerationMatch); From 3458a7df94e2cd788f92fbebecc81c193e7e7eae Mon Sep 17 00:00:00 2001 From: Mahendra Date: Wed, 9 Apr 2025 14:30:33 +0530 Subject: [PATCH 20/29] Update apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs Co-authored-by: Amanda Tarafa Mas <14878252+amanda-tarafa@users.noreply.github.com> --- .../Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs index 1c3b799dcb06..5b27f08810a8 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/StorageFixture.cs @@ -64,7 +64,7 @@ public sealed class StorageFixture : CloudProjectFixtureBase, ICollectionFixture /// /// Name of a bucket with hierarchical namespace enabled /// - public string HnsBucket => BucketPrefix + "hns"; + public string HnsBucket => BucketPrefix + "-hns"; /// /// A small amount of content. Do not mutate the array. From 2ad2f99dacfdefaa1ebdf4d546c46ebe1f8d1fe2 Mon Sep 17 00:00:00 2001 From: Mahendra Date: Wed, 9 Apr 2025 14:43:59 +0530 Subject: [PATCH 21/29] Update apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs Update apis.json --- .../Google.Cloud.Storage.V1/StorageClient.MoveObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs index 502628eda6be..7e410a2daa7f 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/StorageClient.MoveObject.cs @@ -25,7 +25,7 @@ public abstract partial class StorageClient /// Moves an object within a bucket with hierarchical namespace enabled. This method uses the /// moveObject underlying API operation for more flexibility and reliability. /// - /// Name of the bucket containing the object you want to move. Must not be null. + /// Name of the bucket containing the object to move. Must not be null. /// The name of the source object to move within the bucket. Must not be null. /// The name of the new object to move to within the bucket. Must not be null. /// Additional options for the move operation. May be null, in which case appropriate From ab25509d909bab822021c099aa86e7ad5feaf490 Mon Sep 17 00:00:00 2001 From: Mahendra Date: Thu, 10 Apr 2025 14:24:50 +0530 Subject: [PATCH 22/29] Update apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs Co-authored-by: Amanda Tarafa Mas <14878252+amanda-tarafa@users.noreply.github.com> --- .../MoveObjectTest.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs index b69307c3f4dc..5f803561a7ac 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs @@ -38,12 +38,10 @@ public MoveObjectTest(StorageFixture fixture) { _fixture = fixture; _bucket = _fixture.HnsBucket; - _name1 = IdGenerator.FromGuid(); - _name2 = IdGenerator.FromGuid(); - _contentType1 = "application/octet-stream"; - _contentType2 = "application/x-replaced"; - _source1 = GenerateData(100); - _source2 = GenerateData(50); + _originName = IdGenerator.FromGuid(); + _destinationName = IdGenerator.FromGuid(); + _contentType = "application/octet-stream"; + _data = GenerateData(100); } // Moves an object within a hierarchical namespace enabled bucket. From 8aa845e68421c01f94014959306ee1115aa35e23 Mon Sep 17 00:00:00 2001 From: Mahendra Date: Thu, 10 Apr 2025 14:25:35 +0530 Subject: [PATCH 23/29] Update apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs Co-authored-by: Amanda Tarafa Mas <14878252+amanda-tarafa@users.noreply.github.com> --- .../MoveObjectTest.cs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs index 5f803561a7ac..b584976e3e5f 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs @@ -48,18 +48,17 @@ public MoveObjectTest(StorageFixture fixture) [Fact] public async Task MoveObjectAsync() { - var actual = await _fixture.Client.UploadObjectAsync(_bucket, _name1, _contentType1, _source1); - _fixture.Client.UploadObject(_bucket, _name2, _contentType2, _source2); - var expected = await _fixture.Client.MoveObjectAsync(_bucket, actual.Name, _name2); + await _fixture.Client.UploadObjectAsync(_bucket, _originName, _contentType, _data); + + await _fixture.Client.MoveObjectAsync(_bucket, _originName, _destinationName); + + var objects = _fixture.Client.ListObjects(_bucket); + Assert.DoesNotContain(objects, obj => obj.Name == _originName); + Assert.Contains(objects, obj => obj.Name == _destinationName); + using var stream = new MemoryStream(); - await _fixture.Client.DownloadObjectAsync(_bucket, _name2, stream); - // Assert that the content of the destination object is the same as the source object. - Assert.Equal(_source1.ToArray(), stream.ToArray()); - var objects = _fixture.Client.ListObjects(_bucket).ToList(); - // Assert that the destination object exists after the move. - Assert.Contains(objects, obj => obj.Name == expected.Name && obj.Generation == expected.Generation); - // Assert that the source object does not exist after the move. - Assert.DoesNotContain(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); + await _fixture.Client.DownloadObjectAsync(_bucket, _destinationName, stream); + Assert.Equal(_data.ToArray(), stream.ToArray()); } // Move an object to a folders and subfolders by creating folders and subfolders if necessary. From b5da67241f5d267c69db127962725957b8d796df Mon Sep 17 00:00:00 2001 From: Mahendra Date: Thu, 10 Apr 2025 14:26:45 +0530 Subject: [PATCH 24/29] Update apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs Co-authored-by: Amanda Tarafa Mas <14878252+amanda-tarafa@users.noreply.github.com> --- .../Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs index b584976e3e5f..c7ca21a312f3 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs @@ -85,9 +85,8 @@ public async Task MoveObjectToDirectorySubDirectoryAsync() Assert.DoesNotContain(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); } - // Prevent moving the source object to the destination object using bad preconditions (wrong destination generation) set. [Fact] - public async Task PreventMoveObject_With_Wrong_GenerationAsync() + public async Task MoveObjectAsync_GenerationMissmatch_Fails() { var actual = await _fixture.Client.UploadObjectAsync(_bucket, _name1, _contentType1, _source1); await _fixture.Client.UploadObjectAsync(_bucket, _name2, _contentType2, _source2); From 3c013b5a1bc816a84b1825994561d4994c9f980e Mon Sep 17 00:00:00 2001 From: Mahendra Date: Thu, 10 Apr 2025 14:27:18 +0530 Subject: [PATCH 25/29] Update apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs Co-authored-by: Amanda Tarafa Mas <14878252+amanda-tarafa@users.noreply.github.com> --- .../MoveObjectTest.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs index c7ca21a312f3..e6ef2661b0e9 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs @@ -88,14 +88,12 @@ public async Task MoveObjectToDirectorySubDirectoryAsync() [Fact] public async Task MoveObjectAsync_GenerationMissmatch_Fails() { - var actual = await _fixture.Client.UploadObjectAsync(_bucket, _name1, _contentType1, _source1); - await _fixture.Client.UploadObjectAsync(_bucket, _name2, _contentType2, _source2); - var exception = Assert.Throws(() => _fixture.Client.MoveObject(_bucket, actual.Name, _name2, - new MoveObjectOptions { IfGenerationMatch = 0 })); + await _fixture.Client.UploadObjectAsync(_bucket, _originName, _contentType, _data); + + var exception = await Assert.ThrowsAsync(() => _fixture.Client.MoveObjectAsync( + _bucket, _originName, _destinationName, + new MoveObjectOptions { IfGenerationMatch = 0 })); Assert.Equal(HttpStatusCode.PreconditionFailed, exception.HttpStatusCode); - var objects = _fixture.Client.ListObjects(_bucket).ToList(); - // Assert that the source object is not deleted and destination object is not overwritten. - Assert.Contains(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); } // Prevent moving the source object to the destination object using bad preconditions (wrong source metageneration) set. From f8d312d46407488123619379b23f77cf2b079d60 Mon Sep 17 00:00:00 2001 From: Mahendra Date: Thu, 10 Apr 2025 14:27:52 +0530 Subject: [PATCH 26/29] Update apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs Co-authored-by: Amanda Tarafa Mas <14878252+amanda-tarafa@users.noreply.github.com> --- .../MoveObjectTest.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs index e6ef2661b0e9..181b4ba6e79a 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs @@ -96,17 +96,15 @@ public async Task MoveObjectAsync_GenerationMissmatch_Fails() Assert.Equal(HttpStatusCode.PreconditionFailed, exception.HttpStatusCode); } - // Prevent moving the source object to the destination object using bad preconditions (wrong source metageneration) set. [Fact] - public async Task PreventMoveObject_With_Wrong_SourceMetaGenerationAsync() + public async Task MoveObjectAsync_MetaGenerationMissmatch_Fails() { - var actual = await _fixture.Client.UploadObjectAsync(_bucket, _name1, _contentType1, _source1); - var exception = Assert.Throws(() => _fixture.Client.MoveObject(_bucket, actual.Name, _name2, + await _fixture.Client.UploadObjectAsync(_bucket, _originName, _contentType, _data); + + var exception = await Assert.ThrowsAsync(() => _fixture.Client.MoveObjectAsync( + _bucket, _originName, _destinationName, new MoveObjectOptions { IfSourceMetagenerationMatch = 0 })); Assert.Equal(HttpStatusCode.PreconditionFailed, exception.HttpStatusCode); - var objects = _fixture.Client.ListObjects(_bucket).ToList(); - // Assert that the source object is not deleted and destination object is not overwritten. - Assert.Contains(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); } // Move the source object to the destination object using correct preconditions (source metageneration and source generation match) set. From 299c3a4d3642996e741b1022b3bbd0ed637cefc8 Mon Sep 17 00:00:00 2001 From: Mahendra Date: Thu, 10 Apr 2025 14:29:19 +0530 Subject: [PATCH 27/29] Update apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs Co-authored-by: Amanda Tarafa Mas <14878252+amanda-tarafa@users.noreply.github.com> --- .../MoveObjectTest.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs index 181b4ba6e79a..d4fb54d071b7 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs @@ -107,19 +107,21 @@ public async Task MoveObjectAsync_MetaGenerationMissmatch_Fails() Assert.Equal(HttpStatusCode.PreconditionFailed, exception.HttpStatusCode); } - // Move the source object to the destination object using correct preconditions (source metageneration and source generation match) set. [Fact] - public async Task MoveObject_With_Correct_PreconditionsAsync() + public async Task MoveObjectAsync_PreconditionsMatch() { - var actual = await _fixture.Client.UploadObjectAsync(_bucket, _name1, _contentType1, _source1); - _fixture.Client.UploadObject(_bucket, _name2, _contentType2, _source2); - var expected = await _fixture.Client.MoveObjectAsync(_bucket, actual.Name, _name2, - new MoveObjectOptions { IfSourceMetagenerationMatch = actual.Metageneration, IfSourceGenerationMatch = actual.Generation }); - var objects = _fixture.Client.ListObjects(_bucket).ToList(); - // Assert that the destination object exists after the move. - Assert.Contains(objects, obj => obj.Name == expected.Name && obj.Generation == expected.Generation); - // Assert that the source object does not exist after the move. - Assert.DoesNotContain(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); + var origin = await _fixture.Client.UploadObjectAsync(_bucket, _originName, _contentType, _data); + + await _fixture.Client.MoveObjectAsync( + _bucket, _originName, _destinationName, + new MoveObjectOptions + { + IfSourceMetagenerationMatch = origin.Metageneration, + IfSourceGenerationMatch = origin.Generation + }); + var objects = _fixture.Client.ListObjects(_bucket); + + Assert.Contains(objects, obj => obj.Name == expected.Name); } [Fact] From de2d78ad66db87c9ae32d162afa5cf897ee21777 Mon Sep 17 00:00:00 2001 From: mahendra-google Date: Thu, 10 Apr 2025 03:37:16 -0700 Subject: [PATCH 28/29] MoveObjectTest changes Update apis.json Update apis.json --- .../MoveObjectTest.cs | 131 ++---------------- 1 file changed, 13 insertions(+), 118 deletions(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs index d4fb54d071b7..d27271252b88 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/MoveObjectTest.cs @@ -27,12 +27,10 @@ public class MoveObjectTest { private readonly StorageFixture _fixture; private readonly string _bucket; - private readonly string _name1; - private readonly string _name2; - private readonly string _contentType1; - private readonly string _contentType2; - private readonly MemoryStream _source1; - private readonly MemoryStream _source2; + private readonly string _originName; + private readonly string _destinationName; + private readonly string _contentType; + private readonly MemoryStream _data; public MoveObjectTest(StorageFixture fixture) { @@ -44,63 +42,38 @@ public MoveObjectTest(StorageFixture fixture) _data = GenerateData(100); } - // Moves an object within a hierarchical namespace enabled bucket. [Fact] public async Task MoveObjectAsync() { await _fixture.Client.UploadObjectAsync(_bucket, _originName, _contentType, _data); - + await _fixture.Client.MoveObjectAsync(_bucket, _originName, _destinationName); - - var objects = _fixture.Client.ListObjects(_bucket); + + var objects = _fixture.Client.ListObjects(_bucket); Assert.DoesNotContain(objects, obj => obj.Name == _originName); Assert.Contains(objects, obj => obj.Name == _destinationName); - + using var stream = new MemoryStream(); await _fixture.Client.DownloadObjectAsync(_bucket, _destinationName, stream); Assert.Equal(_data.ToArray(), stream.ToArray()); } - // Move an object to a folders and subfolders by creating folders and subfolders if necessary. - [Fact] - public async Task MoveObjectToDirectorySubDirectoryAsync() - { - var actual = await _fixture.Client.UploadObjectAsync(_bucket, _name1, _contentType1, _source1); - _fixture.Client.UploadObject(_bucket, _name2, _contentType2, _source2); - var expected = await _fixture.Client.MoveObjectAsync(_bucket, _name1, $"folder/subfolder/" + _name2); - using var stream = new MemoryStream(); - await _fixture.Client.DownloadObjectAsync(_bucket, $"folder/subfolder/" + _name2, stream); - // Assert that the content of the destination object is the same as the source object. - Assert.Equal(_source1.ToArray(), stream.ToArray()); - // Assert that the generation of the destination object is different from the source object indicating a new object at destination is created. - Assert.NotEqual(actual.Generation, expected.Generation); - var options = new ListObjectsOptions - { - MatchGlob = "folder/subfolder/*" - }; - var objects = _fixture.Client.ListObjects(_bucket, null, options).ToList(); - // Assert that the destination object exists after the move. - Assert.Contains(objects, obj => obj.Name == expected.Name && obj.Generation == expected.Generation); - // Assert that the source object does not exist after the move. - Assert.DoesNotContain(objects, obj => obj.Name == actual.Name && obj.Generation == actual.Generation); - } - [Fact] - public async Task MoveObjectAsync_GenerationMissmatch_Fails() + public async Task MoveObjectAsync_GenerationMismatch_Fails() { await _fixture.Client.UploadObjectAsync(_bucket, _originName, _contentType, _data); var exception = await Assert.ThrowsAsync(() => _fixture.Client.MoveObjectAsync( _bucket, _originName, _destinationName, - new MoveObjectOptions { IfGenerationMatch = 0 })); + new MoveObjectOptions { IfSourceGenerationMatch = 1 })); Assert.Equal(HttpStatusCode.PreconditionFailed, exception.HttpStatusCode); } [Fact] - public async Task MoveObjectAsync_MetaGenerationMissmatch_Fails() + public async Task MoveObjectAsync_MetaGenerationMismatch_Fails() { await _fixture.Client.UploadObjectAsync(_bucket, _originName, _contentType, _data); - + var exception = await Assert.ThrowsAsync(() => _fixture.Client.MoveObjectAsync( _bucket, _originName, _destinationName, new MoveObjectOptions { IfSourceMetagenerationMatch = 0 })); @@ -121,84 +94,6 @@ await _fixture.Client.MoveObjectAsync( }); var objects = _fixture.Client.ListObjects(_bucket); - Assert.Contains(objects, obj => obj.Name == expected.Name); - } - - [Fact] - public async Task MetadataReturned() - { - var actual = await _fixture.Client.UploadObjectAsync(_bucket, _name1, _contentType1, _source1); - var uploadedObject = await _fixture.Client.UploadObjectAsync(_bucket, _name2, _contentType2, _source2); - var expected = _fixture.Client.MoveObject(_bucket, actual.Name, uploadedObject.Name); - - // Assert that the generation of the destination object is different from the source object indicating a new object at destination is created. - Assert.NotEqual(actual.Generation, expected.Generation); - // Assert that the id of the destination object is different from the source object indicating a new object at destination is created. - Assert.NotEqual(actual.Id, expected.Id); - // Just test the values we expect to be updated and not equal after move operation is performed. - Assert.NotEqual(actual.TimeCreatedRaw, expected.TimeCreatedRaw); - Assert.NotEqual(actual.TimeFinalizedRaw, expected.TimeFinalizedRaw); - Assert.NotEqual(actual.UpdatedRaw, expected.UpdatedRaw); - Assert.NotEqual(actual.TimeStorageClassUpdatedRaw, expected.TimeStorageClassUpdatedRaw); - // Assert that the storage class of the destination object is set to Standard if Autoclass is enabled otherwise no change in storage class. - if (_fixture.Client.GetBucket(_bucket).Autoclass != null) - { - if (_fixture.Client.GetBucket(_bucket).Autoclass.Enabled == true) - { - Assert.Equal(StorageClasses.Standard, expected.StorageClass); - } - } - else - { - Assert.Equal(expected.StorageClass, actual.StorageClass); - } - } - - [Fact] - public void MoveObjectFromInvalidBucket() - { - _fixture.Client.UploadObject(_bucket, _name1, _contentType1, _source1); - _fixture.Client.UploadObject(_bucket, _name2, _contentType2, _source2); - var exception = Assert.Throws(() => _fixture.Client.MoveObject("!!!", _name1, _name2)); - Assert.Equal(HttpStatusCode.NotFound, exception.HttpStatusCode); - } - - [Fact] - public void MoveObjectWithInvalidSourceObject() - { - _fixture.Client.UploadObject(_bucket, _name1, _contentType1, _source1); - var exception = Assert.Throws(() => _fixture.Client.MoveObject(_bucket, "!!!", _name1)); - Assert.Equal(HttpStatusCode.NotFound, exception.HttpStatusCode); - } - - // The move operation can only be performed for HNS-enabled buckets (can change in the future). - [Fact] - public void MoveObjectFromNonHnsBucket() - { - _fixture.Client.UploadObject(_bucket, _name1, _contentType1, _source1); - _fixture.Client.UploadObject(_bucket, _name2, _contentType2, _source2); - var exception = Assert.Throws(() => _fixture.Client.MoveObject(_fixture.ReadBucket, _name1, _name2)); - Assert.Equal(HttpStatusCode.Conflict, exception.HttpStatusCode); - } - - // The move operation can only be performed with different source and destination object id. - [Fact] - public void MoveObjectWithSameObjectId() - { - _fixture.Client.UploadObject(_bucket, _name1, _contentType1, _source1); - var exception = Assert.Throws(() => _fixture.Client.MoveObject(_bucket, _name1, _name1)); - Assert.Equal(HttpStatusCode.BadRequest, exception.HttpStatusCode); - } - - // Move operation can be performed on live objects only. - [Fact] - public void MoveObjectWithSoftDeletedObject() - { - var sourceUploadedObject = _fixture.Client.UploadObject(_bucket, _name1, _contentType1, _source1); - _fixture.Client.DeleteObject(_bucket, sourceUploadedObject.Name); - var actual = _fixture.Client.GetObject(_bucket, sourceUploadedObject.Name, new GetObjectOptions { SoftDeletedOnly = true, Generation = sourceUploadedObject.Generation }); - var destinationUploadedObject = _fixture.Client.UploadObject(_bucket, _name2, _contentType2, _source2); - var exception = Assert.Throws(() => _fixture.Client.MoveObject(_bucket, actual.Name, destinationUploadedObject.Name)); - Assert.Equal(HttpStatusCode.NotFound, exception.HttpStatusCode); + Assert.Contains(objects, obj => obj.Name == _destinationName); } } From 4663b1ac604ca46be4d7701ae72a492320ef86cb Mon Sep 17 00:00:00 2001 From: Mahendra Date: Fri, 11 Apr 2025 18:49:45 +0530 Subject: [PATCH 29/29] Update apis.json