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 817c837893cb29b2ee25df757b22e3c46015de8c..c42a9f0fbd75f3e7905f7fb0a5a0d42e4b2871c6 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,10 @@ 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 // 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};)" } @Singleton @@ -48,15 +58,29 @@ 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 = 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 { 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 0000000000000000000000000000000000000000..cf7afdbe8d7aa14ddfb3454aae0ae3b9d7a6d2ac --- /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 + ) + } +}