From a8541817af0e877822e5ec6ca0d5e32235336325 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Fri, 7 Nov 2025 13:00:09 -0800 Subject: [PATCH 1/8] AudiencePolicy --- .../ConfigurationClientBuilder.java | 4 ++ .../implementation/AudiencePolicy.java | 56 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/implementation/AudiencePolicy.java diff --git a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java index 7e209d811dd7..51894ae9f4c1 100644 --- a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java +++ b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java @@ -42,6 +42,7 @@ import com.azure.data.appconfiguration.implementation.AzureAppConfigurationImpl; import com.azure.data.appconfiguration.implementation.ConfigurationClientCredentials; import com.azure.data.appconfiguration.implementation.ConfigurationCredentialsPolicy; +import com.azure.data.appconfiguration.implementation.AudiencePolicy; import com.azure.data.appconfiguration.implementation.QueryParamPolicy; import com.azure.data.appconfiguration.implementation.SyncTokenPolicy; import com.azure.data.appconfiguration.models.ConfigurationAudience; @@ -267,6 +268,9 @@ private HttpPipeline createDefaultHttpPipeline(SyncTokenPolicy syncTokenPolicy, // Add query parameter reordering policy policies.add(new QueryParamPolicy()); + // Add policy to deal with Audience Exception in new clouds + policies.add(new AudiencePolicy(audience)); + policies.addAll(perCallPolicies); HttpPolicyProviders.addBeforeRetryPolicies(policies); diff --git a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/implementation/AudiencePolicy.java b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/implementation/AudiencePolicy.java new file mode 100644 index 000000000000..f8be6695fe3c --- /dev/null +++ b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/implementation/AudiencePolicy.java @@ -0,0 +1,56 @@ +package com.azure.data.appconfiguration.implementation; + +import com.azure.core.exception.HttpResponseException; +import com.azure.core.http.HttpPipelineCallContext; +import com.azure.core.http.HttpPipelineNextPolicy; +import com.azure.core.http.HttpPipelineNextSyncPolicy; +import com.azure.core.http.HttpResponse; +import com.azure.core.http.policy.HttpPipelinePolicy; +import com.azure.data.appconfiguration.models.ConfigurationAudience; + +import reactor.core.publisher.Mono; + +public class AudiencePolicy implements HttpPipelinePolicy { + + private static final String NO_AUDIENCE + = "No audience was provided, one should be configured to connect to this cloud."; + + private static final String INCORRECT_AUDIENCE + = "The incorrect audience was provided. Please update to connect to this cloud."; + + private final ConfigurationAudience audience; + + public AudiencePolicy(ConfigurationAudience audience) { + this.audience = audience; + } + + @Override + public Mono process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) { + try { + return next.clone().process(); + } catch (HttpResponseException ex) { + throw handleAudienceException(ex); + } + } + + @Override + public HttpResponse processSync(HttpPipelineCallContext context, HttpPipelineNextSyncPolicy next) { + try { + return next.clone().processSync(); + } catch (HttpResponseException ex) { + throw handleAudienceException(ex); + } + } + + private HttpResponseException handleAudienceException(HttpResponseException ex) { + if (ex.getMessage().contains("AADSTS500011")) { + String message = INCORRECT_AUDIENCE; + if (audience == null) { + message = NO_AUDIENCE; + } + + return new HttpResponseException(message, ex.getResponse(), ex); + } + return ex; + } +} From 9a82d809214dbe45ed885f745a8e06126db966b3 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Mon, 10 Nov 2025 14:07:50 -0800 Subject: [PATCH 2/8] Updated usage + tests --- .../azure-data-appconfiguration/CHANGELOG.md | 2 + .../implementation/AudiencePolicy.java | 44 ++-- .../implementation/AudiencePolicyTest.java | 222 ++++++++++++++++++ 3 files changed, 252 insertions(+), 16 deletions(-) create mode 100644 sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/implementation/AudiencePolicyTest.java diff --git a/sdk/appconfiguration/azure-data-appconfiguration/CHANGELOG.md b/sdk/appconfiguration/azure-data-appconfiguration/CHANGELOG.md index 67c451261084..fa8e7494f0e0 100644 --- a/sdk/appconfiguration/azure-data-appconfiguration/CHANGELOG.md +++ b/sdk/appconfiguration/azure-data-appconfiguration/CHANGELOG.md @@ -3,7 +3,9 @@ ## 1.9.0-beta.1 (Unreleased) ### Features Added + - Added a pipeline policy to handle query parameters to make sure the keys are always in lower case and in alphabetical order. +- Added audience policy to provide more meaningful error messages for Azure Active Directory authentication failures. The policy detects AAD audience-related errors and provides clear guidance on audience configuration issues. ### Breaking Changes diff --git a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/implementation/AudiencePolicy.java b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/implementation/AudiencePolicy.java index f8be6695fe3c..7e554ea6bd69 100644 --- a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/implementation/AudiencePolicy.java +++ b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/implementation/AudiencePolicy.java @@ -1,3 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. package com.azure.data.appconfiguration.implementation; import com.azure.core.exception.HttpResponseException; @@ -10,45 +12,55 @@ import reactor.core.publisher.Mono; +/** + * HTTP pipeline policy that handles Azure Active Directory audience-related authentication errors. + * This policy intercepts HTTP responses and provides more meaningful error messages when + * audience configuration issues occur during authentication. + */ public class AudiencePolicy implements HttpPipelinePolicy { - private static final String NO_AUDIENCE - = "No audience was provided, one should be configured to connect to this cloud."; + private static final String NO_AUDIENCE_ERROR_MESSAGE + = "No audience was provided. An audience must be configured to connect to this cloud."; - private static final String INCORRECT_AUDIENCE - = "The incorrect audience was provided. Please update to connect to this cloud."; + private static final String INCORRECT_AUDIENCE_ERROR_MESSAGE + = "An incorrect audience was provided. Please update the audience to connect to this cloud."; + + private static final String AAD_AUDIENCE_ERROR_CODE = "AADSTS500011"; private final ConfigurationAudience audience; + /** + * Creates a new instance of AudiencePolicy. + * + * @param audience The configuration audience to use for validation. May be null. + */ public AudiencePolicy(ConfigurationAudience audience) { this.audience = audience; } @Override public Mono process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) { - try { - return next.clone().process(); - } catch (HttpResponseException ex) { - throw handleAudienceException(ex); - } + return next.process().onErrorMap(HttpResponseException.class, this::handleAudienceException); } @Override public HttpResponse processSync(HttpPipelineCallContext context, HttpPipelineNextSyncPolicy next) { try { - return next.clone().processSync(); + return next.processSync(); } catch (HttpResponseException ex) { throw handleAudienceException(ex); } } + /** + * Handles audience-related authentication exceptions by providing more meaningful error messages. + * + * @param ex The original HttpResponseException + * @return A new HttpResponseException with improved error message if audience-related, otherwise the original exception + */ private HttpResponseException handleAudienceException(HttpResponseException ex) { - if (ex.getMessage().contains("AADSTS500011")) { - String message = INCORRECT_AUDIENCE; - if (audience == null) { - message = NO_AUDIENCE; - } - + if (ex.getMessage() != null && ex.getMessage().contains(AAD_AUDIENCE_ERROR_CODE)) { + String message = audience == null ? NO_AUDIENCE_ERROR_MESSAGE : INCORRECT_AUDIENCE_ERROR_MESSAGE; return new HttpResponseException(message, ex.getResponse(), ex); } return ex; diff --git a/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/implementation/AudiencePolicyTest.java b/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/implementation/AudiencePolicyTest.java new file mode 100644 index 000000000000..4361a0515031 --- /dev/null +++ b/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/implementation/AudiencePolicyTest.java @@ -0,0 +1,222 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.data.appconfiguration.implementation; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import com.azure.core.exception.HttpResponseException; +import com.azure.core.http.HttpMethod; +import com.azure.core.http.HttpPipeline; +import com.azure.core.http.HttpPipelineBuilder; +import com.azure.core.http.HttpRequest; +import com.azure.core.http.HttpResponse; +import com.azure.core.http.policy.HttpPipelinePolicy; +import com.azure.core.test.SyncAsyncExtension; +import com.azure.core.test.annotation.SyncAsyncTest; +import com.azure.core.test.http.MockHttpResponse; +import com.azure.core.test.http.NoOpHttpClient; +import com.azure.core.util.Context; +import com.azure.data.appconfiguration.models.ConfigurationAudience; + +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +/** + * Unit tests for AudiencePolicy + */ +public class AudiencePolicyTest { + private static final String LOCAL_HOST = "http://localhost"; + private static final String AAD_AUDIENCE_ERROR_CODE = "AADSTS500011"; + private static final String NO_AUDIENCE_ERROR_MESSAGE + = "No audience was provided. An audience must be configured to connect to this cloud."; + private static final String INCORRECT_AUDIENCE_ERROR_MESSAGE + = "An incorrect audience was provided. Please update the audience to connect to this cloud."; + + @SyncAsyncTest + public void processWithoutException() { + AudiencePolicy audiencePolicy = new AudiencePolicy(ConfigurationAudience.AZURE_PUBLIC_CLOUD); + + HttpPipelinePolicy testPolicy = (context, next) -> { + return next.process(); + }; + + final HttpPipeline pipeline = new HttpPipelineBuilder().httpClient(new NoOpHttpClient() { + @Override + public Mono send(HttpRequest request) { + return Mono.just(new MockHttpResponse(request, 200)); + } + }).policies(audiencePolicy, testPolicy).build(); + + SyncAsyncExtension.execute(() -> sendRequestSync(pipeline), () -> sendRequest(pipeline)); + } + + @Test + public void processWithNonAudienceException() { + AudiencePolicy audiencePolicy = new AudiencePolicy(ConfigurationAudience.AZURE_PUBLIC_CLOUD); + + HttpPipelinePolicy exceptionPolicy = (context, next) -> { + HttpResponseException ex + = new HttpResponseException("Some other error", new MockHttpResponse(context.getHttpRequest(), 401)); + return Mono.error(ex); + }; + + final HttpPipeline pipeline = new HttpPipelineBuilder().httpClient(new NoOpHttpClient()) + .policies(audiencePolicy, exceptionPolicy) + .build(); + + StepVerifier.create(sendRequest(pipeline)) + .expectErrorMatches(throwable -> throwable instanceof HttpResponseException + && throwable.getMessage().equals("Some other error")) + .verify(); + + // Test sync version + HttpResponseException thrown = assertThrows(HttpResponseException.class, () -> sendRequestSync(pipeline)); + assertEquals("Some other error", thrown.getMessage()); + } + + @Test + public void processWithAudienceExceptionAndNullAudience() { + AudiencePolicy audiencePolicy = new AudiencePolicy(null); + + HttpPipelinePolicy exceptionPolicy = (context, next) -> { + HttpResponseException ex = new HttpResponseException("Error " + AAD_AUDIENCE_ERROR_CODE + " occurred", + new MockHttpResponse(context.getHttpRequest(), 401)); + return Mono.error(ex); + }; + + final HttpPipeline pipeline = new HttpPipelineBuilder().httpClient(new NoOpHttpClient()) + .policies(audiencePolicy, exceptionPolicy) + .build(); + + StepVerifier.create(sendRequest(pipeline)) + .expectErrorMatches(throwable -> throwable instanceof HttpResponseException + && throwable.getMessage().equals(NO_AUDIENCE_ERROR_MESSAGE)) + .verify(); + + // Test sync version + HttpResponseException thrown = assertThrows(HttpResponseException.class, () -> sendRequestSync(pipeline)); + assertEquals(NO_AUDIENCE_ERROR_MESSAGE, thrown.getMessage()); + } + + @Test + public void processWithAudienceExceptionAndConfiguredAudience() { + AudiencePolicy audiencePolicy = new AudiencePolicy(ConfigurationAudience.AZURE_PUBLIC_CLOUD); + + HttpPipelinePolicy exceptionPolicy = (context, next) -> { + HttpResponseException ex = new HttpResponseException("Error " + AAD_AUDIENCE_ERROR_CODE + " occurred", + new MockHttpResponse(context.getHttpRequest(), 401)); + return Mono.error(ex); + }; + + final HttpPipeline pipeline = new HttpPipelineBuilder().httpClient(new NoOpHttpClient()) + .policies(audiencePolicy, exceptionPolicy) + .build(); + + StepVerifier.create(sendRequest(pipeline)) + .expectErrorMatches(throwable -> throwable instanceof HttpResponseException + && throwable.getMessage().equals(INCORRECT_AUDIENCE_ERROR_MESSAGE)) + .verify(); + + // Test sync version + HttpResponseException thrown = assertThrows(HttpResponseException.class, () -> sendRequestSync(pipeline)); + assertEquals(INCORRECT_AUDIENCE_ERROR_MESSAGE, thrown.getMessage()); + } + + @Test + public void handleAudienceExceptionWithNullMessage() { + AudiencePolicy audiencePolicy = new AudiencePolicy(ConfigurationAudience.AZURE_PUBLIC_CLOUD); + + HttpResponseException originalException + = new HttpResponseException(null, new MockHttpResponse(new HttpRequest(HttpMethod.GET, LOCAL_HOST), 401)); + + // Use reflection to access the private method for testing + try { + java.lang.reflect.Method method + = AudiencePolicy.class.getDeclaredMethod("handleAudienceException", HttpResponseException.class); + method.setAccessible(true); + + HttpResponseException result = (HttpResponseException) method.invoke(audiencePolicy, originalException); + assertSame(originalException, result, "Should return original exception when message is null"); + } catch (Exception e) { + throw new RuntimeException("Failed to test handleAudienceException with null message", e); + } + } + + @Test + public void handleAudienceExceptionWithoutErrorCode() { + AudiencePolicy audiencePolicy = new AudiencePolicy(ConfigurationAudience.AZURE_PUBLIC_CLOUD); + + HttpResponseException originalException = new HttpResponseException("Some other error", + new MockHttpResponse(new HttpRequest(HttpMethod.GET, LOCAL_HOST), 401)); + + // Use reflection to access the private method for testing + try { + java.lang.reflect.Method method + = AudiencePolicy.class.getDeclaredMethod("handleAudienceException", HttpResponseException.class); + method.setAccessible(true); + + HttpResponseException result = (HttpResponseException) method.invoke(audiencePolicy, originalException); + assertSame(originalException, result, "Should return original exception when error code is not found"); + } catch (Exception e) { + throw new RuntimeException("Failed to test handleAudienceException without error code", e); + } + } + + @Test + public void handleAudienceExceptionWithErrorCodeNullAudience() { + AudiencePolicy audiencePolicy = new AudiencePolicy(null); + + HttpResponseException originalException + = new HttpResponseException("Error " + AAD_AUDIENCE_ERROR_CODE + " occurred", + new MockHttpResponse(new HttpRequest(HttpMethod.GET, LOCAL_HOST), 401)); + + // Use reflection to access the private method for testing + try { + java.lang.reflect.Method method + = AudiencePolicy.class.getDeclaredMethod("handleAudienceException", HttpResponseException.class); + method.setAccessible(true); + + HttpResponseException result = (HttpResponseException) method.invoke(audiencePolicy, originalException); + assertEquals(NO_AUDIENCE_ERROR_MESSAGE, result.getMessage()); + assertSame(originalException.getResponse(), result.getResponse()); + } catch (Exception e) { + throw new RuntimeException("Failed to test handleAudienceException with error code and null audience", e); + } + } + + @Test + public void handleAudienceExceptionWithErrorCodeConfiguredAudience() { + AudiencePolicy audiencePolicy = new AudiencePolicy(ConfigurationAudience.AZURE_PUBLIC_CLOUD); + + HttpResponseException originalException + = new HttpResponseException("Error " + AAD_AUDIENCE_ERROR_CODE + " occurred", + new MockHttpResponse(new HttpRequest(HttpMethod.GET, LOCAL_HOST), 401)); + + // Use reflection to access the private method for testing + try { + java.lang.reflect.Method method + = AudiencePolicy.class.getDeclaredMethod("handleAudienceException", HttpResponseException.class); + method.setAccessible(true); + + HttpResponseException result = (HttpResponseException) method.invoke(audiencePolicy, originalException); + assertEquals(INCORRECT_AUDIENCE_ERROR_MESSAGE, result.getMessage()); + assertSame(originalException.getResponse(), result.getResponse()); + } catch (Exception e) { + throw new RuntimeException("Failed to test handleAudienceException with error code and configured audience", + e); + } + } + + private Mono sendRequest(HttpPipeline pipeline) { + return pipeline.send(new HttpRequest(HttpMethod.GET, LOCAL_HOST)); + } + + private HttpResponse sendRequestSync(HttpPipeline pipeline) { + return pipeline.sendSync(new HttpRequest(HttpMethod.GET, LOCAL_HOST), Context.NONE); + } +} From b7048c2e2958614af83de9e6a31eebc1c22bb2ff Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Mon, 10 Nov 2025 14:13:21 -0800 Subject: [PATCH 3/8] fixing code owners --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e7b89527a93e..42714ab7c1c3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -107,7 +107,7 @@ # ServiceOwners: @miaojiang # PRLabel: %App Configuration -/sdk/appconfiguration/ @alzimmermsft @Azure/azure-java-sdk +/sdk/appconfiguration/ @mrm9084 @rossgrambo @avanigupta @alzimmermsft @Azure/azure-java-sdk # ServiceLabel: %App Configuration # AzureSdkOwners: @mrm9084 From 458d5afaaafc6f1b0e57ad4f80e6e9bfffa56421 Mon Sep 17 00:00:00 2001 From: Matthew Metcalf Date: Mon, 10 Nov 2025 14:20:37 -0800 Subject: [PATCH 4/8] Update sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../azure/data/appconfiguration/ConfigurationClientBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java index 51894ae9f4c1..4a4b07025b88 100644 --- a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java +++ b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java @@ -268,7 +268,7 @@ private HttpPipeline createDefaultHttpPipeline(SyncTokenPolicy syncTokenPolicy, // Add query parameter reordering policy policies.add(new QueryParamPolicy()); - // Add policy to deal with Audience Exception in new clouds + // Add policy to provide better error messages for AAD audience authentication failures policies.add(new AudiencePolicy(audience)); policies.addAll(perCallPolicies); From c7f2d12b2e896dc651713ffe348d5905629faed1 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Tue, 11 Nov 2025 09:27:41 -0800 Subject: [PATCH 5/8] linting changes --- .../ConfigurationClientBuilder.java | 2 +- .../AudiencePolicy.java | 18 +++++++++++++----- .../AudiencePolicyTest.java | 15 +++++++++++---- 3 files changed, 25 insertions(+), 10 deletions(-) rename sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/{implementation => policies}/AudiencePolicy.java (71%) rename sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/{implementation => policies}/AudiencePolicyTest.java (92%) diff --git a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java index 4a4b07025b88..feac82898a40 100644 --- a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java +++ b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java @@ -42,7 +42,7 @@ import com.azure.data.appconfiguration.implementation.AzureAppConfigurationImpl; import com.azure.data.appconfiguration.implementation.ConfigurationClientCredentials; import com.azure.data.appconfiguration.implementation.ConfigurationCredentialsPolicy; -import com.azure.data.appconfiguration.implementation.AudiencePolicy; +import com.azure.data.appconfiguration.policies.AudiencePolicy; import com.azure.data.appconfiguration.implementation.QueryParamPolicy; import com.azure.data.appconfiguration.implementation.SyncTokenPolicy; import com.azure.data.appconfiguration.models.ConfigurationAudience; diff --git a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/implementation/AudiencePolicy.java b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/policies/AudiencePolicy.java similarity index 71% rename from sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/implementation/AudiencePolicy.java rename to sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/policies/AudiencePolicy.java index 7e554ea6bd69..def000874c1a 100644 --- a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/implementation/AudiencePolicy.java +++ b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/policies/AudiencePolicy.java @@ -1,6 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.data.appconfiguration.implementation; +package com.azure.data.appconfiguration.policies; import com.azure.core.exception.HttpResponseException; import com.azure.core.http.HttpPipelineCallContext; @@ -8,6 +8,7 @@ import com.azure.core.http.HttpPipelineNextSyncPolicy; import com.azure.core.http.HttpResponse; import com.azure.core.http.policy.HttpPipelinePolicy; +import com.azure.core.util.logging.ClientLogger; import com.azure.data.appconfiguration.models.ConfigurationAudience; import reactor.core.publisher.Mono; @@ -18,12 +19,19 @@ * audience configuration issues occur during authentication. */ public class AudiencePolicy implements HttpPipelinePolicy { + private static final ClientLogger LOGGER = new ClientLogger(AudiencePolicy.class); private static final String NO_AUDIENCE_ERROR_MESSAGE - = "No audience was provided. An audience must be configured to connect to this cloud."; + = "Unable to authenticate to Azure App Configuration. No authentication token audience was provided. " + + "Please set an Audience in your ConfigurationClientBuilder for the target cloud. " + + "For details on how to configure the authentication token audience visit " + + "https://aka.ms/appconfig/client-token-audience."; private static final String INCORRECT_AUDIENCE_ERROR_MESSAGE - = "An incorrect audience was provided. Please update the audience to connect to this cloud."; + = "Unable to authenticate to Azure App Configuration. An incorrect token audience was provided. " + + "Please set the Audience in your ConfigurationClientBuilder to the appropriate audience for this cloud. " + + "For details on how to configure the authentication token audience visit " + + "https://aka.ms/appconfig/client-token-audience."; private static final String AAD_AUDIENCE_ERROR_CODE = "AADSTS500011"; @@ -48,7 +56,7 @@ public HttpResponse processSync(HttpPipelineCallContext context, HttpPipelineNex try { return next.processSync(); } catch (HttpResponseException ex) { - throw handleAudienceException(ex); + throw LOGGER.logExceptionAsError(handleAudienceException(ex)); } } @@ -65,4 +73,4 @@ private HttpResponseException handleAudienceException(HttpResponseException ex) } return ex; } -} +} \ No newline at end of file diff --git a/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/implementation/AudiencePolicyTest.java b/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/policies/AudiencePolicyTest.java similarity index 92% rename from sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/implementation/AudiencePolicyTest.java rename to sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/policies/AudiencePolicyTest.java index 4361a0515031..e6c265e9a2d7 100644 --- a/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/implementation/AudiencePolicyTest.java +++ b/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/policies/AudiencePolicyTest.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.data.appconfiguration.implementation; +package com.azure.data.appconfiguration.policies; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; @@ -33,9 +33,16 @@ public class AudiencePolicyTest { private static final String LOCAL_HOST = "http://localhost"; private static final String AAD_AUDIENCE_ERROR_CODE = "AADSTS500011"; private static final String NO_AUDIENCE_ERROR_MESSAGE - = "No audience was provided. An audience must be configured to connect to this cloud."; + = "Unable to authenticate to Azure App Configuration. No authentication token audience was provided. " + + "Please set an Audience in your ConfigurationClientBuilder for the target cloud. " + + "For details on how to configure the authentication token audience visit " + + "https://aka.ms/appconfig/client-token-audience."; + private static final String INCORRECT_AUDIENCE_ERROR_MESSAGE - = "An incorrect audience was provided. Please update the audience to connect to this cloud."; + = "Unable to authenticate to Azure App Configuration. An incorrect token audience was provided. " + + "Please set the Audience in your ConfigurationClientBuilder to the appropriate audience for this cloud. " + + "For details on how to configure the authentication token audience visit " + + "https://aka.ms/appconfig/client-token-audience."; @SyncAsyncTest public void processWithoutException() { @@ -219,4 +226,4 @@ private Mono sendRequest(HttpPipeline pipeline) { private HttpResponse sendRequestSync(HttpPipeline pipeline) { return pipeline.sendSync(new HttpRequest(HttpMethod.GET, LOCAL_HOST), Context.NONE); } -} +} \ No newline at end of file From dc1995691d886e0eab00cea23503402bce27e393 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Tue, 11 Nov 2025 09:31:43 -0800 Subject: [PATCH 6/8] Revert "linting changes" This reverts commit c7f2d12b2e896dc651713ffe348d5905629faed1. --- .../ConfigurationClientBuilder.java | 2 +- .../AudiencePolicy.java | 18 +++++------------- .../AudiencePolicyTest.java | 15 ++++----------- 3 files changed, 10 insertions(+), 25 deletions(-) rename sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/{policies => implementation}/AudiencePolicy.java (71%) rename sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/{policies => implementation}/AudiencePolicyTest.java (92%) diff --git a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java index feac82898a40..4a4b07025b88 100644 --- a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java +++ b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java @@ -42,7 +42,7 @@ import com.azure.data.appconfiguration.implementation.AzureAppConfigurationImpl; import com.azure.data.appconfiguration.implementation.ConfigurationClientCredentials; import com.azure.data.appconfiguration.implementation.ConfigurationCredentialsPolicy; -import com.azure.data.appconfiguration.policies.AudiencePolicy; +import com.azure.data.appconfiguration.implementation.AudiencePolicy; import com.azure.data.appconfiguration.implementation.QueryParamPolicy; import com.azure.data.appconfiguration.implementation.SyncTokenPolicy; import com.azure.data.appconfiguration.models.ConfigurationAudience; diff --git a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/policies/AudiencePolicy.java b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/implementation/AudiencePolicy.java similarity index 71% rename from sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/policies/AudiencePolicy.java rename to sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/implementation/AudiencePolicy.java index def000874c1a..7e554ea6bd69 100644 --- a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/policies/AudiencePolicy.java +++ b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/implementation/AudiencePolicy.java @@ -1,6 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.data.appconfiguration.policies; +package com.azure.data.appconfiguration.implementation; import com.azure.core.exception.HttpResponseException; import com.azure.core.http.HttpPipelineCallContext; @@ -8,7 +8,6 @@ import com.azure.core.http.HttpPipelineNextSyncPolicy; import com.azure.core.http.HttpResponse; import com.azure.core.http.policy.HttpPipelinePolicy; -import com.azure.core.util.logging.ClientLogger; import com.azure.data.appconfiguration.models.ConfigurationAudience; import reactor.core.publisher.Mono; @@ -19,19 +18,12 @@ * audience configuration issues occur during authentication. */ public class AudiencePolicy implements HttpPipelinePolicy { - private static final ClientLogger LOGGER = new ClientLogger(AudiencePolicy.class); private static final String NO_AUDIENCE_ERROR_MESSAGE - = "Unable to authenticate to Azure App Configuration. No authentication token audience was provided. " - + "Please set an Audience in your ConfigurationClientBuilder for the target cloud. " - + "For details on how to configure the authentication token audience visit " - + "https://aka.ms/appconfig/client-token-audience."; + = "No audience was provided. An audience must be configured to connect to this cloud."; private static final String INCORRECT_AUDIENCE_ERROR_MESSAGE - = "Unable to authenticate to Azure App Configuration. An incorrect token audience was provided. " - + "Please set the Audience in your ConfigurationClientBuilder to the appropriate audience for this cloud. " - + "For details on how to configure the authentication token audience visit " - + "https://aka.ms/appconfig/client-token-audience."; + = "An incorrect audience was provided. Please update the audience to connect to this cloud."; private static final String AAD_AUDIENCE_ERROR_CODE = "AADSTS500011"; @@ -56,7 +48,7 @@ public HttpResponse processSync(HttpPipelineCallContext context, HttpPipelineNex try { return next.processSync(); } catch (HttpResponseException ex) { - throw LOGGER.logExceptionAsError(handleAudienceException(ex)); + throw handleAudienceException(ex); } } @@ -73,4 +65,4 @@ private HttpResponseException handleAudienceException(HttpResponseException ex) } return ex; } -} \ No newline at end of file +} diff --git a/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/policies/AudiencePolicyTest.java b/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/implementation/AudiencePolicyTest.java similarity index 92% rename from sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/policies/AudiencePolicyTest.java rename to sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/implementation/AudiencePolicyTest.java index e6c265e9a2d7..4361a0515031 100644 --- a/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/policies/AudiencePolicyTest.java +++ b/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/implementation/AudiencePolicyTest.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.data.appconfiguration.policies; +package com.azure.data.appconfiguration.implementation; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; @@ -33,16 +33,9 @@ public class AudiencePolicyTest { private static final String LOCAL_HOST = "http://localhost"; private static final String AAD_AUDIENCE_ERROR_CODE = "AADSTS500011"; private static final String NO_AUDIENCE_ERROR_MESSAGE - = "Unable to authenticate to Azure App Configuration. No authentication token audience was provided. " - + "Please set an Audience in your ConfigurationClientBuilder for the target cloud. " - + "For details on how to configure the authentication token audience visit " - + "https://aka.ms/appconfig/client-token-audience."; - + = "No audience was provided. An audience must be configured to connect to this cloud."; private static final String INCORRECT_AUDIENCE_ERROR_MESSAGE - = "Unable to authenticate to Azure App Configuration. An incorrect token audience was provided. " - + "Please set the Audience in your ConfigurationClientBuilder to the appropriate audience for this cloud. " - + "For details on how to configure the authentication token audience visit " - + "https://aka.ms/appconfig/client-token-audience."; + = "An incorrect audience was provided. Please update the audience to connect to this cloud."; @SyncAsyncTest public void processWithoutException() { @@ -226,4 +219,4 @@ private Mono sendRequest(HttpPipeline pipeline) { private HttpResponse sendRequestSync(HttpPipeline pipeline) { return pipeline.sendSync(new HttpRequest(HttpMethod.GET, LOCAL_HOST), Context.NONE); } -} \ No newline at end of file +} From 22c4e96545e15b888c9e340e94f061eb63895281 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Tue, 11 Nov 2025 09:56:48 -0800 Subject: [PATCH 7/8] text update + suppression --- .../checkstyle-suppressions.xml | 1 + .../implementation/AudiencePolicy.java | 10 ++++++++-- .../implementation/AudiencePolicyTest.java | 11 +++++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/sdk/appconfiguration/azure-data-appconfiguration/checkstyle-suppressions.xml b/sdk/appconfiguration/azure-data-appconfiguration/checkstyle-suppressions.xml index aa64ccaf2075..2c39234812f6 100644 --- a/sdk/appconfiguration/azure-data-appconfiguration/checkstyle-suppressions.xml +++ b/sdk/appconfiguration/azure-data-appconfiguration/checkstyle-suppressions.xml @@ -4,6 +4,7 @@ + diff --git a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/implementation/AudiencePolicy.java b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/implementation/AudiencePolicy.java index 7e554ea6bd69..cfde6ab443db 100644 --- a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/implementation/AudiencePolicy.java +++ b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/implementation/AudiencePolicy.java @@ -20,10 +20,16 @@ public class AudiencePolicy implements HttpPipelinePolicy { private static final String NO_AUDIENCE_ERROR_MESSAGE - = "No audience was provided. An audience must be configured to connect to this cloud."; + = "Unable to authenticate to Azure App Configuration. No authentication token audience was provided. " + + "Please set an Audience in your ConfigurationClientBuilder for the target cloud. " + + "For details on how to configure the authentication token audience visit " + + "https://aka.ms/appconfig/client-token-audience."; private static final String INCORRECT_AUDIENCE_ERROR_MESSAGE - = "An incorrect audience was provided. Please update the audience to connect to this cloud."; + = "Unable to authenticate to Azure App Configuration. An incorrect token audience was provided. " + + "Please set the Audience in your ConfigurationClientBuilder to the appropriate audience for this cloud. " + + "For details on how to configure the authentication token audience visit " + + "https://aka.ms/appconfig/client-token-audience."; private static final String AAD_AUDIENCE_ERROR_CODE = "AADSTS500011"; diff --git a/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/implementation/AudiencePolicyTest.java b/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/implementation/AudiencePolicyTest.java index 4361a0515031..394d0909163e 100644 --- a/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/implementation/AudiencePolicyTest.java +++ b/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/implementation/AudiencePolicyTest.java @@ -33,9 +33,16 @@ public class AudiencePolicyTest { private static final String LOCAL_HOST = "http://localhost"; private static final String AAD_AUDIENCE_ERROR_CODE = "AADSTS500011"; private static final String NO_AUDIENCE_ERROR_MESSAGE - = "No audience was provided. An audience must be configured to connect to this cloud."; + = "Unable to authenticate to Azure App Configuration. No authentication token audience was provided. " + + "Please set an Audience in your ConfigurationClientBuilder for the target cloud. " + + "For details on how to configure the authentication token audience visit " + + "https://aka.ms/appconfig/client-token-audience."; + private static final String INCORRECT_AUDIENCE_ERROR_MESSAGE - = "An incorrect audience was provided. Please update the audience to connect to this cloud."; + = "Unable to authenticate to Azure App Configuration. An incorrect token audience was provided. " + + "Please set the Audience in your ConfigurationClientBuilder to the appropriate audience for this cloud. " + + "For details on how to configure the authentication token audience visit " + + "https://aka.ms/appconfig/client-token-audience."; @SyncAsyncTest public void processWithoutException() { From 4fba9a546de4c605d11473602c6abdf7c06ae754 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Wed, 12 Nov 2025 11:18:20 -0800 Subject: [PATCH 8/8] Trying to fix tests --- .../ConfigurationClientBuilder.java | 29 +++--- .../implementation/AudiencePolicyTest.java | 94 ++++--------------- 2 files changed, 30 insertions(+), 93 deletions(-) diff --git a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java index 4a4b07025b88..26a0b21942b4 100644 --- a/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java +++ b/sdk/appconfiguration/azure-data-appconfiguration/src/main/java/com/azure/data/appconfiguration/ConfigurationClientBuilder.java @@ -3,6 +3,14 @@ package com.azure.data.appconfiguration; +import java.net.MalformedURLException; +import java.net.URL; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + import com.azure.core.annotation.ServiceClientBuilder; import com.azure.core.client.traits.ConfigurationTrait; import com.azure.core.client.traits.ConnectionStringTrait; @@ -33,31 +41,22 @@ import com.azure.core.util.ClientOptions; import com.azure.core.util.Configuration; import com.azure.core.util.CoreUtils; +import static com.azure.core.util.CoreUtils.getApplicationId; import com.azure.core.util.HttpClientOptions; import com.azure.core.util.TracingOptions; import com.azure.core.util.builder.ClientBuilderUtil; import com.azure.core.util.logging.ClientLogger; import com.azure.core.util.tracing.Tracer; import com.azure.core.util.tracing.TracerProvider; +import com.azure.data.appconfiguration.implementation.AudiencePolicy; import com.azure.data.appconfiguration.implementation.AzureAppConfigurationImpl; +import static com.azure.data.appconfiguration.implementation.ClientConstants.APP_CONFIG_TRACING_NAMESPACE_VALUE; import com.azure.data.appconfiguration.implementation.ConfigurationClientCredentials; import com.azure.data.appconfiguration.implementation.ConfigurationCredentialsPolicy; -import com.azure.data.appconfiguration.implementation.AudiencePolicy; import com.azure.data.appconfiguration.implementation.QueryParamPolicy; import com.azure.data.appconfiguration.implementation.SyncTokenPolicy; import com.azure.data.appconfiguration.models.ConfigurationAudience; -import java.net.MalformedURLException; -import java.net.URL; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import static com.azure.core.util.CoreUtils.getApplicationId; -import static com.azure.data.appconfiguration.implementation.ClientConstants.APP_CONFIG_TRACING_NAMESPACE_VALUE; - /** * This class provides a fluent builder API to help aid the configuration and instantiation of * {@link ConfigurationClient ConfigurationClients} and {@link ConfigurationAsyncClient ConfigurationAsyncClients}, call @@ -268,9 +267,6 @@ private HttpPipeline createDefaultHttpPipeline(SyncTokenPolicy syncTokenPolicy, // Add query parameter reordering policy policies.add(new QueryParamPolicy()); - // Add policy to provide better error messages for AAD audience authentication failures - policies.add(new AudiencePolicy(audience)); - policies.addAll(perCallPolicies); HttpPolicyProviders.addBeforeRetryPolicies(policies); @@ -293,6 +289,9 @@ private HttpPipeline createDefaultHttpPipeline(SyncTokenPolicy syncTokenPolicy, policies.add(syncTokenPolicy); policies.addAll(perRetryPolicies); + // Add policy to provide better error messages for AAD audience authentication failures + policies.add(new AudiencePolicy(audience)); + List httpHeaderList = new ArrayList<>(); localClientOptions.getHeaders() .forEach(header -> httpHeaderList.add(new HttpHeader(header.getName(), header.getValue()))); diff --git a/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/implementation/AudiencePolicyTest.java b/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/implementation/AudiencePolicyTest.java index 394d0909163e..78ef12332b26 100644 --- a/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/implementation/AudiencePolicyTest.java +++ b/sdk/appconfiguration/azure-data-appconfiguration/src/test/java/com/azure/data/appconfiguration/implementation/AudiencePolicyTest.java @@ -4,7 +4,6 @@ package com.azure.data.appconfiguration.implementation; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; @@ -135,88 +134,27 @@ public void processWithAudienceExceptionAndConfiguredAudience() { } @Test - public void handleAudienceExceptionWithNullMessage() { + public void processWithNullMessageException() { AudiencePolicy audiencePolicy = new AudiencePolicy(ConfigurationAudience.AZURE_PUBLIC_CLOUD); - HttpResponseException originalException - = new HttpResponseException(null, new MockHttpResponse(new HttpRequest(HttpMethod.GET, LOCAL_HOST), 401)); - - // Use reflection to access the private method for testing - try { - java.lang.reflect.Method method - = AudiencePolicy.class.getDeclaredMethod("handleAudienceException", HttpResponseException.class); - method.setAccessible(true); - - HttpResponseException result = (HttpResponseException) method.invoke(audiencePolicy, originalException); - assertSame(originalException, result, "Should return original exception when message is null"); - } catch (Exception e) { - throw new RuntimeException("Failed to test handleAudienceException with null message", e); - } - } - - @Test - public void handleAudienceExceptionWithoutErrorCode() { - AudiencePolicy audiencePolicy = new AudiencePolicy(ConfigurationAudience.AZURE_PUBLIC_CLOUD); - - HttpResponseException originalException = new HttpResponseException("Some other error", - new MockHttpResponse(new HttpRequest(HttpMethod.GET, LOCAL_HOST), 401)); - - // Use reflection to access the private method for testing - try { - java.lang.reflect.Method method - = AudiencePolicy.class.getDeclaredMethod("handleAudienceException", HttpResponseException.class); - method.setAccessible(true); - - HttpResponseException result = (HttpResponseException) method.invoke(audiencePolicy, originalException); - assertSame(originalException, result, "Should return original exception when error code is not found"); - } catch (Exception e) { - throw new RuntimeException("Failed to test handleAudienceException without error code", e); - } - } - - @Test - public void handleAudienceExceptionWithErrorCodeNullAudience() { - AudiencePolicy audiencePolicy = new AudiencePolicy(null); + HttpPipelinePolicy exceptionPolicy = (context, next) -> { + HttpResponseException ex + = new HttpResponseException(null, new MockHttpResponse(context.getHttpRequest(), 401)); + return Mono.error(ex); + }; - HttpResponseException originalException - = new HttpResponseException("Error " + AAD_AUDIENCE_ERROR_CODE + " occurred", - new MockHttpResponse(new HttpRequest(HttpMethod.GET, LOCAL_HOST), 401)); - - // Use reflection to access the private method for testing - try { - java.lang.reflect.Method method - = AudiencePolicy.class.getDeclaredMethod("handleAudienceException", HttpResponseException.class); - method.setAccessible(true); - - HttpResponseException result = (HttpResponseException) method.invoke(audiencePolicy, originalException); - assertEquals(NO_AUDIENCE_ERROR_MESSAGE, result.getMessage()); - assertSame(originalException.getResponse(), result.getResponse()); - } catch (Exception e) { - throw new RuntimeException("Failed to test handleAudienceException with error code and null audience", e); - } - } + final HttpPipeline pipeline = new HttpPipelineBuilder().httpClient(new NoOpHttpClient()) + .policies(audiencePolicy, exceptionPolicy) + .build(); - @Test - public void handleAudienceExceptionWithErrorCodeConfiguredAudience() { - AudiencePolicy audiencePolicy = new AudiencePolicy(ConfigurationAudience.AZURE_PUBLIC_CLOUD); + StepVerifier.create(sendRequest(pipeline)) + .expectErrorMatches( + throwable -> throwable instanceof HttpResponseException && throwable.getMessage() == null) + .verify(); - HttpResponseException originalException - = new HttpResponseException("Error " + AAD_AUDIENCE_ERROR_CODE + " occurred", - new MockHttpResponse(new HttpRequest(HttpMethod.GET, LOCAL_HOST), 401)); - - // Use reflection to access the private method for testing - try { - java.lang.reflect.Method method - = AudiencePolicy.class.getDeclaredMethod("handleAudienceException", HttpResponseException.class); - method.setAccessible(true); - - HttpResponseException result = (HttpResponseException) method.invoke(audiencePolicy, originalException); - assertEquals(INCORRECT_AUDIENCE_ERROR_MESSAGE, result.getMessage()); - assertSame(originalException.getResponse(), result.getResponse()); - } catch (Exception e) { - throw new RuntimeException("Failed to test handleAudienceException with error code and configured audience", - e); - } + // Test sync version + HttpResponseException thrown = assertThrows(HttpResponseException.class, () -> sendRequestSync(pipeline)); + assertEquals(null, thrown.getMessage(), "Should return original exception when message is null"); } private Mono sendRequest(HttpPipeline pipeline) {