BIT-411: Add NetworkConfigRepository (#150)

This commit is contained in:
Brian Yencho 2023-10-24 09:49:22 -05:00 committed by Álison Fernandes
parent dae7091fde
commit f4dbe68527
8 changed files with 131 additions and 23 deletions

View file

@ -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
}

View file

@ -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,

View file

@ -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,
)
}

View file

@ -0,0 +1,6 @@
package com.x8bit.bitwarden.data.platform.repository
/**
* Responsible for managing the active configuration of the network layer.
*/
interface NetworkConfigRepository

View file

@ -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)
}
}

View file

@ -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,
)
}

View file

@ -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<AuthSdkSource> {
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)
}
}

View file

@ -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>(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)
}
}