diff --git a/.github/workflows/api-level-lint.yml b/.github/workflows/api-level-lint.yml index d84787fdd..0761df0a8 100644 --- a/.github/workflows/api-level-lint.yml +++ b/.github/workflows/api-level-lint.yml @@ -18,7 +18,7 @@ jobs: java-version: 21 cache: gradle - name: Setup Android SDK - uses: android-actions/setup-android@v3.2.1 + uses: android-actions/setup-android@v3.2.2 - name: Add execution right to the script run: chmod +x gradlew working-directory: ./android diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 466b7195f..395791d45 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.3.1" + ".": "3.4.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index e2b60af6d..3ba81fb0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.4.0](https://github.com/microsoftgraph/msgraph-sdk-java-core/compare/v3.3.1...v3.4.0) (2024-11-19) + + +### Features + +* add GraphClientFactory method using TokenCredential ([b75c471](https://github.com/microsoftgraph/msgraph-sdk-java-core/commit/b75c471d17436712d7e6cc8e32e606af200670e7)) +* Support overriding default interceptors via request options ([667cae6](https://github.com/microsoftgraph/msgraph-sdk-java-core/commit/667cae662ec46e938d73817e79d58e84d97171e6)) + + +### Bug Fixes + +* issue where custom interceptors would fail to override default interceptors ([adf470a](https://github.com/microsoftgraph/msgraph-sdk-java-core/commit/adf470aa76e12920effbc9d8feee5721ecb68101)) + ## [3.3.1](https://github.com/microsoftgraph/msgraph-sdk-java-core/compare/v3.3.0...v3.3.1) (2024-10-23) diff --git a/README.md b/README.md index 54296fc8c..09866781e 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ repositories { dependencies { // Include the sdk as a dependency // x-release-please-start-version - implementation 'com.microsoft.graph:microsoft-graph-core:3.3.1' + implementation 'com.microsoft.graph:microsoft-graph-core:3.4.0' // x-release-please-end // This dependency is only needed if you are using the TokenCredentialAuthProvider implementation 'com.azure:azure-identity:1.11.0' @@ -40,7 +40,7 @@ Add the dependency in `dependencies` in pom.xml com.microsoft.graph microsoft-graph-core - 3.3.1 + 3.4.0 com.azure diff --git a/android/build.gradle b/android/build.gradle index 2c9ea95c2..9821c0c24 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -5,8 +5,8 @@ buildscript { } dependencies { - classpath "com.gradle:gradle-enterprise-gradle-plugin:3.18.1" - classpath "com.android.tools.build:gradle:8.7.1" + classpath "com.gradle:gradle-enterprise-gradle-plugin:3.18.2" + classpath "com.android.tools.build:gradle:8.7.2" classpath "com.github.ben-manes:gradle-versions-plugin:0.51.0" } } @@ -43,19 +43,17 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } + lint { + baseline = file("lint-baseline.xml") + } + lintOptions { textOutput "stdout" checkAllWarnings true warningsAsErrors true - disable "UnusedResources" // Unused will be removed on release - disable "IconExpectedSize" // Using the material icons provided from Google - disable "GoogleAppIndexingApiWarning" // We might want to index our app later - disable "InvalidPackage" // Butterknife, Okio and Realm - disable "ResourceType" // Annotation binding - disable "GradleDependency" - disable "NewerVersionAvailable" - disable "DuplicatePlatformClasses" // xpp3 added by azure-identity + lintConfig file("lint.xml") } + sourceSets { main { java.srcDirs = ['../src/main/java'] diff --git a/android/lint-baseline.xml b/android/lint-baseline.xml new file mode 100644 index 000000000..11149ca54 --- /dev/null +++ b/android/lint-baseline.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/android/lint.xml b/android/lint.xml new file mode 100644 index 000000000..0e7a8f716 --- /dev/null +++ b/android/lint.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/build.gradle b/build.gradle index d8f84e580..e156b5e6c 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'maven-publish' id 'signing' id 'jacoco' - id 'com.github.spotbugs' version '6.0.25' + id 'com.github.spotbugs' version '6.0.26' id "org.sonarqube" version "5.1.0.4882" id 'io.github.gradle-nexus.publish-plugin' version '2.0.0' } diff --git a/gradle.properties b/gradle.properties index ee942d982..9996fb853 100644 --- a/gradle.properties +++ b/gradle.properties @@ -27,10 +27,10 @@ mavenArtifactId = microsoft-graph-core mavenMajorVersion = 3 # x-release-please-end # x-release-please-start-minor -mavenMinorVersion = 3 +mavenMinorVersion = 4 # x-release-please-end # x-release-please-start-patch -mavenPatchVersion = 1 +mavenPatchVersion = 0 # x-release-please-end mavenArtifactSuffix = diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index c569a3f67..03159bff0 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -4,21 +4,21 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-params:5.11.3' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.11.3' testImplementation 'org.mockito:mockito-core:5.14.2' - testImplementation 'io.opentelemetry:opentelemetry-api:1.43.0' - testImplementation 'io.opentelemetry:opentelemetry-context:1.43.0' + testImplementation 'io.opentelemetry:opentelemetry-api:1.44.1' + testImplementation 'io.opentelemetry:opentelemetry-context:1.44.1' testImplementation 'io.github.std-uritemplate:std-uritemplate:1.0.6' implementation 'com.google.code.gson:gson:2.11.0' implementation 'jakarta.annotation:jakarta.annotation-api:2.1.1' api 'com.squareup.okhttp3:okhttp:4.12.0' - api 'com.azure:azure-core:1.53.0' + api 'com.azure:azure-core:1.54.1' - api 'com.microsoft.kiota:microsoft-kiota-abstractions:1.7.0' - api 'com.microsoft.kiota:microsoft-kiota-authentication-azure:1.7.0' - implementation 'com.microsoft.kiota:microsoft-kiota-http-okHttp:1.7.0' - implementation 'com.microsoft.kiota:microsoft-kiota-serialization-json:1.7.0' - implementation 'com.microsoft.kiota:microsoft-kiota-serialization-text:1.7.0' - implementation 'com.microsoft.kiota:microsoft-kiota-serialization-form:1.7.0' - implementation 'com.microsoft.kiota:microsoft-kiota-serialization-multipart:1.7.0' + api 'com.microsoft.kiota:microsoft-kiota-abstractions:1.8.0' + api 'com.microsoft.kiota:microsoft-kiota-authentication-azure:1.8.0' + implementation 'com.microsoft.kiota:microsoft-kiota-http-okHttp:1.8.0' + implementation 'com.microsoft.kiota:microsoft-kiota-serialization-json:1.8.0' + implementation 'com.microsoft.kiota:microsoft-kiota-serialization-text:1.8.0' + implementation 'com.microsoft.kiota:microsoft-kiota-serialization-form:1.8.0' + implementation 'com.microsoft.kiota:microsoft-kiota-serialization-multipart:1.8.0' } diff --git a/pom.xml b/pom.xml index a199192ca..a99338bed 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.microsoft.graph microsoft-graph-core - 3.3.1 + 3.4.0 pom @@ -32,7 +32,7 @@ com.azure azure-core - 1.53.0 + 1.54.1 org.junit.jupiter diff --git a/spotBugsExcludeFilter.xml b/spotBugsExcludeFilter.xml index ffb0ea6f2..619fb292d 100644 --- a/spotBugsExcludeFilter.xml +++ b/spotBugsExcludeFilter.xml @@ -64,6 +64,7 @@ xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubu + @@ -111,4 +112,4 @@ xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubu - \ No newline at end of file + diff --git a/src/main/java/com/microsoft/graph/core/CoreConstants.java b/src/main/java/com/microsoft/graph/core/CoreConstants.java index 9025da1e8..2aaf8a7c0 100644 --- a/src/main/java/com/microsoft/graph/core/CoreConstants.java +++ b/src/main/java/com/microsoft/graph/core/CoreConstants.java @@ -16,10 +16,10 @@ private static class VersionValues { private static final int MAJOR = 3; // x-release-please-end // x-release-please-start-minor - private static final int MINOR = 3; + private static final int MINOR = 4; // x-release-please-end // x-release-please-start-patch - private static final int PATCH = 1; + private static final int PATCH = 0; // x-release-please-end } diff --git a/src/main/java/com/microsoft/graph/core/requests/GraphClientFactory.java b/src/main/java/com/microsoft/graph/core/requests/GraphClientFactory.java index baa38bd76..d20c9afae 100644 --- a/src/main/java/com/microsoft/graph/core/requests/GraphClientFactory.java +++ b/src/main/java/com/microsoft/graph/core/requests/GraphClientFactory.java @@ -1,8 +1,11 @@ package com.microsoft.graph.core.requests; +import com.azure.core.credential.TokenCredential; import com.microsoft.graph.core.CoreConstants; +import com.microsoft.graph.core.authentication.AzureIdentityAccessTokenProvider; import com.microsoft.graph.core.requests.middleware.GraphTelemetryHandler; import com.microsoft.graph.core.requests.options.GraphClientOption; +import com.microsoft.kiota.RequestOption; import com.microsoft.kiota.authentication.BaseBearerTokenAuthenticationProvider; import com.microsoft.kiota.http.KiotaClientFactory; import com.microsoft.kiota.http.middleware.AuthorizationHandler; @@ -36,7 +39,7 @@ public static OkHttpClient.Builder create() { * @return an OkHttpClient Builder instance. */ @Nonnull - public static OkHttpClient.Builder create(@Nonnull Interceptor... interceptors) { + public static OkHttpClient.Builder create(@Nonnull final Interceptor... interceptors) { return create(new GraphClientOption(), interceptors); } @@ -46,10 +49,31 @@ public static OkHttpClient.Builder create(@Nonnull Interceptor... interceptors) * @return an OkHttpClient Builder instance. */ @Nonnull - public static OkHttpClient.Builder create(@Nonnull List interceptors) { + public static OkHttpClient.Builder create(@Nonnull final List interceptors) { return create(new GraphClientOption(), interceptors.toArray(new Interceptor[0])); } + /** + * OkHttpClient Builder for Graph with authentication middleware that uses the specified TokenCredential. + * @param tokenCredential the TokenCredential to use for authentication. + * @return an OkHttpClient Builder instance. + */ + @Nonnull + public static OkHttpClient.Builder create(@Nonnull final TokenCredential tokenCredential) { + return create(tokenCredential, new RequestOption[0]); + } + + /** + * OkHttpClient Builder for Graph with authentication middleware that uses the specified TokenCredential and RequestOptions to override default graph interceptors. + * @param tokenCredential the TokenCredential to use for authentication. + * @param requestOptions custom request options to override default graph interceptors + * @return an OkHttpClient Builder instance. + */ + @Nonnull + public static OkHttpClient.Builder create(@Nonnull final TokenCredential tokenCredential, @Nonnull final RequestOption[] requestOptions) { + return create(new BaseBearerTokenAuthenticationProvider(new AzureIdentityAccessTokenProvider(tokenCredential)), requestOptions); + } + /** * OkHttpClient Builder for Graph with specified AuthenticationProvider. * Adds an AuthorizationHandler to the OkHttpClient Builder. @@ -57,9 +81,22 @@ public static OkHttpClient.Builder create(@Nonnull List interceptor * @return an OkHttpClient Builder instance. */ @Nonnull - public static OkHttpClient.Builder create(@Nonnull BaseBearerTokenAuthenticationProvider authenticationProvider) { + public static OkHttpClient.Builder create(@Nonnull final BaseBearerTokenAuthenticationProvider authenticationProvider) { + return create(authenticationProvider, new RequestOption[0]); + } + + /** + * OkHttpClient Builder for Graph with specified AuthenticationProvider and RequestOptions to override default graph interceptors + * @param authenticationProvider the AuthenticationProvider to use for requests. + * @param requestOptions custom request options to override default graph interceptors + * @return an OkHttpClient Builder instance. + */ + @Nonnull + public static OkHttpClient.Builder create(@Nonnull final BaseBearerTokenAuthenticationProvider authenticationProvider, @Nonnull final RequestOption[] requestOptions) { final GraphClientOption graphClientOption = new GraphClientOption(); - final Interceptor[] interceptors = createDefaultGraphInterceptors(graphClientOption); + final List requestOptionsList = new ArrayList<>(Arrays.asList(requestOptions)); + requestOptionsList.add(graphClientOption); + final Interceptor[] interceptors = createDefaultGraphInterceptors(requestOptionsList.toArray(new RequestOption[0])); final ArrayList interceptorList = new ArrayList<>(Arrays.asList(interceptors)); interceptorList.add(new AuthorizationHandler(authenticationProvider)); graphClientOption.featureTracker.setFeatureUsage(FeatureFlag.AUTH_HANDLER_FLAG); @@ -74,18 +111,12 @@ public static OkHttpClient.Builder create(@Nonnull BaseBearerTokenAuthentication * @return an OkHttpClient Builder instance. */ @Nonnull - public static OkHttpClient.Builder create(@Nonnull GraphClientOption graphClientOption, @Nonnull Interceptor... interceptors) { - final OkHttpClient.Builder builder = create(graphClientOption); - //Skip adding interceptor if that class of interceptor already exist. - final List appliedInterceptors = new ArrayList<>(); - for(Interceptor interceptor: builder.interceptors()) { - appliedInterceptors.add(interceptor.getClass().toString()); - } - for (Interceptor interceptor:interceptors){ - if(appliedInterceptors.contains(interceptor.getClass().toString())) { - continue; - } - builder.addInterceptor(interceptor); + public static OkHttpClient.Builder create(@Nonnull final GraphClientOption graphClientOption, @Nonnull final Interceptor... interceptors) { + final OkHttpClient.Builder builder = KiotaClientFactory.create(interceptors); + final List customInterceptors = builder.interceptors(); + final boolean telemetryHandlerExists = customInterceptors.stream().anyMatch(x -> x instanceof GraphTelemetryHandler); + if (!telemetryHandlerExists) { + customInterceptors.add(new GraphTelemetryHandler(graphClientOption)); } return builder; } @@ -97,7 +128,7 @@ public static OkHttpClient.Builder create(@Nonnull GraphClientOption graphClient * @return an OkHttpClient Builder instance. */ @Nonnull - public static OkHttpClient.Builder create(@Nonnull GraphClientOption graphClientOption, @Nonnull List interceptors) { + public static OkHttpClient.Builder create(@Nonnull final GraphClientOption graphClientOption, @Nonnull final List interceptors) { return create(graphClientOption, interceptors.toArray(new Interceptor[0])); } @@ -108,10 +139,21 @@ public static OkHttpClient.Builder create(@Nonnull GraphClientOption graphClient * @return an OkHttpClient Builder instance. */ @Nonnull - public static OkHttpClient.Builder create(@Nullable GraphClientOption graphClientOption) { - GraphClientOption options = graphClientOption != null ? graphClientOption : new GraphClientOption(); - return KiotaClientFactory.create(createDefaultGraphInterceptors(options)); + public static OkHttpClient.Builder create(@Nullable final GraphClientOption graphClientOption) { + GraphClientOption option = graphClientOption == null ? new GraphClientOption() : graphClientOption; + return KiotaClientFactory.create(createDefaultGraphInterceptors(option)); } + + /** + * The OkHttpClient Builder with optional GraphClientOption and RequestOptions to override default graph interceptors + * @param requestOptions custom request options to override default graph interceptors + * @return an OkHttpClient Builder instance. + */ + @Nonnull + public static OkHttpClient.Builder create(@Nonnull final RequestOption[] requestOptions) { + return KiotaClientFactory.create(createDefaultGraphInterceptors(requestOptions)); + } + /** * Creates the default Interceptors for use with Graph. * @@ -119,15 +161,46 @@ public static OkHttpClient.Builder create(@Nullable GraphClientOption graphClien * @return an array of interceptors. */ @Nonnull - public static Interceptor[] createDefaultGraphInterceptors(@Nonnull GraphClientOption graphClientOption) { - List handlers = new ArrayList<>(); - addDefaultFeatureUsages(graphClientOption); + public static Interceptor[] createDefaultGraphInterceptors(@Nonnull final GraphClientOption graphClientOption) { + return getDefaultGraphInterceptors(new RequestOption[]{ graphClientOption }).toArray(new Interceptor[0]); + } - handlers.add(new UrlReplaceHandler(new UrlReplaceHandlerOption(CoreConstants.ReplacementConstants.getDefaultReplacementPairs()))); + /** + * Creates the default Interceptors for use with Graph configured with the provided RequestOptions. + * @param requestOptions custom request options to override default graph interceptors + * @return an array of interceptors. + */ + @Nonnull + public static Interceptor[] createDefaultGraphInterceptors(@Nonnull final RequestOption[] requestOptions) { + Objects.requireNonNull(requestOptions, "parameter requestOptions cannot be null"); + return getDefaultGraphInterceptors(requestOptions).toArray(new Interceptor[0]); + } + + /** + * Creates the default Interceptors for use with Graph. + * @param requestOptions custom request options to override default graph interceptors + * @return a list of interceptors. + */ + private static List getDefaultGraphInterceptors(@Nonnull final RequestOption[] requestOptions) { + GraphClientOption graphClientOption = new GraphClientOption(); + UrlReplaceHandlerOption urlReplaceHandlerOption = new UrlReplaceHandlerOption(CoreConstants.ReplacementConstants.getDefaultReplacementPairs()); + for (RequestOption option : requestOptions) { + if (option instanceof UrlReplaceHandlerOption) { + urlReplaceHandlerOption = (UrlReplaceHandlerOption) option; + } + if (option instanceof GraphClientOption) { + graphClientOption = (GraphClientOption) option; + } + } + + List handlers = new ArrayList<>(); + handlers.add(new UrlReplaceHandler(urlReplaceHandlerOption)); handlers.add(new GraphTelemetryHandler(graphClientOption)); - handlers.addAll(Arrays.asList(KiotaClientFactory.createDefaultInterceptors())); - return handlers.toArray(new Interceptor[0]); + handlers.addAll(Arrays.asList(KiotaClientFactory.createDefaultInterceptors(requestOptions))); + addDefaultFeatureUsages(graphClientOption); + return handlers; } + //These are the default features used by the Graph Client private static void addDefaultFeatureUsages(GraphClientOption graphClientOption) { graphClientOption.featureTracker.setFeatureUsage(FeatureFlag.RETRY_HANDLER_FLAG); diff --git a/src/test/java/com/microsoft/graph/core/requests/GraphClientFactoryTest.java b/src/test/java/com/microsoft/graph/core/requests/GraphClientFactoryTest.java index fcc2972b9..001173c46 100644 --- a/src/test/java/com/microsoft/graph/core/requests/GraphClientFactoryTest.java +++ b/src/test/java/com/microsoft/graph/core/requests/GraphClientFactoryTest.java @@ -10,23 +10,88 @@ import java.io.IOException; import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import com.azure.core.credential.AccessToken; +import com.azure.core.credential.TokenCredential; import com.microsoft.graph.core.authentication.AzureIdentityAccessTokenProvider; import com.microsoft.graph.core.authentication.AzureIdentityAuthenticationProvider; +import com.microsoft.graph.core.requests.middleware.GraphTelemetryHandler; +import com.microsoft.graph.core.requests.options.GraphClientOption; +import com.microsoft.kiota.RequestOption; import com.microsoft.kiota.authentication.AccessTokenProvider; import com.microsoft.kiota.authentication.AllowedHostsValidator; import com.microsoft.kiota.authentication.BaseBearerTokenAuthenticationProvider; +import com.microsoft.kiota.http.middleware.HeadersInspectionHandler; +import com.microsoft.kiota.http.middleware.ParametersNameDecodingHandler; +import com.microsoft.kiota.http.middleware.RedirectHandler; +import com.microsoft.kiota.http.middleware.RetryHandler; +import com.microsoft.kiota.http.middleware.UrlReplaceHandler; +import com.microsoft.kiota.http.middleware.UserAgentHandler; +import com.microsoft.kiota.http.middleware.options.RetryHandlerOption; +import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; +import reactor.core.publisher.Mono; class GraphClientFactoryTest { private static final String ACCESS_TOKEN_STRING = "token"; + @Test + void testDefaultCreate() { + final OkHttpClient.Builder clientBuilder = GraphClientFactory.create(); + assertDefaultHandlersPresent(clientBuilder.interceptors()); + } + + @Test + void testCreateWithCustomInterceptorsAddsTelemetry() { + final OkHttpClient.Builder clientBuilder = GraphClientFactory.create( + new RetryHandler(), new RedirectHandler() + ); + + assertEquals(3, clientBuilder.interceptors().size()); + + for (Interceptor interceptor : clientBuilder.interceptors()) { + assertTrue( + interceptor instanceof GraphTelemetryHandler + || interceptor instanceof RetryHandler + || interceptor instanceof RedirectHandler + ); + } + } + + @Test + void testCreateWithGraphClientOption() { + final OkHttpClient.Builder clientBuilder = GraphClientFactory.create(new GraphClientOption()); + assertDefaultHandlersPresent(clientBuilder.interceptors()); + } + + @Test + void testCreateDefaultInterceptorsWithCustomOptions() { + Interceptor[] interceptors = GraphClientFactory.createDefaultGraphInterceptors( + new RequestOption[] {new RetryHandlerOption(null, 0, 0)} + ); + assertDefaultHandlersPresent(Arrays.asList(interceptors)); + + for (Interceptor interceptor : interceptors) { + if (interceptor instanceof RetryHandler) { + RetryHandlerOption retryOptions = ((RetryHandler) interceptor).getRetryOptions(); + Assertions.assertEquals(0, retryOptions.maxRetries()); + Assertions.assertEquals(0, retryOptions.delay()); + } + } + } + @Test void testCreateWithAuthenticationProvider() throws IOException { final BaseBearerTokenAuthenticationProvider mockAuthenticationProvider = @@ -42,6 +107,92 @@ void testCreateWithAuthenticationProvider() throws IOException { assertEquals("Bearer " + ACCESS_TOKEN_STRING, response.request().header("Authorization")); } + @Test + void testCreateWithTokenCredential() throws IOException { + final TokenCredential tokenCredential = mock(TokenCredential.class); + when(tokenCredential.getTokenSync(any())).thenReturn(new AccessToken(ACCESS_TOKEN_STRING, null)); + when(tokenCredential.getToken(any())).thenReturn(Mono.just(new AccessToken(ACCESS_TOKEN_STRING, null))); + + final OkHttpClient graphClient = GraphClientFactory.create(tokenCredential).addInterceptor(new MockResponseHandler()).build(); + Request request = new Request.Builder().url("https://graph.microsoft.com/v1.0/me").build(); + Response response = graphClient.newCall(request).execute(); + + assertEquals(200, response.code()); + assertNotNull(response.request()); + assertTrue(response.request().headers().names().contains("Authorization")); + assertEquals("Bearer " + ACCESS_TOKEN_STRING, response.request().header("Authorization")); + } + + @Test + void testCreateWithAuthenticationProviderAndCustomRequestOptions() throws IOException { + final BaseBearerTokenAuthenticationProvider mockAuthenticationProvider = + getMockAuthenticationProvider(); + var requestOptions = new ArrayList(); + requestOptions.add(new RetryHandlerOption(null, 0, 0)); + OkHttpClient graphClient = GraphClientFactory.create(mockAuthenticationProvider, requestOptions.toArray(new RequestOption[0])).addInterceptor(new MockResponseHandler()).build(); + + var interceptors = graphClient.interceptors(); + for (Interceptor interceptor : interceptors) { + if (interceptor instanceof RetryHandler) { + RetryHandlerOption retryOptions = ((RetryHandler) interceptor).getRetryOptions(); + Assertions.assertEquals(0, retryOptions.maxRetries()); + Assertions.assertEquals(0, retryOptions.delay()); + } + } + + Request request = new Request.Builder().url("https://graph.microsoft.com/v1.0/me") + .addHeader("CustomHeader", "CustomValue").build(); + Response response = graphClient.newCall(request).execute(); + + assertEquals(200, response.code()); + assertNotNull(response.request()); + assertTrue(response.request().headers().names().contains("Authorization")); + assertTrue(response.request().headers().names().contains("CustomHeader")); + assertEquals("Bearer " + ACCESS_TOKEN_STRING, response.request().header("Authorization")); + assertEquals("CustomValue", response.request().header("CustomHeader")); + } + + @Test + void testCreateWithCustomInterceptorsOverwritesDefaults() throws IOException { + + final Interceptor[] interceptors = {new GraphTelemetryHandler(), getDisabledRetryHandler(), + new RedirectHandler()}; + final OkHttpClient client = GraphClientFactory.create(interceptors).build(); + final Request request = new Request.Builder().url("https://graph.microsoft.com/v1.0/users/").build(); + client.newCall(request).execute(); + + for (Interceptor clientInterceptor : client.interceptors()) { + if (clientInterceptor instanceof RetryHandler) { + RetryHandlerOption retryOptions = ((RetryHandler) clientInterceptor).getRetryOptions(); + Assertions.assertEquals(0, retryOptions.maxRetries()); + Assertions.assertEquals(0, retryOptions.delay()); + + } + + assertTrue(clientInterceptor instanceof GraphTelemetryHandler + || clientInterceptor instanceof RedirectHandler + || clientInterceptor instanceof RetryHandler); + } + } + + private void assertDefaultHandlersPresent(final List interceptors) { + HashSet> expectedInterceptors = new HashSet<>( + Arrays.asList( + GraphTelemetryHandler.class, + RetryHandler.class, + UrlReplaceHandler.class, + UserAgentHandler.class, + RedirectHandler.class, + ParametersNameDecodingHandler.class, + HeadersInspectionHandler.class + ) + ); + + for (Interceptor interceptor : interceptors) { + assertTrue(expectedInterceptors.contains(interceptor.getClass())); + } + } + private static BaseBearerTokenAuthenticationProvider getMockAuthenticationProvider() { final AccessTokenProvider mockAccessTokenProvider = mock(AzureIdentityAccessTokenProvider.class); when(mockAccessTokenProvider.getAuthorizationToken(any(URI.class), anyMap())) @@ -53,4 +204,11 @@ private static BaseBearerTokenAuthenticationProvider getMockAuthenticationProvid .thenReturn(mockAccessTokenProvider); return mockAuthenticationProvider; } + + private static @NotNull RetryHandler getDisabledRetryHandler() { + RetryHandlerOption retryHandlerOption = new RetryHandlerOption( + (delay, executionCount, request, response) -> false, 0, 0); + RetryHandler retryHandler = new RetryHandler(retryHandlerOption); + return retryHandler; + } } diff --git a/src/test/java/com/microsoft/graph/core/requests/middleware/GraphTelemetryHandlerTest.java b/src/test/java/com/microsoft/graph/core/requests/middleware/GraphTelemetryHandlerTest.java index b03f40615..06bf4f9e5 100644 --- a/src/test/java/com/microsoft/graph/core/requests/middleware/GraphTelemetryHandlerTest.java +++ b/src/test/java/com/microsoft/graph/core/requests/middleware/GraphTelemetryHandlerTest.java @@ -10,7 +10,9 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; + import org.junit.jupiter.api.Test; + import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -29,23 +31,31 @@ void telemetryHandlerDefaultTests() throws IOException { final Response response = client.newCall(request).execute(); assertNotNull(response); + assertNotNull(response.request()); + assertNotNull(response.request().header(CoreConstants.Headers.SDK_VERSION_HEADER_NAME)); assertTrue(response.request().header(CoreConstants.Headers.SDK_VERSION_HEADER_NAME).contains(expectedCore)); - assertTrue(!response.request().header(CoreConstants.Headers.SDK_VERSION_HEADER_NAME).contains(CoreConstants.Headers.ANDROID_VERSION_PREFIX)); // Android version is not going to be present on unit tests running on java platform - assertTrue(response.request().header(CoreConstants.Headers.SDK_VERSION_HEADER_NAME).contains(defaultSDKVersion)); + assertTrue(!response.request().header(CoreConstants.Headers.SDK_VERSION_HEADER_NAME).contains( + CoreConstants.Headers.ANDROID_VERSION_PREFIX)); // Android version is not going to be present on unit tests running on java platform + assertTrue( + response.request().header(CoreConstants.Headers.SDK_VERSION_HEADER_NAME).contains(defaultSDKVersion)); } @Test void arrayInterceptorsTest() throws IOException { final String expectedCore = CoreConstants.Headers.GRAPH_VERSION_PREFIX + "/" + CoreConstants.Headers.VERSION; - final Interceptor[] interceptors = {new GraphTelemetryHandler(), new RetryHandler(), new RedirectHandler()}; + final Interceptor[] interceptors = {new GraphTelemetryHandler(), new RetryHandler(), + new RedirectHandler()}; final OkHttpClient client = GraphClientFactory.create(interceptors).build(); final Request request = new Request.Builder().url("https://graph.microsoft.com/v1.0/users/").build(); final Response response = client.newCall(request).execute(); assertNotNull(response); + assertNotNull(response.request()); + assertNotNull(response.request().header(CoreConstants.Headers.SDK_VERSION_HEADER_NAME)); assertTrue(response.request().header(CoreConstants.Headers.SDK_VERSION_HEADER_NAME).contains(expectedCore)); - assertTrue(response.request().header(CoreConstants.Headers.SDK_VERSION_HEADER_NAME).contains(defaultSDKVersion)); + assertTrue( + response.request().header(CoreConstants.Headers.SDK_VERSION_HEADER_NAME).contains(defaultSDKVersion)); } @Test @@ -58,8 +68,11 @@ void arrayInterceptorEmptyTest() throws IOException { final Response response = client.newCall(request).execute(); assertNotNull(response); + assertNotNull(response.request()); + assertNotNull(response.request().header(CoreConstants.Headers.SDK_VERSION_HEADER_NAME)); assertTrue(response.request().header(CoreConstants.Headers.SDK_VERSION_HEADER_NAME).contains(expectedCore)); - assertTrue(response.request().header(CoreConstants.Headers.SDK_VERSION_HEADER_NAME).contains(defaultSDKVersion)); + assertTrue( + response.request().header(CoreConstants.Headers.SDK_VERSION_HEADER_NAME).contains(defaultSDKVersion)); } @Test @@ -76,7 +89,7 @@ void testClientOptions() throws IOException { graphClientOption.setGraphServiceTargetVersion(serviceLibVer); final String expectedCoreVer = - CoreConstants.Headers.GRAPH_VERSION_PREFIX + "/" +coreLibVer; + CoreConstants.Headers.GRAPH_VERSION_PREFIX + "/" + coreLibVer; final String expectedClientEndpoint = CoreConstants.Headers.JAVA_VERSION_PREFIX + "-" + serviceLibVer + "/" + clientLibVer; @@ -84,8 +97,12 @@ void testClientOptions() throws IOException { final Request request = new Request.Builder().url("https://graph.microsoft.com/v1.0/users/").build(); final Response response = client.newCall(request).execute(); + assertNotNull(response); + assertNotNull(response.request()); + assertNotNull(response.request().header(CoreConstants.Headers.SDK_VERSION_HEADER_NAME)); assertTrue(response.request().header(CoreConstants.Headers.SDK_VERSION_HEADER_NAME).contains(expectedCoreVer)); - assertTrue(response.request().header(CoreConstants.Headers.SDK_VERSION_HEADER_NAME).contains(expectedClientEndpoint)); + assertTrue( + response.request().header(CoreConstants.Headers.SDK_VERSION_HEADER_NAME).contains(expectedClientEndpoint)); assertTrue(response.request().header(CoreConstants.Headers.CLIENT_REQUEST_ID).contains(requestId)); } }