From f4dbe685270b56cccddfc16793c63532a854f05a Mon Sep 17 00:00:00 2001 From: Brian Yencho Date: Tue, 24 Oct 2023 09:49:22 -0500 Subject: [PATCH] BIT-411: Add NetworkConfigRepository (#150) --- .../x8bit/bitwarden/BitwardenApplication.kt | 9 +++- .../auth/repository/AuthRepositoryImpl.kt | 11 ---- .../auth/repository/di/RepositoryModule.kt | 3 -- .../repository/NetworkConfigRepository.kt | 6 +++ .../repository/NetworkConfigRepositoryImpl.kt | 34 +++++++++++++ .../repository/di/RepositoryModule.kt | 32 ++++++++++++ .../auth/repository/AuthRepositoryTest.kt | 8 --- .../repository/NetworkConfigRepositoryTest.kt | 51 +++++++++++++++++++ 8 files changed, 131 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/platform/repository/NetworkConfigRepository.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/platform/repository/NetworkConfigRepositoryImpl.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/platform/repository/di/RepositoryModule.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/platform/repository/NetworkConfigRepositoryTest.kt diff --git a/app/src/main/java/com/x8bit/bitwarden/BitwardenApplication.kt b/app/src/main/java/com/x8bit/bitwarden/BitwardenApplication.kt index 4dc93bd8d..5aeb83da3 100644 --- a/app/src/main/java/com/x8bit/bitwarden/BitwardenApplication.kt +++ b/app/src/main/java/com/x8bit/bitwarden/BitwardenApplication.kt @@ -1,10 +1,17 @@ package com.x8bit.bitwarden import android.app.Application +import com.x8bit.bitwarden.data.platform.repository.NetworkConfigRepository import dagger.hilt.android.HiltAndroidApp +import javax.inject.Inject /** * Custom application class. */ @HiltAndroidApp -class BitwardenApplication : Application() +class BitwardenApplication : Application() { + // Inject classes here that must be triggered on startup but are not otherwise consumed by + // other callers. + @Inject + lateinit var networkConfigRepository: NetworkConfigRepository +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt index 86e932940..55df8bdc5 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt @@ -17,7 +17,6 @@ import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult import com.x8bit.bitwarden.data.auth.repository.util.toUserState import com.x8bit.bitwarden.data.auth.util.toSdkParams -import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor import com.x8bit.bitwarden.data.platform.util.flatMap import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -27,7 +26,6 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import javax.inject.Inject import javax.inject.Singleton @@ -43,7 +41,6 @@ class AuthRepositoryImpl @Inject constructor( private val identityService: IdentityService, private val authSdkSource: AuthSdkSource, private val authDiskSource: AuthDiskSource, - private val authTokenInterceptor: AuthTokenInterceptor, dispatcher: CoroutineDispatcher, ) : AuthRepository { private val scope = CoroutineScope(dispatcher) @@ -63,14 +60,6 @@ class AuthRepositoryImpl @Inject constructor( } ?: AuthState.Unauthenticated } - .onEach { - // TODO: Create intermediate class for providing auth token to interceptor (BIT-411) - authTokenInterceptor.authToken = when (it) { - is AuthState.Authenticated -> it.accessToken - AuthState.Unauthenticated -> null - AuthState.Uninitialized -> null - } - } .stateIn( scope = scope, started = SharingStarted.Eagerly, diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/di/RepositoryModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/di/RepositoryModule.kt index 4e9dcdfb4..fc47717cb 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/di/RepositoryModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/di/RepositoryModule.kt @@ -6,7 +6,6 @@ import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityService import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.AuthRepositoryImpl -import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -28,13 +27,11 @@ object RepositoryModule { identityService: IdentityService, authSdkSource: AuthSdkSource, authDiskSource: AuthDiskSource, - authTokenInterceptor: AuthTokenInterceptor, ): AuthRepository = AuthRepositoryImpl( accountsService = accountsService, identityService = identityService, authSdkSource = authSdkSource, authDiskSource = authDiskSource, - authTokenInterceptor = authTokenInterceptor, dispatcher = Dispatchers.IO, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/NetworkConfigRepository.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/NetworkConfigRepository.kt new file mode 100644 index 000000000..8f679a5e0 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/NetworkConfigRepository.kt @@ -0,0 +1,6 @@ +package com.x8bit.bitwarden.data.platform.repository + +/** + * Responsible for managing the active configuration of the network layer. + */ +interface NetworkConfigRepository diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/NetworkConfigRepositoryImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/NetworkConfigRepositoryImpl.kt new file mode 100644 index 000000000..c6ab1c582 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/NetworkConfigRepositoryImpl.kt @@ -0,0 +1,34 @@ +package com.x8bit.bitwarden.data.platform.repository + +import com.x8bit.bitwarden.data.auth.repository.AuthRepository +import com.x8bit.bitwarden.data.auth.repository.model.AuthState +import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +/** + * Primary implementation of [NetworkConfigRepository]. + */ +class NetworkConfigRepositoryImpl( + private val authRepository: AuthRepository, + private val authTokenInterceptor: AuthTokenInterceptor, + dispatcher: CoroutineDispatcher, +) : NetworkConfigRepository { + + private val scope = CoroutineScope(dispatcher) + + init { + authRepository + .authStateFlow + .onEach { authState -> + authTokenInterceptor.authToken = when (authState) { + is AuthState.Authenticated -> authState.accessToken + is AuthState.Unauthenticated -> null + is AuthState.Uninitialized -> null + } + } + .launchIn(scope) + } +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/di/RepositoryModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/di/RepositoryModule.kt new file mode 100644 index 000000000..3b2a781ba --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/di/RepositoryModule.kt @@ -0,0 +1,32 @@ +package com.x8bit.bitwarden.data.platform.repository.di + +import com.x8bit.bitwarden.data.auth.repository.AuthRepository +import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor +import com.x8bit.bitwarden.data.platform.repository.NetworkConfigRepository +import com.x8bit.bitwarden.data.platform.repository.NetworkConfigRepositoryImpl +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.Dispatchers +import javax.inject.Singleton + +/** + * Provides repositories in the auth package. + */ +@Module +@InstallIn(SingletonComponent::class) +object RepositoryModule { + + @Provides + @Singleton + fun provideNetworkConfigRepository( + authRepository: AuthRepository, + authTokenInterceptor: AuthTokenInterceptor, + ): NetworkConfigRepository = + NetworkConfigRepositoryImpl( + authRepository = authRepository, + authTokenInterceptor = authTokenInterceptor, + dispatcher = Dispatchers.IO, + ) +} diff --git a/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt index 149aec944..b6f00d479 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt @@ -22,7 +22,6 @@ import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult import com.x8bit.bitwarden.data.auth.repository.util.toUserState import com.x8bit.bitwarden.data.auth.util.toSdkParams -import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor import io.mockk.clearMocks import io.mockk.coEvery import io.mockk.coVerify @@ -46,7 +45,6 @@ class AuthRepositoryTest { private val accountsService: AccountsService = mockk() private val identityService: IdentityService = mockk() - private val authInterceptor = AuthTokenInterceptor() private val fakeAuthDiskSource = FakeAuthDiskSource() private val authSdkSource = mockk { coEvery { @@ -80,7 +78,6 @@ class AuthRepositoryTest { identityService = identityService, authSdkSource = authSdkSource, authDiskSource = fakeAuthDiskSource, - authTokenInterceptor = authInterceptor, dispatcher = UnconfinedTestDispatcher(), ) @@ -199,7 +196,6 @@ class AuthRepositoryTest { val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null) assertEquals(LoginResult.Success, result) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) - assertEquals(ACCESS_TOKEN, authInterceptor.authToken) coVerify { accountsService.preLogin(email = EMAIL) } coVerify { identityService.getToken( @@ -404,7 +400,6 @@ class AuthRepositoryTest { repository.login(email = EMAIL, password = PASSWORD, captchaToken = null) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) - assertEquals(ACCESS_TOKEN, authInterceptor.authToken) assertEquals(SINGLE_USER_STATE_1, fakeAuthDiskSource.userState) // Then call logout: @@ -414,7 +409,6 @@ class AuthRepositoryTest { repository.logout() assertEquals(AuthState.Unauthenticated, awaitItem()) - assertNull(authInterceptor.authToken) assertNull(fakeAuthDiskSource.userState) } } @@ -443,7 +437,6 @@ class AuthRepositoryTest { repository.login(email = EMAIL, password = PASSWORD, captchaToken = null) assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value) - assertEquals(ACCESS_TOKEN, authInterceptor.authToken) assertEquals(MULTI_USER_STATE, fakeAuthDiskSource.userState) // Then call logout: @@ -453,7 +446,6 @@ class AuthRepositoryTest { repository.logout() assertEquals(AuthState.Authenticated(ACCESS_TOKEN_2), awaitItem()) - assertEquals(ACCESS_TOKEN_2, authInterceptor.authToken) assertEquals(SINGLE_USER_STATE_2, fakeAuthDiskSource.userState) } } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/repository/NetworkConfigRepositoryTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/repository/NetworkConfigRepositoryTest.kt new file mode 100644 index 000000000..c48ecd06d --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/platform/repository/NetworkConfigRepositoryTest.kt @@ -0,0 +1,51 @@ +package com.x8bit.bitwarden.data.platform.repository + +import com.x8bit.bitwarden.data.auth.repository.AuthRepository +import com.x8bit.bitwarden.data.auth.repository.model.AuthState +import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class NetworkConfigRepositoryTest { + private val mutableAuthStateFlow = MutableStateFlow(AuthState.Uninitialized) + + private val authRepository: AuthRepository = mockk() { + every { authStateFlow } returns mutableAuthStateFlow + } + + private val authTokenInterceptor = AuthTokenInterceptor() + + private lateinit var networkConfigRepository: NetworkConfigRepository + + @BeforeEach + fun setUp() { + networkConfigRepository = NetworkConfigRepositoryImpl( + authRepository = authRepository, + authTokenInterceptor = authTokenInterceptor, + dispatcher = UnconfinedTestDispatcher(), + ) + } + + @Test + fun `changes in the AuthState should update the AuthTokenInterceptor`() { + mutableAuthStateFlow.value = AuthState.Uninitialized + assertNull(authTokenInterceptor.authToken) + + mutableAuthStateFlow.value = AuthState.Authenticated(accessToken = "accessToken") + assertEquals( + "accessToken", + authTokenInterceptor.authToken, + ) + + mutableAuthStateFlow.value = AuthState.Unauthenticated + assertNull(authTokenInterceptor.authToken) + } +}