mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 07:05:35 +03:00
BIT-752: Add Environment/EnvironmentRepository/EnvironmentDiskSource (#151)
This commit is contained in:
parent
f4dbe68527
commit
e4ab70a106
15 changed files with 575 additions and 24 deletions
|
@ -1,16 +1,15 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.disk
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import androidx.core.content.edit
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource.Companion.BASE_KEY
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
private const val BASE_KEY = "bwPreferencesStorage"
|
||||
private const val REMEMBERED_EMAIL_ADDRESS_KEY = "$BASE_KEY:rememberedEmail"
|
||||
private const val STATE_KEY = "$BASE_KEY:state"
|
||||
|
||||
|
@ -18,9 +17,10 @@ private const val STATE_KEY = "$BASE_KEY:state"
|
|||
* Primary implementation of [AuthDiskSource].
|
||||
*/
|
||||
class AuthDiskSourceImpl(
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
sharedPreferences: SharedPreferences,
|
||||
private val json: Json,
|
||||
) : AuthDiskSource {
|
||||
) : BaseDiskSource(sharedPreferences = sharedPreferences),
|
||||
AuthDiskSource {
|
||||
override var rememberedEmailAddress: String?
|
||||
get() = getString(key = REMEMBERED_EMAIL_ADDRESS_KEY)
|
||||
set(value) {
|
||||
|
@ -48,25 +48,12 @@ class AuthDiskSourceImpl(
|
|||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
|
||||
private val onSharedPreferenceChangeListener =
|
||||
OnSharedPreferenceChangeListener { _, key ->
|
||||
when (key) {
|
||||
STATE_KEY -> mutableUserStateFlow.tryEmit(userState)
|
||||
}
|
||||
override fun onSharedPreferenceChanged(
|
||||
sharedPreferences: SharedPreferences?,
|
||||
key: String?,
|
||||
) {
|
||||
when (key) {
|
||||
STATE_KEY -> mutableUserStateFlow.tryEmit(userState)
|
||||
}
|
||||
|
||||
init {
|
||||
sharedPreferences
|
||||
.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener)
|
||||
}
|
||||
|
||||
private fun getString(
|
||||
key: String,
|
||||
default: String? = null,
|
||||
): String? = sharedPreferences.getString(key, default)
|
||||
|
||||
private fun putString(
|
||||
key: String,
|
||||
value: String?,
|
||||
): Unit = sharedPreferences.edit { putString(key, value) }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import androidx.core.content.edit
|
||||
|
||||
/**
|
||||
* Base class for simplifying interactions with [SharedPreferences].
|
||||
*/
|
||||
abstract class BaseDiskSource(
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
) : OnSharedPreferenceChangeListener {
|
||||
|
||||
init {
|
||||
@Suppress("LeakingThis")
|
||||
sharedPreferences
|
||||
.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
protected fun getString(
|
||||
key: String,
|
||||
default: String? = null,
|
||||
): String? = sharedPreferences.getString(key, default)
|
||||
|
||||
protected fun putString(
|
||||
key: String,
|
||||
value: String?,
|
||||
): Unit = sharedPreferences.edit { putString(key, value) }
|
||||
|
||||
companion object {
|
||||
const val BASE_KEY: String = "bwPreferencesStorage"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Primary access point for general environment-related disk information.
|
||||
*/
|
||||
interface EnvironmentDiskSource {
|
||||
/**
|
||||
* The currently persisted [EnvironmentUrlDataJson] (or `null` if not set).
|
||||
*/
|
||||
var preAuthEnvironmentUrlData: EnvironmentUrlDataJson?
|
||||
|
||||
/**
|
||||
* Emits updates that track [preAuthEnvironmentUrlData]. This will replay the last known value,
|
||||
* if any.
|
||||
*/
|
||||
val preAuthEnvironmentUrlDataFlow: Flow<EnvironmentUrlDataJson?>
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource.Companion.BASE_KEY
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
private const val PRE_AUTH_URLS_KEY = "$BASE_KEY:preAuthEnvironmentUrls"
|
||||
|
||||
/**
|
||||
* Primary implementation of [EnvironmentDiskSource].
|
||||
*/
|
||||
class EnvironmentDiskSourceImpl(
|
||||
sharedPreferences: SharedPreferences,
|
||||
private val json: Json,
|
||||
) : BaseDiskSource(sharedPreferences = sharedPreferences),
|
||||
EnvironmentDiskSource {
|
||||
override var preAuthEnvironmentUrlData: EnvironmentUrlDataJson?
|
||||
get() = getString(key = PRE_AUTH_URLS_KEY)?.let { json.decodeFromString(it) }
|
||||
set(value) {
|
||||
putString(
|
||||
key = PRE_AUTH_URLS_KEY,
|
||||
value = value?.let { json.encodeToString(value) },
|
||||
)
|
||||
}
|
||||
|
||||
override val preAuthEnvironmentUrlDataFlow: Flow<EnvironmentUrlDataJson?>
|
||||
get() = mutableEnvironmentUrlDataFlow
|
||||
.onSubscription { emit(preAuthEnvironmentUrlData) }
|
||||
|
||||
private val mutableEnvironmentUrlDataFlow = MutableSharedFlow<EnvironmentUrlDataJson?>(
|
||||
replay = 1,
|
||||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
|
||||
override fun onSharedPreferenceChanged(
|
||||
sharedPreferences: SharedPreferences?,
|
||||
key: String?,
|
||||
) {
|
||||
when (key) {
|
||||
PRE_AUTH_URLS_KEY -> mutableEnvironmentUrlDataFlow.tryEmit(preAuthEnvironmentUrlData)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.disk.di
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSourceImpl
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.serialization.json.Json
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Provides persistence-related dependencies in the platform package.
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object DiskModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideEnvironmentDiskSource(
|
||||
sharedPreferences: SharedPreferences,
|
||||
json: Json,
|
||||
): EnvironmentDiskSource =
|
||||
EnvironmentDiskSourceImpl(
|
||||
sharedPreferences = sharedPreferences,
|
||||
json = json,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package com.x8bit.bitwarden.data.platform.repository
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
* Provides an API for observing and modifying environment state.
|
||||
*/
|
||||
interface EnvironmentRepository {
|
||||
/**
|
||||
* The currently set environment.
|
||||
*/
|
||||
var environment: Environment
|
||||
|
||||
/**
|
||||
* Emits updates that track [environment].
|
||||
*/
|
||||
val environmentStateFlow: StateFlow<Environment>
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package com.x8bit.bitwarden.data.platform.repository
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.toEnvironmentUrls
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
/**
|
||||
* Primary implementation of [EnvironmentRepository].
|
||||
*/
|
||||
class EnvironmentRepositoryImpl(
|
||||
private val environmentDiskSource: EnvironmentDiskSource,
|
||||
private val dispatcher: CoroutineDispatcher,
|
||||
) : EnvironmentRepository {
|
||||
|
||||
private val scope = CoroutineScope(dispatcher)
|
||||
|
||||
override var environment: Environment
|
||||
get() = environmentDiskSource
|
||||
.preAuthEnvironmentUrlData
|
||||
.toEnvironmentUrlsOrDefault()
|
||||
set(value) {
|
||||
environmentDiskSource.preAuthEnvironmentUrlData = value.environmentUrlData
|
||||
}
|
||||
|
||||
override val environmentStateFlow: StateFlow<Environment>
|
||||
get() = environmentDiskSource
|
||||
.preAuthEnvironmentUrlDataFlow
|
||||
.map { it.toEnvironmentUrlsOrDefault() }
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = Environment.Us,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a nullable [EnvironmentUrlDataJson] to an [Environment], where `null` values default to
|
||||
* the US environment.
|
||||
*/
|
||||
private fun EnvironmentUrlDataJson?.toEnvironmentUrlsOrDefault(): Environment =
|
||||
this?.toEnvironmentUrls() ?: Environment.Us
|
|
@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.onEach
|
|||
class NetworkConfigRepositoryImpl(
|
||||
private val authRepository: AuthRepository,
|
||||
private val authTokenInterceptor: AuthTokenInterceptor,
|
||||
private val environmentRepository: EnvironmentRepository,
|
||||
dispatcher: CoroutineDispatcher,
|
||||
) : NetworkConfigRepository {
|
||||
|
||||
|
@ -30,5 +31,12 @@ class NetworkConfigRepositoryImpl(
|
|||
}
|
||||
}
|
||||
.launchIn(scope)
|
||||
|
||||
environmentRepository
|
||||
.environmentStateFlow
|
||||
.onEach { environment ->
|
||||
// TODO: Update base URL interceptors (BIT-725)
|
||||
}
|
||||
.launchIn(scope)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package com.x8bit.bitwarden.data.platform.repository.di
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepositoryImpl
|
||||
import com.x8bit.bitwarden.data.platform.repository.NetworkConfigRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.NetworkConfigRepositoryImpl
|
||||
import dagger.Module
|
||||
|
@ -18,15 +21,27 @@ import javax.inject.Singleton
|
|||
@InstallIn(SingletonComponent::class)
|
||||
object RepositoryModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideEnvironmentRepository(
|
||||
environmentDiskSource: EnvironmentDiskSource,
|
||||
): EnvironmentRepository =
|
||||
EnvironmentRepositoryImpl(
|
||||
environmentDiskSource = environmentDiskSource,
|
||||
dispatcher = Dispatchers.IO,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNetworkConfigRepository(
|
||||
authRepository: AuthRepository,
|
||||
authTokenInterceptor: AuthTokenInterceptor,
|
||||
environmentRepository: EnvironmentRepository,
|
||||
): NetworkConfigRepository =
|
||||
NetworkConfigRepositoryImpl(
|
||||
authRepository = authRepository,
|
||||
authTokenInterceptor = authTokenInterceptor,
|
||||
environmentRepository = environmentRepository,
|
||||
dispatcher = Dispatchers.IO,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
package com.x8bit.bitwarden.data.platform.repository.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.parcelize.RawValue
|
||||
|
||||
/**
|
||||
* A higher-level wrapper around [EnvironmentUrlDataJson] that provides type-safety, enumerability,
|
||||
* and human-readable labels.
|
||||
*/
|
||||
sealed class Environment : Parcelable {
|
||||
/**
|
||||
* The [Type] of the environment.
|
||||
*/
|
||||
abstract val type: Type
|
||||
|
||||
/**
|
||||
* The raw [environmentUrlData] that contains specific base URLs for each relevant domain.
|
||||
*/
|
||||
abstract val environmentUrlData: EnvironmentUrlDataJson
|
||||
|
||||
/**
|
||||
* Helper for a returning a human-readable label from a [Type].
|
||||
*/
|
||||
val label: Text get() = type.label
|
||||
|
||||
/**
|
||||
* The default US environment.
|
||||
*/
|
||||
@Parcelize
|
||||
data object Us : Environment() {
|
||||
override val type: Type get() = Type.US
|
||||
override val environmentUrlData: EnvironmentUrlDataJson
|
||||
get() = EnvironmentUrlDataJson.DEFAULT_US
|
||||
}
|
||||
|
||||
/**
|
||||
* The default EU environment.
|
||||
*/
|
||||
@Parcelize
|
||||
data object Eu : Environment() {
|
||||
override val type: Type get() = Type.EU
|
||||
override val environmentUrlData: EnvironmentUrlDataJson
|
||||
get() = EnvironmentUrlDataJson.DEFAULT_EU
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom self-hosted environment with a fully configurable [environmentUrlData].
|
||||
*/
|
||||
@Parcelize
|
||||
data class SelfHosted(
|
||||
override val environmentUrlData: @RawValue EnvironmentUrlDataJson,
|
||||
) : Environment() {
|
||||
override val type: Type get() = Type.SELF_HOSTED
|
||||
}
|
||||
|
||||
/**
|
||||
* A summary of the various types that can be enumerated over and which contains a
|
||||
* human-readable [label].
|
||||
*/
|
||||
enum class Type(val label: Text) {
|
||||
US(label = "bitwarden.com".asText()),
|
||||
EU(label = "bitwarden.eu".asText()),
|
||||
SELF_HOSTED(label = R.string.self_hosted.asText()),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.x8bit.bitwarden.data.platform.repository.util
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
|
||||
/**
|
||||
* Converts a raw [EnvironmentUrlDataJson] to an externally-consumable [Environment].
|
||||
*/
|
||||
fun EnvironmentUrlDataJson.toEnvironmentUrls(): Environment =
|
||||
when (this) {
|
||||
Environment.Us.environmentUrlData -> Environment.Us
|
||||
Environment.Eu.environmentUrlData -> Environment.Eu
|
||||
else -> Environment.SelfHosted(environmentUrlData = this)
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class EnvironmentDiskSourceTest {
|
||||
private val fakeSharedPreferences = FakeSharedPreferences()
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
private val json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
explicitNulls = false
|
||||
}
|
||||
|
||||
private val environmentDiskSource = EnvironmentDiskSourceImpl(
|
||||
sharedPreferences = fakeSharedPreferences,
|
||||
json = json,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `preAuthEnvironmentUrlData should pull from and update SharedPreferences`() {
|
||||
val environmentKey = "bwPreferencesStorage:preAuthEnvironmentUrls"
|
||||
|
||||
// Shared preferences and the repository start with the same value.
|
||||
assertNull(environmentDiskSource.preAuthEnvironmentUrlData)
|
||||
assertNull(fakeSharedPreferences.getString(environmentKey, null))
|
||||
|
||||
// Updating the repository updates shared preferences
|
||||
environmentDiskSource.preAuthEnvironmentUrlData = ENVIRONMENT_URL_DATA
|
||||
assertEquals(
|
||||
json.parseToJsonElement(
|
||||
ENVIRONMENT_URL_DATA_JSON,
|
||||
),
|
||||
json.parseToJsonElement(
|
||||
fakeSharedPreferences.getString(environmentKey, null)!!,
|
||||
),
|
||||
)
|
||||
|
||||
// Update SharedPreferences updates the repository
|
||||
fakeSharedPreferences.edit().putString(environmentKey, null).apply()
|
||||
assertNull(environmentDiskSource.preAuthEnvironmentUrlData)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `preAuthEnvironmentUrlDataFlow should react to changes in preAuthEnvironmentUrlData`() =
|
||||
runTest {
|
||||
environmentDiskSource.preAuthEnvironmentUrlDataFlow.test {
|
||||
// The initial values of the Flow and the property are in sync
|
||||
assertNull(environmentDiskSource.preAuthEnvironmentUrlData)
|
||||
assertNull(awaitItem())
|
||||
|
||||
// Updating the repository updates shared preferences
|
||||
environmentDiskSource.preAuthEnvironmentUrlData = ENVIRONMENT_URL_DATA
|
||||
assertEquals(ENVIRONMENT_URL_DATA, awaitItem())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val ENVIRONMENT_URL_DATA_JSON = """
|
||||
{
|
||||
"base": "base",
|
||||
"api": "api",
|
||||
"identity": "identity",
|
||||
"icon": "icon",
|
||||
"notifications": "notifications",
|
||||
"webVault": "webVault",
|
||||
"events": "events"
|
||||
}
|
||||
"""
|
||||
|
||||
private val ENVIRONMENT_URL_DATA = EnvironmentUrlDataJson(
|
||||
base = "base",
|
||||
api = "api",
|
||||
identity = "identity",
|
||||
icon = "icon",
|
||||
notifications = "notifications",
|
||||
webVault = "webVault",
|
||||
events = "events",
|
||||
)
|
|
@ -0,0 +1,123 @@
|
|||
package com.x8bit.bitwarden.data.platform.repository
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.toEnvironmentUrls
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
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
|
||||
|
||||
class EnvironmentRepositoryTest {
|
||||
private val fakeEnvironmentDiskSource = FakeEnvironmentDiskSource()
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val repository = EnvironmentRepositoryImpl(
|
||||
environmentDiskSource = fakeEnvironmentDiskSource,
|
||||
dispatcher = UnconfinedTestDispatcher(),
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
mockkStatic(ENVIRONMENT_EXTENSIONS_PATH)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
unmockkStatic(ENVIRONMENT_EXTENSIONS_PATH)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `environment should pull from and update EnvironmentDiskSource`() {
|
||||
val environmentUrlDataJson = mockk<EnvironmentUrlDataJson>()
|
||||
val environment = mockk<Environment>() {
|
||||
every { environmentUrlData } returns environmentUrlDataJson
|
||||
}
|
||||
every { environmentUrlDataJson.toEnvironmentUrls() } returns environment
|
||||
|
||||
// The repository exposes a non-null default value when the disk source is empty
|
||||
assertNull(fakeEnvironmentDiskSource.preAuthEnvironmentUrlData)
|
||||
assertEquals(
|
||||
Environment.Us,
|
||||
repository.environment,
|
||||
)
|
||||
|
||||
// Updating the repository updates the disk source
|
||||
repository.environment = environment
|
||||
assertEquals(
|
||||
environmentUrlDataJson,
|
||||
fakeEnvironmentDiskSource.preAuthEnvironmentUrlData,
|
||||
)
|
||||
|
||||
// Updating the disk source updates the repository
|
||||
fakeEnvironmentDiskSource.preAuthEnvironmentUrlData = null
|
||||
assertEquals(
|
||||
Environment.Us,
|
||||
repository.environment,
|
||||
)
|
||||
fakeEnvironmentDiskSource.preAuthEnvironmentUrlData = environmentUrlDataJson
|
||||
assertEquals(
|
||||
environment,
|
||||
repository.environment,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `environmentStateFow should react to changes in environment`() = runTest {
|
||||
val environmentUrlDataJson = mockk<EnvironmentUrlDataJson>()
|
||||
val environment = mockk<Environment>() {
|
||||
every { environmentUrlData } returns environmentUrlDataJson
|
||||
}
|
||||
every { environmentUrlDataJson.toEnvironmentUrls() } returns environment
|
||||
|
||||
repository.environmentStateFlow.test {
|
||||
// The initial values of the Flow and the property are in sync
|
||||
assertEquals(
|
||||
Environment.Us,
|
||||
repository.environment,
|
||||
)
|
||||
assertEquals(
|
||||
Environment.Us,
|
||||
awaitItem(),
|
||||
)
|
||||
|
||||
// Updating the property causes a flow emissions
|
||||
repository.environment = environment
|
||||
assertEquals(environment, awaitItem())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val ENVIRONMENT_EXTENSIONS_PATH =
|
||||
"com.x8bit.bitwarden.data.platform.repository.util.EnvironmentExtensionsKt"
|
||||
|
||||
private class FakeEnvironmentDiskSource : EnvironmentDiskSource {
|
||||
override var preAuthEnvironmentUrlData: EnvironmentUrlDataJson? = null
|
||||
set(value) {
|
||||
field = value
|
||||
mutablePreAuthEnvironmentUrlDataFlow.tryEmit(value)
|
||||
}
|
||||
|
||||
override val preAuthEnvironmentUrlDataFlow: Flow<EnvironmentUrlDataJson?>
|
||||
get() = mutablePreAuthEnvironmentUrlDataFlow
|
||||
.onSubscription { emit(preAuthEnvironmentUrlData) }
|
||||
|
||||
private val mutablePreAuthEnvironmentUrlDataFlow =
|
||||
MutableSharedFlow<EnvironmentUrlDataJson?>(
|
||||
replay = 1,
|
||||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
}
|
|
@ -3,6 +3,7 @@ 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 com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
@ -16,11 +17,16 @@ import org.junit.jupiter.api.Test
|
|||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class NetworkConfigRepositoryTest {
|
||||
private val mutableAuthStateFlow = MutableStateFlow<AuthState>(AuthState.Uninitialized)
|
||||
private val mutableEnvironmentStateFlow = MutableStateFlow<Environment>(Environment.Us)
|
||||
|
||||
private val authRepository: AuthRepository = mockk() {
|
||||
every { authStateFlow } returns mutableAuthStateFlow
|
||||
}
|
||||
|
||||
private val environmentRepository: EnvironmentRepository = mockk {
|
||||
every { environmentStateFlow } returns mutableEnvironmentStateFlow
|
||||
}
|
||||
|
||||
private val authTokenInterceptor = AuthTokenInterceptor()
|
||||
|
||||
private lateinit var networkConfigRepository: NetworkConfigRepository
|
||||
|
@ -30,6 +36,7 @@ class NetworkConfigRepositoryTest {
|
|||
networkConfigRepository = NetworkConfigRepositoryImpl(
|
||||
authRepository = authRepository,
|
||||
authTokenInterceptor = authTokenInterceptor,
|
||||
environmentRepository = environmentRepository,
|
||||
dispatcher = UnconfinedTestDispatcher(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package com.x8bit.bitwarden.data.platform.repository.util
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class EnvironmentExtensionsTest {
|
||||
@Test
|
||||
fun `toEnvironmentUrls should correctly convert US urls to the expected type`() {
|
||||
assertEquals(
|
||||
Environment.Us,
|
||||
EnvironmentUrlDataJson.DEFAULT_US.toEnvironmentUrls(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toEnvironmentUrls should correctly convert EU urls to the expected type`() {
|
||||
assertEquals(
|
||||
Environment.Eu,
|
||||
EnvironmentUrlDataJson.DEFAULT_EU.toEnvironmentUrls(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toEnvironmentUrls should correctly convert custom urls to the expected type`() {
|
||||
val environmentUrlData = EnvironmentUrlDataJson(
|
||||
base = "base",
|
||||
api = "api",
|
||||
identity = "identity",
|
||||
icon = "icon",
|
||||
notifications = "notifications",
|
||||
webVault = "webVault",
|
||||
events = "events",
|
||||
)
|
||||
assertEquals(
|
||||
Environment.SelfHosted(
|
||||
environmentUrlData = environmentUrlData,
|
||||
),
|
||||
environmentUrlData.toEnvironmentUrls(),
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue