From ea0c86d3f3c293a0d04fa2264858d8a88c1dbb54 Mon Sep 17 00:00:00 2001 From: Sean McCullough Date: Wed, 28 May 2025 11:58:42 -0500 Subject: [PATCH 1/6] Set Blob Tags Conditional Headers --- .../Azure.Storage.Blobs/src/BlobBaseClient.cs | 17 +++--- .../src/Generated/AppendBlobRestClient.cs | 2 +- .../src/Generated/BlobRestClient.cs | 36 ++++++++++--- .../src/Generated/BlockBlobRestClient.cs | 2 +- .../src/Generated/ContainerRestClient.cs | 2 +- .../src/Generated/PageBlobRestClient.cs | 2 +- .../src/Generated/ServiceRestClient.cs | 2 +- .../Azure.Storage.Blobs/src/autorest.md | 2 +- .../tests/BlobBaseClientTests.cs | 54 +++++++++++++++++++ 9 files changed, 98 insertions(+), 21 deletions(-) diff --git a/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs b/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs index adf5972374f1..cb7eb05e429c 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs @@ -6250,15 +6250,6 @@ private async Task SetTagsInternal( DiagnosticScope scope = ClientConfiguration.ClientDiagnostics.CreateScope($"{nameof(BlobBaseClient)}.{nameof(SetTags)}"); - conditions.ValidateConditionsNotPresent( - invalidConditions: - BlobRequestConditionProperty.IfModifiedSince - | BlobRequestConditionProperty.IfUnmodifiedSince - | BlobRequestConditionProperty.IfMatch - | BlobRequestConditionProperty.IfNoneMatch, - operationName: nameof(BlobBaseClient.SetTags), - parameterName: nameof(conditions)); - try { scope.Start(); @@ -6269,6 +6260,10 @@ private async Task SetTagsInternal( response = await BlobRestClient.SetTagsAsync( ifTags: conditions?.TagConditions, leaseId: conditions?.LeaseId, + ifModifiedSince: conditions?.IfModifiedSince, + ifUnmodifiedSince: conditions?.IfUnmodifiedSince, + ifMatch: conditions?.IfMatch?.ToString(), + ifNoneMatch: conditions?.IfNoneMatch?.ToString(), tags: tags.ToBlobTags(), cancellationToken: cancellationToken) .ConfigureAwait(false); @@ -6278,6 +6273,10 @@ private async Task SetTagsInternal( response = BlobRestClient.SetTags( ifTags: conditions?.TagConditions, leaseId: conditions?.LeaseId, + ifModifiedSince: conditions?.IfModifiedSince, + ifUnmodifiedSince: conditions?.IfUnmodifiedSince, + ifMatch: conditions?.IfMatch?.ToString(), + ifNoneMatch: conditions?.IfNoneMatch?.ToString(), tags: tags.ToBlobTags(), cancellationToken: cancellationToken); } diff --git a/sdk/storage/Azure.Storage.Blobs/src/Generated/AppendBlobRestClient.cs b/sdk/storage/Azure.Storage.Blobs/src/Generated/AppendBlobRestClient.cs index 12af7d9e1cf6..67a713dab5ae 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Generated/AppendBlobRestClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/Generated/AppendBlobRestClient.cs @@ -29,7 +29,7 @@ internal partial class AppendBlobRestClient /// The handler for diagnostic messaging in the client. /// The HTTP pipeline for sending and receiving REST requests and responses. /// The URL of the service account, container, or blob that is the target of the desired operation. - /// Specifies the version of the operation to use for this request. The default value is "2025-11-05". + /// Specifies the version of the operation to use for this request. The default value is "2026-02-06". /// , , or is null. public AppendBlobRestClient(ClientDiagnostics clientDiagnostics, HttpPipeline pipeline, string url, string version) { diff --git a/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs b/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs index 4274fbecc98d..c9e7244f05cf 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs @@ -30,7 +30,7 @@ internal partial class BlobRestClient /// The handler for diagnostic messaging in the client. /// The HTTP pipeline for sending and receiving REST requests and responses. /// The URL of the service account, container, or blob that is the target of the desired operation. - /// Specifies the version of the operation to use for this request. The default value is "2025-11-05". + /// Specifies the version of the operation to use for this request. The default value is "2026-02-06". /// , , or is null. public BlobRestClient(ClientDiagnostics clientDiagnostics, HttpPipeline pipeline, string url, string version) { @@ -2634,7 +2634,7 @@ public ResponseWithHeaders GetTags(int? timeout = } } - internal HttpMessage CreateSetTagsRequest(int? timeout, string versionId, byte[] transactionalContentMD5, byte[] transactionalContentCrc64, string ifTags, string leaseId, BlobTags tags) + internal HttpMessage CreateSetTagsRequest(int? timeout, string versionId, byte[] transactionalContentMD5, byte[] transactionalContentCrc64, string ifTags, string leaseId, DateTimeOffset? ifModifiedSince, DateTimeOffset? ifUnmodifiedSince, string ifMatch, string ifNoneMatch, BlobTags tags) { var message = _pipeline.CreateMessage(); var request = message.Request; @@ -2664,6 +2664,22 @@ internal HttpMessage CreateSetTagsRequest(int? timeout, string versionId, byte[] { request.Headers.Add("x-ms-lease-id", leaseId); } + if (ifModifiedSince != null) + { + request.Headers.Add("If-Modified-Since", ifModifiedSince.Value, "R"); + } + if (ifUnmodifiedSince != null) + { + request.Headers.Add("If-Unmodified-Since", ifUnmodifiedSince.Value, "R"); + } + if (ifMatch != null) + { + request.Headers.Add("If-Match", ifMatch); + } + if (ifNoneMatch != null) + { + request.Headers.Add("If-None-Match", ifNoneMatch); + } request.Headers.Add("Accept", "application/xml"); if (tags != null) { @@ -2686,11 +2702,15 @@ internal HttpMessage CreateSetTagsRequest(int? timeout, string versionId, byte[] /// Specify the transactional crc64 for the body, to be validated by the service. /// Specify a SQL where clause on blob tags to operate only on blobs with a matching value. /// If specified, the operation only succeeds if the resource's lease is active and matches this ID. + /// Specify this header value to operate only on a blob if it has been modified since the specified date/time. + /// Specify this header value to operate only on a blob if it has not been modified since the specified date/time. + /// Specify an ETag value to operate only on blobs with a matching value. + /// Specify an ETag value to operate only on blobs without a matching value. /// Blob tags. /// The cancellation token to use. - public async Task> SetTagsAsync(int? timeout = null, string versionId = null, byte[] transactionalContentMD5 = null, byte[] transactionalContentCrc64 = null, string ifTags = null, string leaseId = null, BlobTags tags = null, CancellationToken cancellationToken = default) + public async Task> SetTagsAsync(int? timeout = null, string versionId = null, byte[] transactionalContentMD5 = null, byte[] transactionalContentCrc64 = null, string ifTags = null, string leaseId = null, DateTimeOffset? ifModifiedSince = null, DateTimeOffset? ifUnmodifiedSince = null, string ifMatch = null, string ifNoneMatch = null, BlobTags tags = null, CancellationToken cancellationToken = default) { - using var message = CreateSetTagsRequest(timeout, versionId, transactionalContentMD5, transactionalContentCrc64, ifTags, leaseId, tags); + using var message = CreateSetTagsRequest(timeout, versionId, transactionalContentMD5, transactionalContentCrc64, ifTags, leaseId, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, tags); await _pipeline.SendAsync(message, cancellationToken).ConfigureAwait(false); var headers = new BlobSetTagsHeaders(message.Response); switch (message.Response.Status) @@ -2709,11 +2729,15 @@ public async Task> SetTagsAsync(int? tim /// Specify the transactional crc64 for the body, to be validated by the service. /// Specify a SQL where clause on blob tags to operate only on blobs with a matching value. /// If specified, the operation only succeeds if the resource's lease is active and matches this ID. + /// Specify this header value to operate only on a blob if it has been modified since the specified date/time. + /// Specify this header value to operate only on a blob if it has not been modified since the specified date/time. + /// Specify an ETag value to operate only on blobs with a matching value. + /// Specify an ETag value to operate only on blobs without a matching value. /// Blob tags. /// The cancellation token to use. - public ResponseWithHeaders SetTags(int? timeout = null, string versionId = null, byte[] transactionalContentMD5 = null, byte[] transactionalContentCrc64 = null, string ifTags = null, string leaseId = null, BlobTags tags = null, CancellationToken cancellationToken = default) + public ResponseWithHeaders SetTags(int? timeout = null, string versionId = null, byte[] transactionalContentMD5 = null, byte[] transactionalContentCrc64 = null, string ifTags = null, string leaseId = null, DateTimeOffset? ifModifiedSince = null, DateTimeOffset? ifUnmodifiedSince = null, string ifMatch = null, string ifNoneMatch = null, BlobTags tags = null, CancellationToken cancellationToken = default) { - using var message = CreateSetTagsRequest(timeout, versionId, transactionalContentMD5, transactionalContentCrc64, ifTags, leaseId, tags); + using var message = CreateSetTagsRequest(timeout, versionId, transactionalContentMD5, transactionalContentCrc64, ifTags, leaseId, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, tags); _pipeline.Send(message, cancellationToken); var headers = new BlobSetTagsHeaders(message.Response); switch (message.Response.Status) diff --git a/sdk/storage/Azure.Storage.Blobs/src/Generated/BlockBlobRestClient.cs b/sdk/storage/Azure.Storage.Blobs/src/Generated/BlockBlobRestClient.cs index 25265831953b..af938b490fe5 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Generated/BlockBlobRestClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/Generated/BlockBlobRestClient.cs @@ -30,7 +30,7 @@ internal partial class BlockBlobRestClient /// The handler for diagnostic messaging in the client. /// The HTTP pipeline for sending and receiving REST requests and responses. /// The URL of the service account, container, or blob that is the target of the desired operation. - /// Specifies the version of the operation to use for this request. The default value is "2025-11-05". + /// Specifies the version of the operation to use for this request. The default value is "2026-02-06". /// , , or is null. public BlockBlobRestClient(ClientDiagnostics clientDiagnostics, HttpPipeline pipeline, string url, string version) { diff --git a/sdk/storage/Azure.Storage.Blobs/src/Generated/ContainerRestClient.cs b/sdk/storage/Azure.Storage.Blobs/src/Generated/ContainerRestClient.cs index 16a0e0dcfc99..89ac561219f3 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Generated/ContainerRestClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/Generated/ContainerRestClient.cs @@ -31,7 +31,7 @@ internal partial class ContainerRestClient /// The handler for diagnostic messaging in the client. /// The HTTP pipeline for sending and receiving REST requests and responses. /// The URL of the service account, container, or blob that is the target of the desired operation. - /// Specifies the version of the operation to use for this request. The default value is "2025-11-05". + /// Specifies the version of the operation to use for this request. The default value is "2026-02-06". /// , , or is null. public ContainerRestClient(ClientDiagnostics clientDiagnostics, HttpPipeline pipeline, string url, string version) { diff --git a/sdk/storage/Azure.Storage.Blobs/src/Generated/PageBlobRestClient.cs b/sdk/storage/Azure.Storage.Blobs/src/Generated/PageBlobRestClient.cs index eed65eeb59ab..26024e2ad053 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Generated/PageBlobRestClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/Generated/PageBlobRestClient.cs @@ -30,7 +30,7 @@ internal partial class PageBlobRestClient /// The handler for diagnostic messaging in the client. /// The HTTP pipeline for sending and receiving REST requests and responses. /// The URL of the service account, container, or blob that is the target of the desired operation. - /// Specifies the version of the operation to use for this request. The default value is "2025-11-05". + /// Specifies the version of the operation to use for this request. The default value is "2026-02-06". /// , , or is null. public PageBlobRestClient(ClientDiagnostics clientDiagnostics, HttpPipeline pipeline, string url, string version) { diff --git a/sdk/storage/Azure.Storage.Blobs/src/Generated/ServiceRestClient.cs b/sdk/storage/Azure.Storage.Blobs/src/Generated/ServiceRestClient.cs index faf91b38270a..8936d67e02ca 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Generated/ServiceRestClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/Generated/ServiceRestClient.cs @@ -31,7 +31,7 @@ internal partial class ServiceRestClient /// The handler for diagnostic messaging in the client. /// The HTTP pipeline for sending and receiving REST requests and responses. /// The URL of the service account, container, or blob that is the target of the desired operation. - /// Specifies the version of the operation to use for this request. The default value is "2025-11-05". + /// Specifies the version of the operation to use for this request. The default value is "2026-02-06". /// , , or is null. public ServiceRestClient(ClientDiagnostics clientDiagnostics, HttpPipeline pipeline, string url, string version) { diff --git a/sdk/storage/Azure.Storage.Blobs/src/autorest.md b/sdk/storage/Azure.Storage.Blobs/src/autorest.md index 036312cee30d..5e63010a9c09 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/autorest.md +++ b/sdk/storage/Azure.Storage.Blobs/src/autorest.md @@ -4,7 +4,7 @@ Run `dotnet build /t:GenerateCode` to generate code. ``` yaml input-file: - - https://raw.githubusercontent.com/Azure/azure-rest-api-specs/596d8d2a8c1c50bd6ebe60036143f4c4787fc816/specification/storage/data-plane/Microsoft.BlobStorage/stable/2025-11-05/blob.json + - Q:\src\azure-rest-api-specs\specification\storage\data-plane\Microsoft.BlobStorage\stable\2026-02-06\blob.json generation1-convenience-client: true # https://github.com/Azure/autorest/issues/4075 skip-semantics-validation: true diff --git a/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs b/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs index 6580dbff4cd3..1844c6c065ed 100644 --- a/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs +++ b/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs @@ -7044,6 +7044,60 @@ await TestHelper.AssertExpectedExceptionAsync( e => Assert.AreEqual(BlobErrorCode.LeaseNotPresentWithBlobOperation.ToString(), e.ErrorCode)); } + [RecordedTest] + [ServiceVersion(Min = BlobClientOptions.ServiceVersion.V2026_02_06)] + public async Task SetTags_AccessConditions() + { + var garbageLeaseId = GetGarbageLeaseId(); + foreach (AccessConditionParameters parameters in AccessConditions_Data) + { + // Arrange + await using DisposingContainer test = await GetTestContainerAsync(); + BlobBaseClient blob = await GetNewBlobClient(test.Container); + + parameters.Match = await SetupBlobMatchCondition(blob, parameters.Match); + parameters.LeaseId = await SetupBlobLeaseCondition(blob, parameters.LeaseId, garbageLeaseId); + BlobRequestConditions accessConditions = BuildAccessConditions( + parameters: parameters, + lease: true); + + Dictionary tags = BuildTags(); + + // Act + Response response = await blob.SetTagsAsync( + tags: tags, + conditions: accessConditions); + + // Assert + Assert.IsNotNull(response.Headers.RequestId); + } + } + + [RecordedTest] + //[ServiceVersion(Min = BlobClientOptions.ServiceVersion.V2026_02_06)] + public async Task SetTags_AccessConditionsFail() + { + var garbageLeaseId = GetGarbageLeaseId(); + foreach (AccessConditionParameters parameters in GetAccessConditionsFail_Data(garbageLeaseId)) + { + // Arrange + await using DisposingContainer test = await GetTestContainerAsync(); + BlobBaseClient blob = await GetNewBlobClient(test.Container); + + parameters.NoneMatch = await SetupBlobMatchCondition(blob, parameters.NoneMatch); + BlobRequestConditions accessConditions = BuildAccessConditions(parameters); + + Dictionary tags = BuildTags(); + + // Act + await TestHelper.AssertExpectedExceptionAsync( + blob.SetTagsAsync( + tags: tags, + conditions: accessConditions), + e => { }); + } + } + #region GenerateSasTests [RecordedTest] public void CanGenerateSas_ClientConstructors() From 673705eb04cea09364517a5d45e12f975ad9c0b7 Mon Sep 17 00:00:00 2001 From: Sean McCullough Date: Wed, 28 May 2025 13:30:54 -0500 Subject: [PATCH 2/6] updatE --- sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs b/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs index 1844c6c065ed..2e39c4aced5b 100644 --- a/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs +++ b/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs @@ -7074,7 +7074,7 @@ public async Task SetTags_AccessConditions() } [RecordedTest] - //[ServiceVersion(Min = BlobClientOptions.ServiceVersion.V2026_02_06)] + [ServiceVersion(Min = BlobClientOptions.ServiceVersion.V2026_02_06)] public async Task SetTags_AccessConditionsFail() { var garbageLeaseId = GetGarbageLeaseId(); From 31eff52f7d76dc55700111225775c92d466c8893 Mon Sep 17 00:00:00 2001 From: Sean McCullough Date: Tue, 17 Jun 2025 11:08:48 -0500 Subject: [PATCH 3/6] Added conditional headers to Get Blob Tags --- .../Azure.Storage.Blobs/src/BlobBaseClient.cs | 9 ----- .../src/Generated/BlobRestClient.cs | 34 ++++++++++++++++--- .../tests/BlobBaseClientTests.cs | 31 +++++++++++++++-- 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs b/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs index cb7eb05e429c..49528af2257c 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs @@ -6060,15 +6060,6 @@ private async Task> GetTagsInternal( DiagnosticScope scope = ClientConfiguration.ClientDiagnostics.CreateScope($"{nameof(BlobBaseClient)}.{nameof(GetTags)}"); - conditions.ValidateConditionsNotPresent( - invalidConditions: - BlobRequestConditionProperty.IfModifiedSince - | BlobRequestConditionProperty.IfUnmodifiedSince - | BlobRequestConditionProperty.IfMatch - | BlobRequestConditionProperty.IfNoneMatch, - operationName: nameof(BlobBaseClient.GetTags), - parameterName: nameof(conditions)); - try { scope.Start(); diff --git a/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs b/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs index c9e7244f05cf..f6570d0a8301 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs @@ -2542,7 +2542,7 @@ public ResponseWithHeaders Query(string snapshot = nul } } - internal HttpMessage CreateGetTagsRequest(int? timeout, string snapshot, string versionId, string ifTags, string leaseId) + internal HttpMessage CreateGetTagsRequest(int? timeout, string snapshot, string versionId, string ifTags, string leaseId, DateTimeOffset? ifModifiedSince, DateTimeOffset? ifUnmodifiedSince, string ifMatch, string ifNoneMatch) { var message = _pipeline.CreateMessage(); var request = message.Request; @@ -2572,6 +2572,22 @@ internal HttpMessage CreateGetTagsRequest(int? timeout, string snapshot, string { request.Headers.Add("x-ms-lease-id", leaseId); } + if (ifModifiedSince != null) + { + request.Headers.Add("If-Modified-Since", ifModifiedSince.Value, "R"); + } + if (ifUnmodifiedSince != null) + { + request.Headers.Add("If-Unmodified-Since", ifUnmodifiedSince.Value, "R"); + } + if (ifMatch != null) + { + request.Headers.Add("If-Match", ifMatch); + } + if (ifNoneMatch != null) + { + request.Headers.Add("If-None-Match", ifNoneMatch); + } request.Headers.Add("Accept", "application/xml"); return message; } @@ -2582,10 +2598,14 @@ internal HttpMessage CreateGetTagsRequest(int? timeout, string snapshot, string /// The version id parameter is an opaque DateTime value that, when present, specifies the version of the blob to operate on. It's for service version 2019-10-10 and newer. /// Specify a SQL where clause on blob tags to operate only on blobs with a matching value. /// If specified, the operation only succeeds if the resource's lease is active and matches this ID. + /// Specify this header value to operate only on a blob if it has been modified since the specified date/time. + /// Specify this header value to operate only on a blob if it has not been modified since the specified date/time. + /// Specify an ETag value to operate only on blobs with a matching value. + /// Specify an ETag value to operate only on blobs without a matching value. /// The cancellation token to use. - public async Task> GetTagsAsync(int? timeout = null, string snapshot = null, string versionId = null, string ifTags = null, string leaseId = null, CancellationToken cancellationToken = default) + public async Task> GetTagsAsync(int? timeout = null, string snapshot = null, string versionId = null, string ifTags = null, string leaseId = null, DateTimeOffset? ifModifiedSince = null, DateTimeOffset? ifUnmodifiedSince = null, string ifMatch = null, string ifNoneMatch = null, CancellationToken cancellationToken = default) { - using var message = CreateGetTagsRequest(timeout, snapshot, versionId, ifTags, leaseId); + using var message = CreateGetTagsRequest(timeout, snapshot, versionId, ifTags, leaseId, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch); await _pipeline.SendAsync(message, cancellationToken).ConfigureAwait(false); var headers = new BlobGetTagsHeaders(message.Response); switch (message.Response.Status) @@ -2611,10 +2631,14 @@ public async Task> GetTagsAsyn /// The version id parameter is an opaque DateTime value that, when present, specifies the version of the blob to operate on. It's for service version 2019-10-10 and newer. /// Specify a SQL where clause on blob tags to operate only on blobs with a matching value. /// If specified, the operation only succeeds if the resource's lease is active and matches this ID. + /// Specify this header value to operate only on a blob if it has been modified since the specified date/time. + /// Specify this header value to operate only on a blob if it has not been modified since the specified date/time. + /// Specify an ETag value to operate only on blobs with a matching value. + /// Specify an ETag value to operate only on blobs without a matching value. /// The cancellation token to use. - public ResponseWithHeaders GetTags(int? timeout = null, string snapshot = null, string versionId = null, string ifTags = null, string leaseId = null, CancellationToken cancellationToken = default) + public ResponseWithHeaders GetTags(int? timeout = null, string snapshot = null, string versionId = null, string ifTags = null, string leaseId = null, DateTimeOffset? ifModifiedSince = null, DateTimeOffset? ifUnmodifiedSince = null, string ifMatch = null, string ifNoneMatch = null, CancellationToken cancellationToken = default) { - using var message = CreateGetTagsRequest(timeout, snapshot, versionId, ifTags, leaseId); + using var message = CreateGetTagsRequest(timeout, snapshot, versionId, ifTags, leaseId, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch); _pipeline.Send(message, cancellationToken); var headers = new BlobGetTagsHeaders(message.Response); switch (message.Response.Status) diff --git a/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs b/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs index 2e39c4aced5b..78b06e62df52 100644 --- a/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs +++ b/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs @@ -6903,6 +6903,28 @@ await TestHelper.AssertExpectedExceptionAsync( e => Assert.AreEqual(BlobErrorCode.BlobNotFound.ToString(), e.ErrorCode)); } + [RecordedTest] + [ServiceVersion(Min = BlobClientOptions.ServiceVersion.V2026_02_06)] + public async Task GetTags_AccessConditionsFail() + { + var garbageLeaseId = GetGarbageLeaseId(); + foreach (AccessConditionParameters parameters in GetAccessConditionsFail_Data(garbageLeaseId)) + { + // Arrange + await using DisposingContainer test = await GetTestContainerAsync(); + BlobBaseClient blob = await GetNewBlobClient(test.Container); + + parameters.NoneMatch = await SetupBlobMatchCondition(blob, parameters.NoneMatch); + BlobRequestConditions accessConditions = BuildAccessConditions(parameters); + + // Act + await TestHelper.AssertExpectedExceptionAsync( + blob.GetTagsAsync( + conditions: accessConditions), + e => { }); + } + } + [RecordedTest] [ServiceVersion(Min = BlobClientOptions.ServiceVersion.V2019_12_12)] public async Task GetTagsAsync_Error() @@ -7046,7 +7068,7 @@ await TestHelper.AssertExpectedExceptionAsync( [RecordedTest] [ServiceVersion(Min = BlobClientOptions.ServiceVersion.V2026_02_06)] - public async Task SetTags_AccessConditions() + public async Task GetSetTags_AccessConditions() { var garbageLeaseId = GetGarbageLeaseId(); foreach (AccessConditionParameters parameters in AccessConditions_Data) @@ -7064,12 +7086,15 @@ public async Task SetTags_AccessConditions() Dictionary tags = BuildTags(); // Act - Response response = await blob.SetTagsAsync( + await blob.SetTagsAsync( tags: tags, conditions: accessConditions); + Response response = await blob.GetTagsAsync( + conditions: accessConditions); + // Assert - Assert.IsNotNull(response.Headers.RequestId); + AssertDictionaryEquality(tags, response.Value.Tags); } } From d88705aebd22ffc17e65145510114b3e71f54867 Mon Sep 17 00:00:00 2001 From: Sean McCullough Date: Mon, 23 Jun 2025 13:22:20 -0500 Subject: [PATCH 4/6] Removed exception tests for conditional headers --- .../tests/BlobBaseClientTests.cs | 83 ------------------- 1 file changed, 83 deletions(-) diff --git a/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs b/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs index 78b06e62df52..3ca7325bd67a 100644 --- a/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs +++ b/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs @@ -6507,46 +6507,6 @@ public async Task GetSetTagsAsync() AssertDictionaryEquality(tags, getTagsResponse.Value.Tags); } - [RecordedTest] - [TestCase(nameof(BlobRequestConditions.IfModifiedSince))] - [TestCase(nameof(BlobRequestConditions.IfUnmodifiedSince))] - [TestCase(nameof(BlobRequestConditions.IfMatch))] - [TestCase(nameof(BlobRequestConditions.IfNoneMatch))] - public async Task GetTagsAsync_InvalidRequestConditions(string invalidCondition) - { - // Arrange - Uri uri = new Uri("https://www.doesntmatter.com"); - BlobBaseClient blobBaseClient = new BlobBaseClient(uri, GetOptions()); - - BlobRequestConditions conditions = new BlobRequestConditions(); - - switch (invalidCondition) - { - case nameof(BlobRequestConditions.IfModifiedSince): - conditions.IfModifiedSince = new DateTimeOffset(); - break; - case nameof(BlobRequestConditions.IfUnmodifiedSince): - conditions.IfUnmodifiedSince = new DateTimeOffset(); - break; - case nameof(BlobRequestConditions.IfMatch): - conditions.IfMatch = new ETag(); - break; - case nameof(BlobRequestConditions.IfNoneMatch): - conditions.IfNoneMatch = new ETag(); - break; - } - - // Act - await TestHelper.AssertExpectedExceptionAsync( - blobBaseClient.GetTagsAsync( - conditions: conditions), - e => - { - Assert.IsTrue(e.Message.Contains($"GetTags does not support the {invalidCondition} condition(s).")); - Assert.IsTrue(e.Message.Contains("conditions")); - }); - } - [RecordedTest] [ServiceVersion(Min = BlobClientOptions.ServiceVersion.V2019_12_12)] public async Task GetSetTagsAsync_BlobTagSas() @@ -6939,49 +6899,6 @@ await TestHelper.AssertExpectedExceptionAsync( e => Assert.AreEqual(BlobErrorCode.BlobNotFound.ToString(), e.ErrorCode)); } - [RecordedTest] - [TestCase(nameof(BlobRequestConditions.IfModifiedSince))] - [TestCase(nameof(BlobRequestConditions.IfUnmodifiedSince))] - [TestCase(nameof(BlobRequestConditions.IfMatch))] - [TestCase(nameof(BlobRequestConditions.IfNoneMatch))] - public async Task SetTagsAsync_InvalidRequestConditions(string invalidCondition) - { - // Arrange - Uri uri = new Uri("https://www.doesntmatter.com"); - BlobBaseClient blobBaseClient = new BlobBaseClient(uri, GetOptions()); - - BlobRequestConditions conditions = new BlobRequestConditions(); - - switch (invalidCondition) - { - case nameof(BlobRequestConditions.IfModifiedSince): - conditions.IfModifiedSince = new DateTimeOffset(); - break; - case nameof(BlobRequestConditions.IfUnmodifiedSince): - conditions.IfUnmodifiedSince = new DateTimeOffset(); - break; - case nameof(BlobRequestConditions.IfMatch): - conditions.IfMatch = new ETag(); - break; - case nameof(BlobRequestConditions.IfNoneMatch): - conditions.IfNoneMatch = new ETag(); - break; - } - - Dictionary tags = BuildTags(); - - // Act - await TestHelper.AssertExpectedExceptionAsync( - blobBaseClient.SetTagsAsync( - tags, - conditions: conditions), - e => - { - Assert.IsTrue(e.Message.Contains($"SetTags does not support the {invalidCondition} condition(s).")); - Assert.IsTrue(e.Message.Contains("conditions")); - }); - } - [RecordedTest] [ServiceVersion(Min = BlobClientOptions.ServiceVersion.V2019_12_12)] public async Task SetTagsAsync_Error() From 6e24478958668d91d3888660aa9612af291c4f46 Mon Sep 17 00:00:00 2001 From: Sean McCullough Date: Tue, 24 Jun 2025 10:57:01 -0500 Subject: [PATCH 5/6] fix --- .../src/Generated/BlobRestClient.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs b/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs index f6570d0a8301..720caef66b4a 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs @@ -2574,19 +2574,19 @@ internal HttpMessage CreateGetTagsRequest(int? timeout, string snapshot, string } if (ifModifiedSince != null) { - request.Headers.Add("If-Modified-Since", ifModifiedSince.Value, "R"); + request.Headers.Add("x-ms-blob-if-modified-since", ifModifiedSince.Value, "R"); } if (ifUnmodifiedSince != null) { - request.Headers.Add("If-Unmodified-Since", ifUnmodifiedSince.Value, "R"); + request.Headers.Add("x-ms-blob-if-unmodified-since", ifUnmodifiedSince.Value, "R"); } if (ifMatch != null) { - request.Headers.Add("If-Match", ifMatch); + request.Headers.Add("x-ms-blob-if-match", ifMatch); } if (ifNoneMatch != null) { - request.Headers.Add("If-None-Match", ifNoneMatch); + request.Headers.Add("x-ms-blob-if-none-match", ifNoneMatch); } request.Headers.Add("Accept", "application/xml"); return message; @@ -2690,19 +2690,19 @@ internal HttpMessage CreateSetTagsRequest(int? timeout, string versionId, byte[] } if (ifModifiedSince != null) { - request.Headers.Add("If-Modified-Since", ifModifiedSince.Value, "R"); + request.Headers.Add("x-ms-blob-if-modified-since", ifModifiedSince.Value, "R"); } if (ifUnmodifiedSince != null) { - request.Headers.Add("If-Unmodified-Since", ifUnmodifiedSince.Value, "R"); + request.Headers.Add("x-ms-blob-if-unmodified-since", ifUnmodifiedSince.Value, "R"); } if (ifMatch != null) { - request.Headers.Add("If-Match", ifMatch); + request.Headers.Add("x-ms-blob-if-match", ifMatch); } if (ifNoneMatch != null) { - request.Headers.Add("If-None-Match", ifNoneMatch); + request.Headers.Add("x-ms-blob-if-none-match", ifNoneMatch); } request.Headers.Add("Accept", "application/xml"); if (tags != null) From bcc636552ee26972be2564d519746a89db104283 Mon Sep 17 00:00:00 2001 From: Sean McCullough Date: Tue, 24 Jun 2025 11:11:28 -0500 Subject: [PATCH 6/6] Recorded tests --- sdk/storage/Azure.Storage.Blobs/assets.json | 2 +- sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/sdk/storage/Azure.Storage.Blobs/assets.json b/sdk/storage/Azure.Storage.Blobs/assets.json index 4def2c6cc80f..f81bf8cfc709 100644 --- a/sdk/storage/Azure.Storage.Blobs/assets.json +++ b/sdk/storage/Azure.Storage.Blobs/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "net", "TagPrefix": "net/storage/Azure.Storage.Blobs", - "Tag": "net/storage/Azure.Storage.Blobs_274b449b34" + "Tag": "net/storage/Azure.Storage.Blobs_c053e68e29" } diff --git a/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs b/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs index 49528af2257c..c096f0c57e02 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs @@ -6070,6 +6070,10 @@ private async Task> GetTagsInternal( response = await BlobRestClient.GetTagsAsync( ifTags: conditions?.TagConditions, leaseId: conditions?.LeaseId, + ifModifiedSince: conditions?.IfModifiedSince, + ifUnmodifiedSince: conditions?.IfUnmodifiedSince, + ifMatch: conditions?.IfMatch?.ToString(), + ifNoneMatch: conditions?.IfNoneMatch?.ToString(), cancellationToken: cancellationToken) .ConfigureAwait(false); } @@ -6078,6 +6082,10 @@ private async Task> GetTagsInternal( response = BlobRestClient.GetTags( ifTags: conditions?.TagConditions, leaseId: conditions?.LeaseId, + ifModifiedSince: conditions?.IfModifiedSince, + ifUnmodifiedSince: conditions?.IfUnmodifiedSince, + ifMatch: conditions?.IfMatch?.ToString(), + ifNoneMatch: conditions?.IfNoneMatch?.ToString(), cancellationToken: cancellationToken); }