mirror of
https://github.com/bitwarden/android.git
synced 2026-04-27 19:38:42 -05:00
[BWA-58] Define feature flag repo (#176)
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
package com.bitwarden.authenticator.data.platform.repository
|
||||
|
||||
import com.bitwarden.authenticator.data.platform.datasource.disk.model.FeatureFlagsConfiguration
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
* Provides an API for observing the server config state.
|
||||
*/
|
||||
interface FeatureFlagRepository {
|
||||
|
||||
/**
|
||||
* Emits updates that track [FeatureFlagsConfiguration].
|
||||
*/
|
||||
val featureFlagConfigStateFlow: StateFlow<FeatureFlagsConfiguration?>
|
||||
|
||||
/**
|
||||
* Gets the state [FeatureFlagsConfiguration].
|
||||
*/
|
||||
suspend fun getFeatureFlagsConfiguration(): FeatureFlagsConfiguration
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.bitwarden.authenticator.data.platform.repository
|
||||
|
||||
import com.bitwarden.authenticator.data.platform.datasource.disk.FeatureFlagDiskSource
|
||||
import com.bitwarden.authenticator.data.platform.datasource.disk.model.FeatureFlagsConfiguration
|
||||
import com.bitwarden.authenticator.data.platform.manager.DispatcherManager
|
||||
import com.bitwarden.authenticator.data.platform.manager.model.LocalFeatureFlag
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
|
||||
/**
|
||||
* Primary implementation of [FeatureFlagRepositoryImpl].
|
||||
*/
|
||||
class FeatureFlagRepositoryImpl(
|
||||
private val featureFlagDiskSource: FeatureFlagDiskSource,
|
||||
dispatcherManager: DispatcherManager,
|
||||
) : FeatureFlagRepository {
|
||||
|
||||
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
|
||||
|
||||
override val featureFlagConfigStateFlow: StateFlow<FeatureFlagsConfiguration?>
|
||||
get() = featureFlagDiskSource
|
||||
.featureFlagsConfigurationFlow
|
||||
.stateIn(
|
||||
scope = unconfinedScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = featureFlagDiskSource.featureFlagsConfiguration,
|
||||
)
|
||||
|
||||
override suspend fun getFeatureFlagsConfiguration() =
|
||||
featureFlagDiskSource.featureFlagsConfiguration
|
||||
?: initLocalFeatureFlagsConfiguration()
|
||||
|
||||
private fun initLocalFeatureFlagsConfiguration(): FeatureFlagsConfiguration {
|
||||
val config = FeatureFlagsConfiguration(
|
||||
mapOf(
|
||||
LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive(
|
||||
LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue
|
||||
)
|
||||
)
|
||||
)
|
||||
featureFlagDiskSource.featureFlagsConfiguration = config
|
||||
return config
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,12 @@ package com.bitwarden.authenticator.data.platform.repository.di
|
||||
|
||||
import com.bitwarden.authenticator.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.bitwarden.authenticator.data.authenticator.datasource.sdk.AuthenticatorSdkSource
|
||||
import com.bitwarden.authenticator.data.platform.datasource.disk.FeatureFlagDiskSource
|
||||
import com.bitwarden.authenticator.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.bitwarden.authenticator.data.platform.manager.BiometricsEncryptionManager
|
||||
import com.bitwarden.authenticator.data.platform.manager.DispatcherManager
|
||||
import com.bitwarden.authenticator.data.platform.repository.FeatureFlagRepository
|
||||
import com.bitwarden.authenticator.data.platform.repository.FeatureFlagRepositoryImpl
|
||||
import com.bitwarden.authenticator.data.platform.repository.SettingsRepository
|
||||
import com.bitwarden.authenticator.data.platform.repository.SettingsRepositoryImpl
|
||||
import dagger.Module
|
||||
@@ -36,4 +39,15 @@ object PlatformRepositoryModule {
|
||||
biometricsEncryptionManager = biometricsEncryptionManager,
|
||||
authenticatorSdkSource = authenticatorSdkSource,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFeatureFlagRepo(
|
||||
featureFlagDiskSource: FeatureFlagDiskSource,
|
||||
dispatcherManager: DispatcherManager,
|
||||
): FeatureFlagRepository =
|
||||
FeatureFlagRepositoryImpl(
|
||||
featureFlagDiskSource = featureFlagDiskSource,
|
||||
dispatcherManager = dispatcherManager,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.bitwarden.authenticator.data.platform.base
|
||||
|
||||
import com.bitwarden.authenticator.data.platform.manager.DispatcherManager
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.MainCoroutineDispatcher
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.setMain
|
||||
|
||||
/**
|
||||
* A faked implementation of [DispatcherManager] that uses [UnconfinedTestDispatcher].
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class FakeDispatcherManager(
|
||||
override val default: CoroutineDispatcher = UnconfinedTestDispatcher(),
|
||||
override val io: CoroutineDispatcher = UnconfinedTestDispatcher(),
|
||||
override val unconfined: CoroutineDispatcher = UnconfinedTestDispatcher(),
|
||||
) : DispatcherManager {
|
||||
override val main: MainCoroutineDispatcher = Dispatchers.Main
|
||||
|
||||
/**
|
||||
* Updates the main dispatcher to use the provided [dispatcher]. Used in conjunction with
|
||||
* [resetMain].
|
||||
*/
|
||||
fun setMain(dispatcher: CoroutineDispatcher) {
|
||||
Dispatchers.setMain(dispatcher)
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the main dispatcher to it's default state. Used in conjunction with [setMain].
|
||||
*/
|
||||
fun resetMain() {
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.bitwarden.authenticator.data.platform.datasource.disk.util
|
||||
|
||||
import com.bitwarden.authenticator.data.platform.datasource.disk.FeatureFlagDiskSource
|
||||
import com.bitwarden.authenticator.data.platform.datasource.disk.model.FeatureFlagsConfiguration
|
||||
import com.bitwarden.authenticator.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
|
||||
/**
|
||||
* A faked implementation of [FeatureFlagDiskSource] for testing.
|
||||
*/
|
||||
class FakeFeatureFlagDiskSource : FeatureFlagDiskSource {
|
||||
|
||||
private var configuration: FeatureFlagsConfiguration? = null
|
||||
private val mutableConfigurationFlow =
|
||||
bufferedMutableSharedFlow<FeatureFlagsConfiguration?>(replay = 1)
|
||||
|
||||
override var featureFlagsConfiguration: FeatureFlagsConfiguration?
|
||||
get() = configuration
|
||||
set(value) {
|
||||
configuration = value
|
||||
mutableConfigurationFlow.tryEmit(value)
|
||||
}
|
||||
override val featureFlagsConfigurationFlow: Flow<FeatureFlagsConfiguration?>
|
||||
get() = mutableConfigurationFlow
|
||||
.onSubscription { emit(configuration) }
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.bitwarden.authenticator.data.platform.repository
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.authenticator.data.platform.base.FakeDispatcherManager
|
||||
import com.bitwarden.authenticator.data.platform.datasource.disk.model.FeatureFlagsConfiguration
|
||||
import com.bitwarden.authenticator.data.platform.datasource.disk.util.FakeFeatureFlagDiskSource
|
||||
import com.bitwarden.authenticator.data.platform.manager.model.LocalFeatureFlag
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
|
||||
class FeatureFlagRepositoryTest {
|
||||
|
||||
private val fakeFeatureFlagDiskSource = FakeFeatureFlagDiskSource()
|
||||
private val featureFlagRepo = FeatureFlagRepositoryImpl(
|
||||
featureFlagDiskSource = fakeFeatureFlagDiskSource,
|
||||
dispatcherManager = FakeDispatcherManager(),
|
||||
)
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `getFeatureFlagsConfiguration should init configuration with local flags when there is none in state`() =
|
||||
runTest {
|
||||
assertNull(fakeFeatureFlagDiskSource.featureFlagsConfiguration)
|
||||
|
||||
featureFlagRepo.getFeatureFlagsConfiguration()
|
||||
|
||||
assertEquals(
|
||||
FEATURE_FLAGS_CONFIG,
|
||||
fakeFeatureFlagDiskSource.featureFlagsConfiguration
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `featureFlagsConfigurationFlow should react to feature flag configuration changes`() =
|
||||
runTest {
|
||||
featureFlagRepo.getFeatureFlagsConfiguration()
|
||||
|
||||
featureFlagRepo.featureFlagConfigStateFlow.test {
|
||||
assertEquals(fakeFeatureFlagDiskSource.featureFlagsConfiguration, awaitItem())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val FEATURE_FLAGS_CONFIG =
|
||||
FeatureFlagsConfiguration(
|
||||
featureFlags = mapOf(
|
||||
LocalFeatureFlag.BitwardenAuthenticationEnabled.name to
|
||||
JsonPrimitive(LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue),
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user