From 36af17d6ddec93437868cc15cfdbfb663d329e83 Mon Sep 17 00:00:00 2001 From: mahendra-google Date: Fri, 20 Feb 2026 03:19:17 -0800 Subject: [PATCH] feat(Storage): Add filter to ListObjectOptions and integration tests for object context --- .../CopyObjectTest.cs | 33 +++++++++++++ .../GetObjectTest.cs | 36 ++++++++++++++ .../ListObjectsTest.cs | 32 +++++++++++++ .../PatchObjectTest.cs | 34 ++++++++++++++ .../UpdateObjectTest.cs | 47 +++++++++++++++++++ .../UploadObjectTest.cs | 28 +++++++++++ .../ListObjectsOptionsTest.cs | 5 +- .../ListObjectsOptions.cs | 10 ++++ 8 files changed, 224 insertions(+), 1 deletion(-) diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/CopyObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/CopyObjectTest.cs index d824d0005139..fff60f755592 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/CopyObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/CopyObjectTest.cs @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Google.Apis.Storage.v1.Data; using Google.Cloud.ClientTesting; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Xunit; @@ -120,5 +122,36 @@ public void MetadataOverride() Object fetched = _fixture.Client.GetObject(_fixture.SingleVersionBucket, destName); Assert.Equal("text/html", fetched.ContentType); } + + [Fact] + public void CopyObjectWithObjectContexts() + { + string contextKey = "A\u00F1\u03A9\U0001F680"; + string contextValue = "Ab\u00F1\u03A9\U0001F680"; + var custom = new Dictionary + { + { contextKey, new ObjectCustomContextPayload { Value = contextValue } } + }; + + var destination = new Object + { + Bucket = _fixture.SingleVersionBucket, + Name = IdGenerator.FromGuid(), + ContentType = "test/type", + ContentDisposition = "attachment", + Metadata = new Dictionary { { "x", "y" } }, + Contexts = new Object.ContextsData { Custom = custom } + }; + var source = GenerateData(100); + var result = _fixture.Client.UploadObject(destination, source); + _fixture.Client.CopyObject( + _fixture.SingleVersionBucket, destination.Name, + _fixture.MultiVersionBucket, destination.Name); + Object fetched = _fixture.Client.GetObject(_fixture.MultiVersionBucket, destination.Name); + Assert.Equal(result.Contexts.Custom.Count, fetched.Contexts.Custom.Count); + var fetchedEntry = Assert.Single(fetched.Contexts.Custom); + Assert.Equal(contextKey, fetchedEntry.Key); + Assert.Equal(contextValue, fetchedEntry.Value.Value); + } } } diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/GetObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/GetObjectTest.cs index ca7344ef4ee3..c553c9ef2425 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/GetObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/GetObjectTest.cs @@ -12,11 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Google.Apis.Storage.v1.Data; using Google.Cloud.ClientTesting; +using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Linq; using System.Threading.Tasks; using Xunit; +using static Google.Cloud.Storage.V1.IntegrationTests.TestHelpers; namespace Google.Cloud.Storage.V1.IntegrationTests { @@ -87,5 +91,37 @@ public async Task GetSoftDeleted() Assert.Equal((ulong) _fixture.SmallContent.Length, softDeleted.Size); Assert.NotNull(softDeleted.SoftDeleteTimeDateTimeOffset); } + + [Fact] + public void GetObjectContexts() + { + string contextKey = "A\u00F1\u03A9\U0001F680"; + string contextValue = "Ab\u00F1\u03A9\U0001F680"; + var custom = new Dictionary + { + { contextKey, new ObjectCustomContextPayload { Value = contextValue } } + }; + + var destination = new Object + { + Bucket = _fixture.MultiVersionBucket, + Name = IdGenerator.FromGuid(), + ContentType = "test/type", + ContentDisposition = "attachment", + Metadata = new Dictionary { { "x", "y" } }, + Contexts = new Object.ContextsData { Custom = custom } + }; + var source = GenerateData(100); + var result = _fixture.Client.UploadObject(destination, source); + + + var obj = _fixture.Client.GetObject(_fixture.MultiVersionBucket, destination.Name); + Assert.Equal(_fixture.MultiVersionBucket, obj.Bucket); + Assert.NotNull(obj.Contexts); + Assert.Equal(obj.Contexts.Custom.Count, result.Contexts.Custom.Count); + var resultEntry = Assert.Single(obj.Contexts.Custom); + Assert.Equal(contextKey, resultEntry.Key); + Assert.Equal(contextValue, resultEntry.Value.Value); + } } } diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/ListObjectsTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/ListObjectsTest.cs index cc8ae5eaf2c6..de7e864da133 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/ListObjectsTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/ListObjectsTest.cs @@ -12,12 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Google.Apis.Storage.v1.Data; +using Google.Cloud.ClientTesting; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading; 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 @@ -121,6 +125,34 @@ public void PartialResponses() } } + [Fact] + public void ListObjectsMatchingContextKeyValuePair() + { + string contextKey = "A\u00F1\u03A9\U0001F680"; + string contextValue = "Ab\u00F1\u03A9\U0001F680"; + var custom = new Dictionary + { + { contextKey, new ObjectCustomContextPayload { Value = contextValue } } + + }; + var destination = new Object + { + Bucket = _fixture.ReadBucket, + Name = IdGenerator.FromGuid(), + Contexts = new Object.ContextsData { Custom = custom } + }; + + var source = GenerateData(100); + _fixture.Client.UploadObject(destination, source); + string filter = $@"contexts.""{contextKey}""=""{contextValue}"""; + var options = new ListObjectsOptions { Filter = filter }; + var objects = _fixture.Client.ListObjects(_fixture.ReadBucket, options: options).ToList(); + var obj = Assert.Single(objects); + var fetchedContext = Assert.Single(obj.Contexts.Custom); + Assert.Equal(contextKey, fetchedContext.Key); + Assert.Equal(contextValue, fetchedContext.Value.Value); + } + private async Task AssertObjects(string prefix, ListObjectsOptions options, params string[] expectedNames) { IEnumerable actual = _fixture.Client.ListObjects(_fixture.ReadBucket, prefix, options); diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/PatchObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/PatchObjectTest.cs index 2b6ffd02aef0..3d00cf99fe93 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/PatchObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/PatchObjectTest.cs @@ -12,10 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Google.Apis.Storage.v1.Data; using Google.Cloud.ClientTesting; using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using Xunit; +using static Google.Cloud.Storage.V1.IntegrationTests.TestHelpers; using Object = Google.Apis.Storage.v1.Data.Object; namespace Google.Cloud.Storage.V1.IntegrationTests @@ -50,5 +54,35 @@ public void MissingProperties() Assert.Throws(() => client.PatchObject(new Object { Bucket = _fixture.SingleVersionBucket })); Assert.Throws(() => client.PatchObject(new Object { Name = IdGenerator.FromGuid() })); } + + [Fact] + public void ClearAllObjectContexts() + { + var client = _fixture.Client; + var custom = new Dictionary + { + { "A\u00F1\u03A9\U0001F680", new ObjectCustomContextPayload { Value = "Ab\u00F1\u03A9\U0001F680" } } + }; + + var destination = new Object + { + Bucket = _fixture.SingleVersionBucket, + Name = IdGenerator.FromGuid(), + ContentType = "test/type", + ContentDisposition = "attachment", + Metadata = new Dictionary { { "x", "y" } }, + Contexts = new Object.ContextsData { Custom = custom } + }; + var source = GenerateData(100); + _fixture.Client.UploadObject(destination, source); + + var modifiedCustom = new Dictionary + { + }; + + Object obj = new Object { Name = destination.Name, Bucket = destination.Bucket, ContentType = "text/plain", Contexts = new Object.ContextsData { Custom = modifiedCustom } }; + var updated = client.PatchObject(obj); + Assert.Null(updated.Contexts); + } } } diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/UpdateObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/UpdateObjectTest.cs index efc8a98959fa..52c0f1e78c28 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/UpdateObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/UpdateObjectTest.cs @@ -12,10 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Google.Apis.Storage.v1.Data; using Google.Cloud.ClientTesting; using System.Collections.Generic; using System.IO; using Xunit; +using static Google.Cloud.Storage.V1.IntegrationTests.TestHelpers; namespace Google.Cloud.Storage.V1.IntegrationTests { @@ -39,5 +41,50 @@ public void Success() var updated = client.UpdateObject(obj); Assert.Equal("value", updated.Metadata["key"]); } + + [Fact] + public void UpdateObjectWithObjectContexts() + { + var client = _fixture.Client; + var source = GenerateData(100); + string contextKey = "A\u00F1\u03A9\U0001F680"; + string contextValue = "Ab\u00F1\u03A9\U0001F680"; + var custom = new Dictionary + { + { contextKey, new ObjectCustomContextPayload { Value = contextValue } } + }; + + var destination = new Object + { + Bucket = _fixture.MultiVersionBucket, + Name = IdGenerator.FromGuid(), + ContentType = "test/type", + ContentDisposition = "attachment", + Metadata = new Dictionary { { "x", "y" } }, + Contexts = new Object.ContextsData { Custom = custom } + }; + + var result = client.UploadObject(destination, source); + string modifiedContextValue = "AAb\u00F1\u03A9\U0001F680"; + var modifiedCustom = new Dictionary + { + { contextKey, new ObjectCustomContextPayload { Value = modifiedContextValue } } + + }; + + var modifiedDestination = new Object + { + Bucket = _fixture.MultiVersionBucket, + Name = destination.Name, + Contexts = new Object.ContextsData { Custom = modifiedCustom } + }; + var updated = client.UpdateObject(modifiedDestination); + Assert.NotNull(updated.Contexts.Custom); + Assert.Equal(modifiedCustom.Count, updated.Contexts.Custom.Count); + var resultEntry = Assert.Single(result.Contexts.Custom); + var updatedEntry = Assert.Single(updated.Contexts.Custom); + Assert.Equal(modifiedContextValue, updatedEntry.Value.Value); + Assert.NotEqual(updatedEntry.Value.UpdateTimeRaw, resultEntry.Value.UpdateTimeRaw); + } } } diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/UploadObjectTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/UploadObjectTest.cs index 906b8137e749..b1cb95bb2fba 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/UploadObjectTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/UploadObjectTest.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.Configuration; using System.IO; +using System.Linq; using System.Net; using System.Net.Http; using System.Threading; @@ -91,6 +92,33 @@ public void UploadWithObject() ValidateData(_fixture.MultiVersionBucket, destination.Name, source); } + [Fact] + public void UploadObjectWithObjectContexts() + { + string contextKey = "A\u00F1\u03A9\U0001F680"; + string contextValue = "Ab\u00F1\u03A9\U0001F680"; + var custom = new Dictionary + { + { contextKey, new ObjectCustomContextPayload { Value = contextValue } } + }; + var destination = new Object + { + Bucket = _fixture.MultiVersionBucket, + Name = IdGenerator.FromGuid(), + Contexts = new Object.ContextsData { Custom = custom } + }; + var source = GenerateData(100); + var result = _fixture.Client.UploadObject(destination, source); + Assert.Equal(destination.Name, result.Name); + Assert.Equal(destination.Bucket, result.Bucket); + var resultEntry = Assert.Single(result.Contexts.Custom); + Assert.Equal(contextKey, resultEntry.Key); + Assert.Equal(contextValue, resultEntry.Value.Value); + Assert.NotNull(resultEntry.Value.CreateTimeRaw); + Assert.NotNull(resultEntry.Value.UpdateTimeRaw); + ValidateData(_fixture.MultiVersionBucket, destination.Name, source); + } + [Fact] public async Task UploadAsyncWithProgress() { diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/ListObjectsOptionsTest.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/ListObjectsOptionsTest.cs index 9480d0c91c09..3dace8d8cfea 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/ListObjectsOptionsTest.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/ListObjectsOptionsTest.cs @@ -38,6 +38,7 @@ public void ModifyRequest_DefaultOptions() Assert.Null(request.StartOffset); Assert.Null(request.EndOffset); Assert.Null(request.MatchGlob); + Assert.Null(request.Filter); } [Fact] @@ -58,7 +59,8 @@ public void ModifyRequest_AllOptions() Fields = "items(name),nextPageToken", StartOffset = "start", EndOffset = "end", - MatchGlob = "a/*.txt" + MatchGlob = "a/*.txt", + Filter = "contexts.\"key\":*\"" }; options.ModifyRequest(request); Assert.Equal(10, request.MaxResults); @@ -74,6 +76,7 @@ public void ModifyRequest_AllOptions() Assert.Equal("start", request.StartOffset); Assert.Equal("end", request.EndOffset); Assert.Equal("a/*.txt", request.MatchGlob); + Assert.Equal("contexts.\"key\":*\"", request.Filter); } } } diff --git a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/ListObjectsOptions.cs b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/ListObjectsOptions.cs index 292606227d6b..a7b88500f33f 100644 --- a/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/ListObjectsOptions.cs +++ b/apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/ListObjectsOptions.cs @@ -106,6 +106,12 @@ public sealed class ListObjectsOptions /// public string EndOffset { get; set; } + /// + /// If set, filters results to include only objects to which the specified context is attached. + /// If delimiter is set, the returned prefixes are exempt from this filter. + /// + public string Filter { get; set; } + /// /// Options to pass custom retry configuration for each API request. /// @@ -169,6 +175,10 @@ internal void ModifyRequest(ListRequest request) { request.MatchGlob = MatchGlob; } + if (Filter != null) + { + request.Filter = Filter; + } } } }