From 205f8e49e4ac8a9630be8275df4c4340d55f6ecb Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Mon, 30 Sep 2024 19:49:50 +0600 Subject: [PATCH 1/3] fix: handle IOException on OkHttp interceptor --- .../e/apps/di/network/InterceptorModule.kt | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/foundation/e/apps/di/network/InterceptorModule.kt b/app/src/main/java/foundation/e/apps/di/network/InterceptorModule.kt index 817c83789..f6f965147 100644 --- a/app/src/main/java/foundation/e/apps/di/network/InterceptorModule.kt +++ b/app/src/main/java/foundation/e/apps/di/network/InterceptorModule.kt @@ -25,7 +25,13 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import foundation.e.apps.BuildConfig import okhttp3.Interceptor +import okhttp3.Protocol +import okhttp3.Request +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.logging.HttpLoggingInterceptor +import timber.log.Timber +import java.io.IOException import java.util.Locale import javax.inject.Singleton @@ -36,6 +42,8 @@ class InterceptorModule { companion object { private const val HEADER_USER_AGENT = "User-Agent" private const val HEADER_ACCEPT_LANGUAGE = "Accept-Language" + private const val ERROR_RESPONSE_CODE = 999 + private const val ERROR_RESPONSE_MESSAGE = "IOException occurred." } @Singleton @@ -51,12 +59,25 @@ class InterceptorModule { "Dalvik/2.1.0 (Linux; U; Android ${Build.VERSION.RELEASE};)") .header(HEADER_ACCEPT_LANGUAGE, Locale.getDefault().language) - val response = chain.proceed(builder.build()) + val response = try { + chain.proceed(builder.build()) + } catch (ioException: IOException) { + Timber.e(ioException) + return@Interceptor createResponseForIOException(chain.request()) + } return@Interceptor response } } + private fun createResponseForIOException(request: Request) = Response.Builder() + .request(request) + .protocol(Protocol.HTTP_1_1) + .code(ERROR_RESPONSE_CODE) + .message(ERROR_RESPONSE_MESSAGE) + .body(ERROR_RESPONSE_MESSAGE.toResponseBody()) + .build() + @Provides @Singleton fun provideLoggingInterceptor(): HttpLoggingInterceptor { -- GitLab From 13736fe618650b671446cac38fd1f9dee025b45f Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Tue, 1 Oct 2024 13:12:59 +0600 Subject: [PATCH 2/3] test: add unit tests for InterceptorModule --- .../e/apps/di/network/InterceptorModule.kt | 9 +- .../apps/di/network/InterceptorModuleTest.kt | 112 ++++++++++++++++++ 2 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 app/src/test/java/foundation/e/apps/di/network/InterceptorModuleTest.kt diff --git a/app/src/main/java/foundation/e/apps/di/network/InterceptorModule.kt b/app/src/main/java/foundation/e/apps/di/network/InterceptorModule.kt index f6f965147..05a30b69d 100644 --- a/app/src/main/java/foundation/e/apps/di/network/InterceptorModule.kt +++ b/app/src/main/java/foundation/e/apps/di/network/InterceptorModule.kt @@ -42,8 +42,10 @@ class InterceptorModule { companion object { private const val HEADER_USER_AGENT = "User-Agent" private const val HEADER_ACCEPT_LANGUAGE = "Accept-Language" - private const val ERROR_RESPONSE_CODE = 999 - private const val ERROR_RESPONSE_MESSAGE = "IOException occurred." + const val ERROR_RESPONSE_CODE = 999 + const val ERROR_RESPONSE_MESSAGE = "IOException occurred." + val HEADER_USER_AGENT_VALUE = + "Dalvik/2.1.0 (Linux; U; Android ${Build.VERSION.RELEASE};)" } @Singleton @@ -56,7 +58,8 @@ class InterceptorModule { .newBuilder() .header( HEADER_USER_AGENT, - "Dalvik/2.1.0 (Linux; U; Android ${Build.VERSION.RELEASE};)") + HEADER_USER_AGENT_VALUE + ) .header(HEADER_ACCEPT_LANGUAGE, Locale.getDefault().language) val response = try { diff --git a/app/src/test/java/foundation/e/apps/di/network/InterceptorModuleTest.kt b/app/src/test/java/foundation/e/apps/di/network/InterceptorModuleTest.kt new file mode 100644 index 000000000..cf7afdbe8 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/di/network/InterceptorModuleTest.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2024 MURENA SAS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package foundation.e.apps.di.network + +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertNotNull +import junit.framework.TestCase.assertTrue +import okhttp3.Interceptor +import okhttp3.Protocol +import okhttp3.Request +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.whenever +import java.io.IOException +import java.util.Locale + +class InterceptorModuleTest { + + private lateinit var module: InterceptorModule + private lateinit var chain: Interceptor.Chain + private lateinit var request: Request + private lateinit var response: Response + + @Before + fun setUp() { + module = InterceptorModule() + + // Mocking the chain and request + chain = mock() + request = Request.Builder() + .url("http://test.com") + .build() + + // Mocking the response + response = Response.Builder() + .request(request) + .protocol(Protocol.HTTP_1_1) + .code(200) + .message("OK") + .body("Response body".toResponseBody()) + .build() + + whenever(chain.request()).thenReturn(request) + } + + @Test + fun `provideInterceptor should add correct headers to request`() { + // Mock the chain's proceed method to return a valid response + whenever(chain.proceed(any())).thenReturn(response) + + val interceptor = module.provideInterceptor() + + // Intercept the request + val interceptedResponse = interceptor.intercept(chain) + + // Verify the headers + val requestCaptor = argumentCaptor() + verify(chain).proceed(requestCaptor.capture()) + val capturedRequest = requestCaptor.firstValue + + assertEquals( + InterceptorModule.HEADER_USER_AGENT_VALUE, + capturedRequest.header("User-Agent") + ) + assertEquals(Locale.getDefault().language, capturedRequest.header("Accept-Language")) + assertEquals(response, interceptedResponse) + } + + @Test + fun `provideInterceptor should return custom response on IOException`() { + // Mock IOException when proceeding with chain + whenever(chain.proceed(any())).thenThrow(IOException()) + + val interceptor = module.provideInterceptor() + + // Intercept the request, should catch IOException and return custom response + val interceptedResponse = interceptor.intercept(chain) + + assertNotNull(interceptedResponse) + assertEquals(InterceptorModule.ERROR_RESPONSE_CODE, interceptedResponse.code) + assertEquals( + InterceptorModule.ERROR_RESPONSE_MESSAGE, + interceptedResponse.message + ) + assertTrue( + interceptedResponse.body?.string() + ?.contains(InterceptorModule.ERROR_RESPONSE_MESSAGE) == true + ) + } +} -- GitLab From fe3cebe4c7f07d1f482bd3b106cde46dc7c23e12 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Tue, 1 Oct 2024 15:24:37 +0600 Subject: [PATCH 3/3] doc: update code comment --- .../main/java/foundation/e/apps/di/network/InterceptorModule.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/foundation/e/apps/di/network/InterceptorModule.kt b/app/src/main/java/foundation/e/apps/di/network/InterceptorModule.kt index 05a30b69d..c42a9f0fb 100644 --- a/app/src/main/java/foundation/e/apps/di/network/InterceptorModule.kt +++ b/app/src/main/java/foundation/e/apps/di/network/InterceptorModule.kt @@ -42,7 +42,7 @@ class InterceptorModule { companion object { private const val HEADER_USER_AGENT = "User-Agent" private const val HEADER_ACCEPT_LANGUAGE = "Accept-Language" - const val ERROR_RESPONSE_CODE = 999 + const val ERROR_RESPONSE_CODE = 999 // Arbitrary value, not to mix with HTTP status code const val ERROR_RESPONSE_MESSAGE = "IOException occurred." val HEADER_USER_AGENT_VALUE = "Dalvik/2.1.0 (Linux; U; Android ${Build.VERSION.RELEASE};)" -- GitLab