mirror of
https://github.com/bitwarden/android.git
synced 2024-11-21 17:05:44 +03:00
BIT-411: Add NetworkConfigRepository (#150)
This commit is contained in:
parent
dae7091fde
commit
f4dbe68527
8 changed files with 131 additions and 23 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.platform.repository
|
||||
|
||||
/**
|
||||
* Responsible for managing the active configuration of the network layer.
|
||||
*/
|
||||
interface NetworkConfigRepository
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue