From ca43f5e96b472e07f63f1e6bfb51e8acb8ea7ccd Mon Sep 17 00:00:00 2001 From: utkrishtS Date: Wed, 11 Feb 2026 15:56:51 +0530 Subject: [PATCH] feat: add Builder pattern for DefaultClient with deprecated legacy constructor --- EXAMPLES.md | 102 +++++- V4_MIGRATION_GUIDE.md | 31 ++ .../auth0/android/request/DefaultClient.kt | 254 ++++++++++++--- .../android/provider/WebAuthProviderTest.kt | 1 + .../android/request/DefaultClientTest.kt | 290 +++++++++++++++++- .../com/auth0/android/util/SSLTestUtils.kt | 20 +- .../com/auth0/sample/DatabaseLoginFragment.kt | 4 +- 7 files changed, 626 insertions(+), 76 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index cae42b8ca..064bc8355 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -3201,10 +3201,12 @@ The Auth0 class can be configured with a `NetworkingClient`, which will be used ### Timeout configuration ```kotlin -val netClient = DefaultClient( - connectTimeout = 30, - readTimeout = 30 -) +val netClient = DefaultClient.Builder() + .connectTimeout(30) + .readTimeout(30) + .writeTimeout(30) + .callTimeout(120) + .build() val account = Auth0.getInstance("{YOUR_CLIENT_ID}", "{YOUR_DOMAIN}") account.networkingClient = netClient @@ -3214,43 +3216,78 @@ account.networkingClient = netClient Using Java ```java -DefaultClient netClient = new DefaultClient(30, 30); +DefaultClient netClient = new DefaultClient.Builder() + .connectTimeout(30) + .readTimeout(30) + .writeTimeout(30) + .callTimeout(120) + .build(); Auth0 account = Auth0.getInstance("client id", "domain"); account.setNetworkingClient(netClient); ``` -### Logging configuration +
+ Legacy constructor (still supported) ```kotlin val netClient = DefaultClient( - enableLogging = true + connectTimeout = 30, + readTimeout = 30 ) +``` +
+ +### Logging configuration + +```kotlin +val netClient = DefaultClient.Builder() + .enableLogging(true) + .build() val account = Auth0.getInstance("{YOUR_CLIENT_ID}", "{YOUR_DOMAIN}") account.networkingClient = netClient ``` +You can also customize the log level and provide a custom logger: + +```kotlin +val netClient = DefaultClient.Builder() + .enableLogging(true) + .logLevel(HttpLoggingInterceptor.Level.HEADERS) // NONE, BASIC, HEADERS, or BODY (default) + .logger(HttpLoggingInterceptor.Logger { message -> Log.d("Auth0Http", message) }) + .build() +``` +
Using Java ```java -import java.util.HashMap; - -DefaultClient netClient = new DefaultClient( - 10, 10, new HashMap<>() ,true -); +DefaultClient netClient = new DefaultClient.Builder() + .enableLogging(true) + .logLevel(HttpLoggingInterceptor.Level.HEADERS) + .build(); Auth0 account = Auth0.getInstance("client id", "domain"); account.setNetworkingClient(netClient); ```
-### Set additional headers for all requests +
+ Legacy constructor (still supported) ```kotlin val netClient = DefaultClient( - defaultHeaders = mapOf("{HEADER-NAME}" to "{HEADER-VALUE}") + enableLogging = true ) +``` +
+ +### Set additional headers for all requests + +```kotlin +val netClient = DefaultClient.Builder() + .defaultHeaders(mapOf("{HEADER-NAME}" to "{HEADER-VALUE}")) + .build() val account = Auth0.getInstance("{YOUR_CLIENT_ID}", "{YOUR_DOMAIN}") account.networkingClient = netClient @@ -3263,14 +3300,45 @@ account.networkingClient = netClient Map defaultHeaders = new HashMap<>(); defaultHeaders.put("{HEADER-NAME}", "{HEADER-VALUE}"); -DefaultClient netClient = new DefaultClient( - 10,10 , defaultHeaders -); +DefaultClient netClient = new DefaultClient.Builder() + .defaultHeaders(defaultHeaders) + .build(); Auth0 account = Auth0.getInstance("client id", "domain"); account.setNetworkingClient(netClient); ``` +
+ Legacy constructor (still supported) + +```kotlin +val netClient = DefaultClient( + defaultHeaders = mapOf("{HEADER-NAME}" to "{HEADER-VALUE}") +) +``` +
+ +### Custom interceptors + +You can add custom OkHttp interceptors to the `DefaultClient` for use cases such as auth token injection, analytics, or certificate pinning: + +```kotlin +val netClient = DefaultClient.Builder() + .addInterceptor(Interceptor { chain -> + val request = chain.request().newBuilder() + .addHeader("X-Request-Id", UUID.randomUUID().toString()) + .build() + chain.proceed(request) + }) + .addInterceptor(myAnalyticsInterceptor) + .build() + +val account = Auth0.getInstance("{YOUR_CLIENT_ID}", "{YOUR_DOMAIN}") +account.networkingClient = netClient +``` + +Interceptors are invoked in the order they were added, after the built-in retry interceptor and before the logging interceptor. + ### Advanced configuration For more advanced configuration of the networking client, you can provide a custom implementation of `NetworkingClient`. This may be useful when you wish to reuse your own networking client, configure a proxy, etc. diff --git a/V4_MIGRATION_GUIDE.md b/V4_MIGRATION_GUIDE.md index b8ca744b3..97d80c09c 100644 --- a/V4_MIGRATION_GUIDE.md +++ b/V4_MIGRATION_GUIDE.md @@ -96,6 +96,37 @@ implementation 'com.google.code.gson:gson:2.8.9' // your preferred version > **Note:** Pinning or excluding is not recommended long-term, as the SDK has been tested and validated against Gson 2.11.0. +### DefaultClient.Builder + +v4 introduces a `DefaultClient.Builder` for configuring the HTTP client. This replaces the constructor-based approach with a more flexible builder pattern that supports additional options such as write/call timeouts, custom interceptors, and custom loggers. + +**v3 (constructor-based — deprecated):** + +```kotlin +// ⚠️ Deprecated: still compiles but shows a warning +val client = DefaultClient( + connectTimeout = 30, + readTimeout = 30, + enableLogging = true +) +``` + +**v4 (builder pattern — recommended):** + +```kotlin +val client = DefaultClient.Builder() + .connectTimeout(30) + .readTimeout(30) + .writeTimeout(30) + .callTimeout(120) + .enableLogging(true) + .logLevel(HttpLoggingInterceptor.Level.HEADERS) + .addInterceptor(myCustomInterceptor) + .build() +``` + +The legacy constructor is deprecated but **not removed** — existing code will continue to compile and run. Your IDE will show a deprecation warning with a suggested `ReplaceWith` quick-fix to migrate to the Builder. + ## Getting Help If you encounter issues during migration: diff --git a/auth0/src/main/java/com/auth0/android/request/DefaultClient.kt b/auth0/src/main/java/com/auth0/android/request/DefaultClient.kt index 157f4f6b7..0b1b4d74e 100644 --- a/auth0/src/main/java/com/auth0/android/request/DefaultClient.kt +++ b/auth0/src/main/java/com/auth0/android/request/DefaultClient.kt @@ -24,25 +24,187 @@ import javax.net.ssl.X509TrustManager /** * Default implementation of a Networking Client. + * + * Use [DefaultClient.Builder] to create a new instance with custom configuration: + * + * ```kotlin + * val client = DefaultClient.Builder() + * .connectTimeout(30) + * .readTimeout(30) + * .writeTimeout(30) + * .enableLogging(true) + * .addInterceptor(myInterceptor) + * .build() + * ``` + * + * The legacy constructor-based API is still supported for backward compatibility. */ -public class DefaultClient @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal constructor( - connectTimeout: Int, - readTimeout: Int, +public class DefaultClient private constructor( private val defaultHeaders: Map, - enableLogging: Boolean, private val gson: Gson, - sslSocketFactory: SSLSocketFactory?, - trustManager: X509TrustManager? + okHttpClientBuilder: OkHttpClient.Builder ) : NetworkingClient { /** - * Create a new DefaultClient. + * Builder for creating a [DefaultClient] instance with custom configuration. * - * @param connectTimeout the connection timeout, in seconds, to use when executing requests. Default is ten seconds. - * @param readTimeout the read timeout, in seconds, to use when executing requests. Default is ten seconds. - * @param defaultHeaders any headers that should be sent on all requests. If a specific request specifies a header with the same key as any header in the default headers, the header specified on the request will take precedence. Default is an empty map. - * @param enableLogging whether HTTP request and response info should be logged. This should only be set to `true` for debugging purposes in non-production environments, as sensitive information is included in the logs. Defaults to `false`. + * Example usage: + * ```kotlin + * val client = DefaultClient.Builder() + * .connectTimeout(30) + * .readTimeout(30) + * .writeTimeout(30) + * .callTimeout(60) + * .defaultHeaders(mapOf("X-Custom" to "value")) + * .enableLogging(true) + * .logLevel(HttpLoggingInterceptor.Level.HEADERS) + * .logger(myCustomLogger) + * .addInterceptor(myInterceptor) + * .build() + * ``` */ + public class Builder { + private var connectTimeout: Int = DEFAULT_TIMEOUT_SECONDS + private var readTimeout: Int = DEFAULT_TIMEOUT_SECONDS + private var writeTimeout: Int = DEFAULT_TIMEOUT_SECONDS + private var callTimeout: Int = 0 + private var defaultHeaders: Map = mapOf() + private var enableLogging: Boolean = false + private var logLevel: HttpLoggingInterceptor.Level = HttpLoggingInterceptor.Level.BODY + private var logger: HttpLoggingInterceptor.Logger? = null + private val interceptors: MutableList = mutableListOf() + private var gson: Gson = GsonProvider.gson + private var sslSocketFactory: SSLSocketFactory? = null + private var trustManager: X509TrustManager? = null + + /** + * Sets the connection timeout, in seconds. Default is 10 seconds. + */ + public fun connectTimeout(timeout: Int): Builder = apply { this.connectTimeout = timeout } + + /** + * Sets the read timeout, in seconds. Default is 10 seconds. + */ + public fun readTimeout(timeout: Int): Builder = apply { this.readTimeout = timeout } + + /** + * Sets the write timeout, in seconds. Default is 10 seconds. + */ + public fun writeTimeout(timeout: Int): Builder = apply { this.writeTimeout = timeout } + + /** + * Sets the call timeout, in seconds. Default is 0 (no timeout). + * This is an overall timeout that spans the entire call: resolving DNS, connecting, + * writing the request body, server processing, and reading the response body. + */ + public fun callTimeout(timeout: Int): Builder = apply { this.callTimeout = timeout } + + /** + * Sets default headers to include on all requests. If a specific request specifies + * a header with the same key, the request-level header takes precedence. + */ + public fun defaultHeaders(headers: Map): Builder = + apply { this.defaultHeaders = headers } + + /** + * Enables or disables HTTP logging. Should only be set to `true` for debugging + * in non-production environments, as sensitive information may be logged. + * Defaults to `false`. + */ + public fun enableLogging(enable: Boolean): Builder = apply { this.enableLogging = enable } + + /** + * Sets the log level for the HTTP logging interceptor. + * Only takes effect if [enableLogging] is set to `true`. + * Default is [HttpLoggingInterceptor.Level.BODY]. + */ + public fun logLevel(level: HttpLoggingInterceptor.Level): Builder = + apply { this.logLevel = level } + + /** + * Sets a custom logger for the HTTP logging interceptor. + * Only takes effect if [enableLogging] is set to `true`. + * If not set, the default [HttpLoggingInterceptor.Logger] (which logs to logcat) is used. + */ + public fun logger(logger: HttpLoggingInterceptor.Logger): Builder = + apply { this.logger = logger } + + /** + * Adds a custom OkHttp [Interceptor]. Multiple interceptors can be added + * by calling this method multiple times. They are invoked in the order they were added, + * after the built-in [RetryInterceptor] and before the logging interceptor. + */ + public fun addInterceptor(interceptor: Interceptor): Builder = + apply { this.interceptors.add(interceptor) } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal fun gson(gson: Gson): Builder = apply { this.gson = gson } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal fun sslSocketFactory( + sslSocketFactory: SSLSocketFactory, + trustManager: X509TrustManager + ): Builder = apply { + this.sslSocketFactory = sslSocketFactory + this.trustManager = trustManager + } + + /** + * Builds a new [DefaultClient] instance with the configured options. + */ + public fun build(): DefaultClient { + val okBuilder = OkHttpClient.Builder() + + okBuilder.addInterceptor(RetryInterceptor()) + + interceptors.forEach { okBuilder.addInterceptor(it) } + + if (enableLogging) { + val loggingInterceptor = if (logger != null) { + HttpLoggingInterceptor(logger!!) + } else { + HttpLoggingInterceptor() + } + loggingInterceptor.setLevel(logLevel) + okBuilder.addInterceptor(loggingInterceptor) + } + + okBuilder.connectTimeout(connectTimeout.toLong(), TimeUnit.SECONDS) + okBuilder.readTimeout(readTimeout.toLong(), TimeUnit.SECONDS) + okBuilder.writeTimeout(writeTimeout.toLong(), TimeUnit.SECONDS) + okBuilder.callTimeout(callTimeout.toLong(), TimeUnit.SECONDS) + + val ssl = sslSocketFactory + val tm = trustManager + if (ssl != null && tm != null) { + okBuilder.sslSocketFactory(ssl, tm) + } + + return DefaultClient(defaultHeaders, gson, okBuilder) + } + } + + /** + * Create a new DefaultClient with default configuration. + * + * For more configuration options, use [DefaultClient.Builder]. + * + * @param connectTimeout the connection timeout, in seconds. Default is 10 seconds. + * @param readTimeout the read timeout, in seconds. Default is 10 seconds. + * @param defaultHeaders headers to include on all requests. Default is an empty map. + * @param enableLogging whether to log HTTP request/response info. Defaults to `false`. + */ + @Deprecated( + message = "Use DefaultClient.Builder() for more configuration options.", + replaceWith = ReplaceWith( + "DefaultClient.Builder()" + + ".connectTimeout(connectTimeout)" + + ".readTimeout(readTimeout)" + + ".defaultHeaders(defaultHeaders)" + + ".enableLogging(enableLogging)" + + ".build()" + ) + ) @JvmOverloads public constructor( connectTimeout: Int = DEFAULT_TIMEOUT_SECONDS, @@ -50,13 +212,44 @@ public class DefaultClient @VisibleForTesting(otherwise = VisibleForTesting.PRIV defaultHeaders: Map = mapOf(), enableLogging: Boolean = false, ) : this( - connectTimeout, - readTimeout, - defaultHeaders, - enableLogging, - GsonProvider.gson, - null, - null + defaultHeaders = defaultHeaders, + gson = GsonProvider.gson, + okHttpClientBuilder = OkHttpClient.Builder().apply { + addInterceptor(RetryInterceptor()) + if (enableLogging) { + addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) + } + connectTimeout(connectTimeout.toLong(), TimeUnit.SECONDS) + readTimeout(readTimeout.toLong(), TimeUnit.SECONDS) + } + ) + + /** + * Internal constructor used by tests to inject SSL and Gson. + */ + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal constructor( + connectTimeout: Int, + readTimeout: Int, + defaultHeaders: Map, + enableLogging: Boolean, + gson: Gson, + sslSocketFactory: SSLSocketFactory?, + trustManager: X509TrustManager? + ) : this( + defaultHeaders = defaultHeaders, + gson = gson, + okHttpClientBuilder = OkHttpClient.Builder().apply { + addInterceptor(RetryInterceptor()) + if (enableLogging) { + addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) + } + connectTimeout(connectTimeout.toLong(), TimeUnit.SECONDS) + readTimeout(readTimeout.toLong(), TimeUnit.SECONDS) + if (sslSocketFactory != null && trustManager != null) { + sslSocketFactory(sslSocketFactory, trustManager) + } + } ) @get:VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @@ -125,35 +318,14 @@ public class DefaultClient @VisibleForTesting(otherwise = VisibleForTesting.PRIV } init { - val builder = OkHttpClient.Builder() - // Add retry interceptor - builder.addInterceptor(RetryInterceptor()) - - // logging - if (enableLogging) { - val logger: Interceptor = HttpLoggingInterceptor() - .setLevel(HttpLoggingInterceptor.Level.BODY) - builder.addInterceptor(logger) - } + okHttpClient = okHttpClientBuilder.build() - // timeouts - builder.connectTimeout(connectTimeout.toLong(), TimeUnit.SECONDS) - builder.readTimeout(readTimeout.toLong(), TimeUnit.SECONDS) - - // testing with ssl hook (internal constructor params visibility only) - if (sslSocketFactory != null && trustManager != null) { - builder.sslSocketFactory(sslSocketFactory, trustManager) - } - - okHttpClient = builder.build() - - // Non-retryable client for DPoP requests + // Non-retryable client for DPoP requests — inherits all configuration nonRetryableOkHttpClient = okHttpClient.newBuilder() .retryOnConnectionFailure(false) .build() } - internal companion object { const val DEFAULT_TIMEOUT_SECONDS: Int = 10 val APPLICATION_JSON_UTF8: MediaType = diff --git a/auth0/src/test/java/com/auth0/android/provider/WebAuthProviderTest.kt b/auth0/src/test/java/com/auth0/android/provider/WebAuthProviderTest.kt index 9eea3b9e2..94baa6b3f 100644 --- a/auth0/src/test/java/com/auth0/android/provider/WebAuthProviderTest.kt +++ b/auth0/src/test/java/com/auth0/android/provider/WebAuthProviderTest.kt @@ -1169,6 +1169,7 @@ public class WebAuthProviderTest { @Test @Throws(Exception::class) + @Suppress("DEPRECATION") public fun shouldResumeLoginWithCustomNetworkingClient() { val networkingClient: NetworkingClient = Mockito.spy(DefaultClient()) val authCallback = mock>() diff --git a/auth0/src/test/java/com/auth0/android/request/DefaultClientTest.kt b/auth0/src/test/java/com/auth0/android/request/DefaultClientTest.kt index 7a71a70bf..0db1c95a0 100644 --- a/auth0/src/test/java/com/auth0/android/request/DefaultClientTest.kt +++ b/auth0/src/test/java/com/auth0/android/request/DefaultClientTest.kt @@ -81,6 +81,7 @@ public class DefaultClientTest { } @Test + @Suppress("DEPRECATION") public fun shouldHaveLoggingDisabledByDefault() { val netClient = DefaultClient(enableLogging = false) assertThat(netClient.okHttpClient.interceptors, hasSize(1)) @@ -91,6 +92,7 @@ public class DefaultClientTest { } @Test + @Suppress("DEPRECATION") public fun shouldHaveRetryInterceptorEnabled() { val netClient = DefaultClient(enableLogging = false) assertThat(netClient.okHttpClient.interceptors, hasSize(1)) @@ -101,6 +103,7 @@ public class DefaultClientTest { } @Test + @Suppress("DEPRECATION") public fun shouldHaveLoggingEnabledIfSpecified() { val netClient = DefaultClient(enableLogging = true) assertThat(netClient.okHttpClient.interceptors, hasSize(2)) @@ -113,6 +116,7 @@ public class DefaultClientTest { } @Test + @Suppress("DEPRECATION") public fun shouldHaveDefaultTimeoutValues() { val client = DefaultClient() assertThat(client.okHttpClient.connectTimeoutMillis, equalTo(10 * 1000)) @@ -120,6 +124,7 @@ public class DefaultClientTest { } @Test + @Suppress("DEPRECATION") public fun shouldUseTimeoutConfigIfSpecified() { val client = DefaultClient(connectTimeout = 100, readTimeout = 200) assertThat(client.okHttpClient.connectTimeoutMillis, equalTo(100 * 1000)) @@ -353,14 +358,283 @@ public class DefaultClientTest { } private fun createDefaultClientForTest(defaultHeaders: Map): DefaultClient { - return DefaultClient( - defaultHeaders = defaultHeaders, - readTimeout = 10, - connectTimeout = 10, - enableLogging = false, - gson = gson, - sslSocketFactory = SSLTestUtils.clientCertificates.sslSocketFactory(), - trustManager = SSLTestUtils.clientCertificates.trustManager + return DefaultClient.Builder() + .connectTimeout(10) + .readTimeout(10) + .defaultHeaders(defaultHeaders) + .enableLogging(false) + .gson(gson) + .sslSocketFactory( + SSLTestUtils.clientCertificates.sslSocketFactory(), + SSLTestUtils.clientCertificates.trustManager + ) + .build() + } + + @Test + public fun builderShouldCreateClientWithDefaultValues() { + val client = DefaultClient.Builder().build() + assertThat(client.okHttpClient.connectTimeoutMillis, equalTo(10 * 1000)) + assertThat(client.okHttpClient.readTimeoutMillis, equalTo(10 * 1000)) + assertThat(client.okHttpClient.writeTimeoutMillis, equalTo(10 * 1000)) + assertThat(client.okHttpClient.callTimeoutMillis, equalTo(0)) + assertThat(client.okHttpClient.interceptors, hasSize(1)) + assertThat(client.okHttpClient.interceptors[0] is RetryInterceptor, equalTo(true)) + } + + @Test + public fun builderShouldSetConnectTimeout() { + val client = DefaultClient.Builder() + .connectTimeout(30) + .build() + assertThat(client.okHttpClient.connectTimeoutMillis, equalTo(30 * 1000)) + } + + @Test + public fun builderShouldSetReadTimeout() { + val client = DefaultClient.Builder() + .readTimeout(45) + .build() + assertThat(client.okHttpClient.readTimeoutMillis, equalTo(45 * 1000)) + } + + @Test + public fun builderShouldSetWriteTimeout() { + val client = DefaultClient.Builder() + .writeTimeout(20) + .build() + assertThat(client.okHttpClient.writeTimeoutMillis, equalTo(20 * 1000)) + } + + @Test + public fun builderShouldSetCallTimeout() { + val client = DefaultClient.Builder() + .callTimeout(60) + .build() + assertThat(client.okHttpClient.callTimeoutMillis, equalTo(60 * 1000)) + } + + @Test + public fun builderShouldSetAllTimeouts() { + val client = DefaultClient.Builder() + .connectTimeout(15) + .readTimeout(25) + .writeTimeout(35) + .callTimeout(120) + .build() + assertThat(client.okHttpClient.connectTimeoutMillis, equalTo(15 * 1000)) + assertThat(client.okHttpClient.readTimeoutMillis, equalTo(25 * 1000)) + assertThat(client.okHttpClient.writeTimeoutMillis, equalTo(35 * 1000)) + assertThat(client.okHttpClient.callTimeoutMillis, equalTo(120 * 1000)) + } + + @Test + public fun builderShouldEnableLoggingWithDefaultLevel() { + val client = DefaultClient.Builder() + .enableLogging(true) + .build() + assertThat(client.okHttpClient.interceptors, hasSize(2)) + val loggingInterceptor = client.okHttpClient.interceptors[1] as HttpLoggingInterceptor + assertThat(loggingInterceptor.level, equalTo(HttpLoggingInterceptor.Level.BODY)) + } + + @Test + public fun builderShouldSetCustomLogLevel() { + val client = DefaultClient.Builder() + .enableLogging(true) + .logLevel(HttpLoggingInterceptor.Level.HEADERS) + .build() + assertThat(client.okHttpClient.interceptors, hasSize(2)) + val loggingInterceptor = client.okHttpClient.interceptors[1] as HttpLoggingInterceptor + assertThat(loggingInterceptor.level, equalTo(HttpLoggingInterceptor.Level.HEADERS)) + } + + @Test + public fun builderShouldNotAddLoggingInterceptorWhenDisabled() { + val client = DefaultClient.Builder() + .enableLogging(false) + .logLevel(HttpLoggingInterceptor.Level.HEADERS) + .build() + assertThat(client.okHttpClient.interceptors, hasSize(1)) + assertThat(client.okHttpClient.interceptors[0] is RetryInterceptor, equalTo(true)) + } + + @Test + public fun builderShouldSetCustomLogger() { + val logs = mutableListOf() + val customLogger = HttpLoggingInterceptor.Logger { message -> logs.add(message) } + + val client = DefaultClient.Builder() + .enableLogging(true) + .logger(customLogger) + .sslSocketFactory( + SSLTestUtils.clientCertificates.sslSocketFactory(), + SSLTestUtils.clientCertificates.trustManager + ) + .build() + + assertThat(client.okHttpClient.interceptors, hasSize(2)) + assertThat(client.okHttpClient.interceptors[1] is HttpLoggingInterceptor, equalTo(true)) + + enqueueMockResponse(STATUS_SUCCESS, JSON_OK) + executeRequest(HttpMethod.GET, client) + assertThat(logs.isEmpty(), equalTo(false)) + } + + @Test + public fun builderShouldAddSingleCustomInterceptor() { + var intercepted = false + val customInterceptor = Interceptor { chain -> + intercepted = true + chain.proceed(chain.request()) + } + + val client = DefaultClient.Builder() + .addInterceptor(customInterceptor) + .sslSocketFactory( + SSLTestUtils.clientCertificates.sslSocketFactory(), + SSLTestUtils.clientCertificates.trustManager + ) + .build() + + assertThat(client.okHttpClient.interceptors, hasSize(2)) + assertThat(client.okHttpClient.interceptors[0] is RetryInterceptor, equalTo(true)) + + enqueueMockResponse(STATUS_SUCCESS, JSON_OK) + executeRequest(HttpMethod.GET, client) + assertThat(intercepted, equalTo(true)) + } + + @Test + public fun builderShouldAddMultipleCustomInterceptors() { + val callOrder = mutableListOf() + val firstInterceptor = Interceptor { chain -> + callOrder.add("first") + chain.proceed(chain.request()) + } + val secondInterceptor = Interceptor { chain -> + callOrder.add("second") + chain.proceed(chain.request()) + } + + val client = DefaultClient.Builder() + .addInterceptor(firstInterceptor) + .addInterceptor(secondInterceptor) + .sslSocketFactory( + SSLTestUtils.clientCertificates.sslSocketFactory(), + SSLTestUtils.clientCertificates.trustManager + ) + .build() + + assertThat(client.okHttpClient.interceptors, hasSize(3)) + + enqueueMockResponse(STATUS_SUCCESS, JSON_OK) + executeRequest(HttpMethod.GET, client) + assertThat(callOrder, equalTo(listOf("first", "second"))) + } + + @Test + public fun builderShouldPlaceCustomInterceptorsBeforeLogging() { + val callOrder = mutableListOf() + val customInterceptor = Interceptor { chain -> + callOrder.add("custom") + chain.proceed(chain.request()) + } + val customLogger = HttpLoggingInterceptor.Logger { callOrder.add("logging") } + + val client = DefaultClient.Builder() + .addInterceptor(customInterceptor) + .enableLogging(true) + .logger(customLogger) + .sslSocketFactory( + SSLTestUtils.clientCertificates.sslSocketFactory(), + SSLTestUtils.clientCertificates.trustManager + ) + .build() + + assertThat(client.okHttpClient.interceptors, hasSize(3)) + assertThat(client.okHttpClient.interceptors[0] is RetryInterceptor, equalTo(true)) + assertThat(client.okHttpClient.interceptors[2] is HttpLoggingInterceptor, equalTo(true)) + + enqueueMockResponse(STATUS_SUCCESS, JSON_OK) + executeRequest(HttpMethod.GET, client) + val customIndex = callOrder.indexOf("custom") + val loggingIndex = callOrder.indexOf("logging") + assertThat(customIndex, not(equalTo(-1))) + assertThat(loggingIndex, not(equalTo(-1))) + assertThat(customIndex < loggingIndex, equalTo(true)) + } + + @Test + public fun builderShouldSetDefaultHeaders() { + enqueueMockResponse(STATUS_SUCCESS, JSON_OK) + val client = DefaultClient.Builder() + .defaultHeaders(mapOf("x-custom" to "test-value")) + .sslSocketFactory( + SSLTestUtils.clientCertificates.sslSocketFactory(), + SSLTestUtils.clientCertificates.trustManager + ) + .build() + + executeRequest(HttpMethod.GET, client) + val sentRequest = mockServer.takeRequest() + requestAssertions(sentRequest, HttpMethod.GET, mapOf("x-custom" to "test-value")) + } + + @Test + public fun builderNonRetryableClientShouldInheritConfiguration() { + val customInterceptor = Interceptor { chain -> chain.proceed(chain.request()) } + val client = DefaultClient.Builder() + .connectTimeout(25) + .readTimeout(35) + .writeTimeout(45) + .addInterceptor(customInterceptor) + .enableLogging(true) + .build() + + assertThat( + client.nonRetryableOkHttpClient.connectTimeoutMillis, + equalTo(client.okHttpClient.connectTimeoutMillis) + ) + assertThat( + client.nonRetryableOkHttpClient.readTimeoutMillis, + equalTo(client.okHttpClient.readTimeoutMillis) + ) + assertThat( + client.nonRetryableOkHttpClient.writeTimeoutMillis, + equalTo(client.okHttpClient.writeTimeoutMillis) + ) + assertThat( + client.nonRetryableOkHttpClient.interceptors.size, + equalTo(client.okHttpClient.interceptors.size) + ) + assertThat(client.okHttpClient.retryOnConnectionFailure, equalTo(true)) + assertThat(client.nonRetryableOkHttpClient.retryOnConnectionFailure, equalTo(false)) + } + + @Test + @Suppress("DEPRECATION") + public fun legacyConstructorShouldStillWork() { + val client = DefaultClient() + assertThat(client.okHttpClient.connectTimeoutMillis, equalTo(10 * 1000)) + assertThat(client.okHttpClient.readTimeoutMillis, equalTo(10 * 1000)) + assertThat(client.okHttpClient.interceptors, hasSize(1)) + assertThat(client.okHttpClient.interceptors[0] is RetryInterceptor, equalTo(true)) + } + + @Test + @Suppress("DEPRECATION") + public fun legacyConstructorWithParamsShouldStillWork() { + val client = DefaultClient( + connectTimeout = 30, + readTimeout = 45, + defaultHeaders = mapOf("X-Test" to "value"), + enableLogging = true ) + assertThat(client.okHttpClient.connectTimeoutMillis, equalTo(30 * 1000)) + assertThat(client.okHttpClient.readTimeoutMillis, equalTo(45 * 1000)) + assertThat(client.okHttpClient.interceptors, hasSize(2)) + assertThat(client.okHttpClient.interceptors[0] is RetryInterceptor, equalTo(true)) + assertThat(client.okHttpClient.interceptors[1] is HttpLoggingInterceptor, equalTo(true)) } } \ No newline at end of file diff --git a/auth0/src/test/java/com/auth0/android/util/SSLTestUtils.kt b/auth0/src/test/java/com/auth0/android/util/SSLTestUtils.kt index 542a8e4b6..897d71123 100644 --- a/auth0/src/test/java/com/auth0/android/util/SSLTestUtils.kt +++ b/auth0/src/test/java/com/auth0/android/util/SSLTestUtils.kt @@ -31,15 +31,17 @@ internal object SSLTestUtils { .heldCertificate(localhostCertificate) .build() - testClient = DefaultClient( - defaultHeaders = mapOf(), - readTimeout = 10, - connectTimeout = 10, - enableLogging = false, - gson = GsonProvider.gson, - sslSocketFactory = clientCertificates.sslSocketFactory(), - trustManager = clientCertificates.trustManager - ) + testClient = DefaultClient.Builder() + .connectTimeout(10) + .readTimeout(10) + .defaultHeaders(mapOf()) + .enableLogging(false) + .gson(GsonProvider.gson) + .sslSocketFactory( + clientCertificates.sslSocketFactory(), + clientCertificates.trustManager + ) + .build() } fun createMockWebServer(): MockWebServer { diff --git a/sample/src/main/java/com/auth0/sample/DatabaseLoginFragment.kt b/sample/src/main/java/com/auth0/sample/DatabaseLoginFragment.kt index 918de531b..c4e54212a 100644 --- a/sample/src/main/java/com/auth0/sample/DatabaseLoginFragment.kt +++ b/sample/src/main/java/com/auth0/sample/DatabaseLoginFragment.kt @@ -59,7 +59,9 @@ class DatabaseLoginFragment : Fragment() { getString(R.string.com_auth0_domain) ) // Only enable network traffic logging on production environments! - account.networkingClient = DefaultClient(enableLogging = true) + account.networkingClient = DefaultClient.Builder() + .enableLogging(true) + .build() account }