[PM-19870] Migrate ServerConfig and ConfigDiskSource to the data module (#4992)

This commit is contained in:
Patrick Honkonen 2025-04-04 14:33:54 -04:00 committed by GitHub
parent c4f54ee93c
commit dda8237ce5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 88 additions and 388 deletions

View File

@ -272,6 +272,7 @@ dependencies {
standardImplementation(libs.google.play.review)
// Pull in test fixtures from other modules
testImplementation(testFixtures(project(":data")))
testImplementation(testFixtures(project(":network")))
testImplementation(libs.androidx.compose.ui.test)

View File

@ -8,6 +8,7 @@ import com.bitwarden.core.data.util.asSuccess
import com.bitwarden.core.data.util.flatMap
import com.bitwarden.crypto.HashPurpose
import com.bitwarden.crypto.Kdf
import com.bitwarden.data.datasource.disk.ConfigDiskSource
import com.bitwarden.network.model.RegisterRequestJson
import com.bitwarden.network.model.ResendEmailRequestJson
import com.bitwarden.network.model.ResendNewDeviceOtpRequestJson
@ -103,7 +104,6 @@ import com.x8bit.bitwarden.data.auth.repository.util.userSwitchingChangesFlow
import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants.DEFAULT_PBKDF2_ITERATIONS
import com.x8bit.bitwarden.data.auth.util.YubiKeyResult
import com.x8bit.bitwarden.data.auth.util.toSdkParams
import com.x8bit.bitwarden.data.platform.datasource.disk.ConfigDiskSource
import com.x8bit.bitwarden.data.platform.error.MissingPropertyException
import com.x8bit.bitwarden.data.platform.error.NoActiveUserException
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager

View File

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.auth.repository.di
import com.bitwarden.data.datasource.disk.ConfigDiskSource
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsService
import com.x8bit.bitwarden.data.auth.datasource.network.service.DevicesService
@ -13,7 +14,6 @@ import com.x8bit.bitwarden.data.auth.manager.TrustedDeviceManager
import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.AuthRepositoryImpl
import com.x8bit.bitwarden.data.platform.datasource.disk.ConfigDiskSource
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
import com.x8bit.bitwarden.data.platform.manager.LogsManager

View File

@ -5,9 +5,9 @@ import android.content.Context
import android.content.SharedPreferences
import androidx.room.Room
import com.bitwarden.data.datasource.di.EncryptedPreferences
import com.bitwarden.data.datasource.disk.ConfigDiskSource
import com.bitwarden.data.datasource.disk.ConfigDiskSourceImpl
import com.x8bit.bitwarden.data.platform.datasource.di.UnencryptedPreferences
import com.x8bit.bitwarden.data.platform.datasource.disk.ConfigDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.ConfigDiskSourceImpl
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSourceImpl
import com.x8bit.bitwarden.data.platform.datasource.disk.EventDiskSource

View File

@ -1,6 +1,6 @@
package com.x8bit.bitwarden.data.platform.manager
import com.x8bit.bitwarden.data.platform.datasource.disk.model.ServerConfig
import com.bitwarden.data.datasource.disk.model.ServerConfig
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import com.x8bit.bitwarden.data.platform.repository.ServerConfigRepository
import com.x8bit.bitwarden.data.platform.util.isServerVersionAtLeast

View File

@ -1,6 +1,6 @@
package com.x8bit.bitwarden.data.platform.repository
import com.x8bit.bitwarden.data.platform.datasource.disk.model.ServerConfig
import com.bitwarden.data.datasource.disk.model.ServerConfig
import kotlinx.coroutines.flow.StateFlow
/**

View File

@ -1,8 +1,8 @@
package com.x8bit.bitwarden.data.platform.repository
import com.bitwarden.data.datasource.disk.ConfigDiskSource
import com.bitwarden.data.datasource.disk.model.ServerConfig
import com.bitwarden.network.service.ConfigService
import com.x8bit.bitwarden.data.platform.datasource.disk.ConfigDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.model.ServerConfig
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted

View File

@ -1,12 +1,12 @@
package com.x8bit.bitwarden.data.platform.repository.di
import android.view.autofill.AutofillManager
import com.bitwarden.data.datasource.disk.ConfigDiskSource
import com.bitwarden.network.service.ConfigService
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityEnabledManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillEnabledManager
import com.x8bit.bitwarden.data.platform.datasource.disk.ConfigDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.FeatureFlagOverrideDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource

View File

@ -1,8 +1,6 @@
package com.x8bit.bitwarden.data.platform.util
import com.x8bit.bitwarden.data.platform.datasource.disk.model.ServerConfig
import kotlin.text.split
import kotlin.text.toIntOrNull
import com.bitwarden.data.datasource.disk.model.ServerConfig
private const val VERSION_SEPARATOR = "."
private const val SUFFIX_SEPARATOR = "-"

View File

@ -4,6 +4,7 @@ import androidx.core.content.edit
import app.cash.turbine.test
import com.bitwarden.authenticatorbridge.util.generateSecretKey
import com.bitwarden.core.di.CoreModule
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
import com.bitwarden.network.model.KdfTypeJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
@ -17,7 +18,6 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorUserDecryptionOptionsJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceUserDecryptionOptionsJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.UserDecryptionOptionsJson
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacySecureStorageMigrator
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockOrganization
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockPolicy

View File

@ -1,6 +1,6 @@
package com.x8bit.bitwarden.data.auth.datasource.disk.legacy
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacySecureStorage
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacySecureStorageMigratorImpl
import org.junit.jupiter.api.Assertions.assertEquals

View File

@ -15,6 +15,8 @@ import com.bitwarden.crypto.HashPurpose
import com.bitwarden.crypto.Kdf
import com.bitwarden.crypto.RsaKeyPair
import com.bitwarden.crypto.TrustDeviceResponse
import com.bitwarden.data.datasource.disk.model.ServerConfig
import com.bitwarden.data.datasource.disk.util.FakeConfigDiskSource
import com.bitwarden.network.model.ConfigResponseJson
import com.bitwarden.network.model.KdfTypeJson
import com.bitwarden.network.model.OrganizationAutoEnrollStatusResponseJson
@ -107,8 +109,6 @@ import com.x8bit.bitwarden.data.auth.repository.util.toUserState
import com.x8bit.bitwarden.data.auth.util.YubiKeyResult
import com.x8bit.bitwarden.data.auth.util.toSdkParams
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
import com.x8bit.bitwarden.data.platform.datasource.disk.model.ServerConfig
import com.x8bit.bitwarden.data.platform.datasource.disk.util.FakeConfigDiskSource
import com.x8bit.bitwarden.data.platform.error.MissingPropertyException
import com.x8bit.bitwarden.data.platform.error.NoActiveUserException
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager

View File

@ -2,8 +2,8 @@ package com.x8bit.bitwarden.data.platform.datasource.disk
import androidx.core.content.edit
import app.cash.turbine.test
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.Json

View File

@ -1,7 +1,7 @@
package com.x8bit.bitwarden.data.platform.datasource.disk
import androidx.core.content.edit
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse

View File

@ -1,7 +1,7 @@
package com.x8bit.bitwarden.data.platform.datasource.disk
import androidx.core.content.edit
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
import com.x8bit.bitwarden.data.platform.util.getBinaryLongFromZoneDateTime
import com.x8bit.bitwarden.data.platform.util.getZoneDateTimeFromBinaryLong
import org.junit.jupiter.api.Assertions.assertEquals

View File

@ -4,7 +4,7 @@ import androidx.core.content.edit
import app.cash.turbine.test
import com.bitwarden.core.data.util.decodeFromStringOrNull
import com.bitwarden.core.di.CoreModule
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
import com.x8bit.bitwarden.data.platform.manager.model.AppResumeScreenData
import com.x8bit.bitwarden.data.platform.repository.model.ClearClipboardFrequency
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType

View File

@ -1,7 +1,7 @@
package com.x8bit.bitwarden.data.platform.datasource.disk.legacy
import androidx.core.content.edit
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import io.mockk.every
import io.mockk.just

View File

@ -1,10 +1,10 @@
package com.x8bit.bitwarden.data.platform.manager
import app.cash.turbine.test
import com.bitwarden.data.datasource.disk.model.ServerConfig
import com.bitwarden.network.model.ConfigResponseJson
import com.bitwarden.network.model.ConfigResponseJson.EnvironmentJson
import com.bitwarden.network.model.ConfigResponseJson.ServerJson
import com.x8bit.bitwarden.data.platform.datasource.disk.model.ServerConfig
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import com.x8bit.bitwarden.data.platform.repository.util.FakeServerConfigRepository
import com.x8bit.bitwarden.data.platform.util.isServerVersionAtLeast

View File

@ -4,6 +4,7 @@ import app.cash.turbine.test
import com.bitwarden.core.data.util.asFailure
import com.bitwarden.core.data.util.asSuccess
import com.bitwarden.core.di.CoreModule
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
import com.bitwarden.network.model.PushTokenRequest
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
@ -11,7 +12,6 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSourceImpl
import com.x8bit.bitwarden.data.platform.datasource.network.service.PushService

View File

@ -1,13 +1,13 @@
package com.x8bit.bitwarden.data.platform.repository
import app.cash.turbine.test
import com.bitwarden.data.datasource.disk.model.ServerConfig
import com.bitwarden.network.model.ConfigResponseJson
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
import com.x8bit.bitwarden.data.platform.datasource.disk.FeatureFlagOverrideDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.model.ServerConfig
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import io.mockk.every
import io.mockk.just

View File

@ -2,13 +2,13 @@ package com.x8bit.bitwarden.data.platform.repository
import app.cash.turbine.test
import com.bitwarden.core.data.util.asSuccess
import com.bitwarden.data.datasource.disk.model.ServerConfig
import com.bitwarden.data.datasource.disk.util.FakeConfigDiskSource
import com.bitwarden.network.model.ConfigResponseJson
import com.bitwarden.network.model.ConfigResponseJson.EnvironmentJson
import com.bitwarden.network.model.ConfigResponseJson.ServerJson
import com.bitwarden.network.service.ConfigService
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
import com.x8bit.bitwarden.data.platform.datasource.disk.model.ServerConfig
import com.x8bit.bitwarden.data.platform.datasource.disk.util.FakeConfigDiskSource
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import io.mockk.coEvery
import io.mockk.mockk

View File

@ -1,9 +1,9 @@
package com.x8bit.bitwarden.data.platform.repository.util
import com.bitwarden.data.datasource.disk.model.ServerConfig
import com.bitwarden.network.model.ConfigResponseJson
import com.bitwarden.network.model.ConfigResponseJson.EnvironmentJson
import com.bitwarden.network.model.ConfigResponseJson.ServerJson
import com.x8bit.bitwarden.data.platform.datasource.disk.model.ServerConfig
import com.x8bit.bitwarden.data.platform.repository.ServerConfigRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

View File

@ -1,7 +1,7 @@
package com.x8bit.bitwarden.data.tools.generator.datasource.disk
import androidx.core.content.edit
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
import com.x8bit.bitwarden.data.tools.generator.repository.model.UsernameGenerationOptions
import kotlinx.serialization.json.Json

View File

@ -207,6 +207,7 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.tooling)
// Pull in test fixtures from other modules.
testImplementation(testFixtures(project(":data")))
testImplementation(testFixtures(project(":network")))
testImplementation(libs.androidx.compose.ui.test)

View File

@ -1,21 +0,0 @@
package com.bitwarden.authenticator.data.platform.datasource.disk
import com.bitwarden.authenticator.data.platform.datasource.disk.model.ServerConfig
import kotlinx.coroutines.flow.Flow
/**
* Primary access point for server configuration-related disk information.
*/
interface ConfigDiskSource {
/**
* The currently persisted [ServerConfig] (or `null` if not set).
*/
var serverConfig: ServerConfig?
/**
* Emits updates that track [ServerConfig]. This will replay the last known value,
* if any.
*/
val serverConfigFlow: Flow<ServerConfig?>
}

View File

@ -1,37 +0,0 @@
package com.bitwarden.authenticator.data.platform.datasource.disk
import android.content.SharedPreferences
import com.bitwarden.authenticator.data.platform.datasource.disk.model.ServerConfig
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.core.data.util.decodeFromStringOrNull
import com.bitwarden.data.datasource.disk.BaseDiskSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onSubscription
import kotlinx.serialization.json.Json
private const val SERVER_CONFIGURATIONS = "serverConfigurations"
/**
* Primary implementation of [ConfigDiskSource].
*/
class ConfigDiskSourceImpl(
sharedPreferences: SharedPreferences,
private val json: Json,
) : BaseDiskSource(sharedPreferences = sharedPreferences),
ConfigDiskSource {
override var serverConfig: ServerConfig?
get() = getString(key = SERVER_CONFIGURATIONS)?.let { json.decodeFromStringOrNull(it) }
set(value) {
putString(
key = SERVER_CONFIGURATIONS,
value = value?.let { json.encodeToString(it) },
)
mutableServerConfigFlow.tryEmit(value)
}
override val serverConfigFlow: Flow<ServerConfig?>
get() = mutableServerConfigFlow.onSubscription { emit(serverConfig) }
private val mutableServerConfigFlow = bufferedMutableSharedFlow<ServerConfig?>(replay = 1)
}

View File

@ -2,14 +2,14 @@ package com.bitwarden.authenticator.data.platform.datasource.disk.di
import android.content.SharedPreferences
import com.bitwarden.authenticator.data.platform.datasource.di.UnencryptedPreferences
import com.bitwarden.authenticator.data.platform.datasource.disk.ConfigDiskSource
import com.bitwarden.authenticator.data.platform.datasource.disk.ConfigDiskSourceImpl
import com.bitwarden.authenticator.data.platform.datasource.disk.FeatureFlagDiskSource
import com.bitwarden.authenticator.data.platform.datasource.disk.FeatureFlagDiskSourceImpl
import com.bitwarden.authenticator.data.platform.datasource.disk.FeatureFlagOverrideDiskSource
import com.bitwarden.authenticator.data.platform.datasource.disk.FeatureFlagOverrideDiskSourceImpl
import com.bitwarden.authenticator.data.platform.datasource.disk.SettingsDiskSource
import com.bitwarden.authenticator.data.platform.datasource.disk.SettingsDiskSourceImpl
import com.bitwarden.data.datasource.disk.ConfigDiskSource
import com.bitwarden.data.datasource.disk.ConfigDiskSourceImpl
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn

View File

@ -1,22 +0,0 @@
package com.bitwarden.authenticator.data.platform.datasource.disk.model
import com.bitwarden.network.model.ConfigResponseJson
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* A higher-level wrapper around [ConfigResponseJson] that provides a timestamp
* to check if a sync is necessary
*
* @property lastSync The [Long] of the last sync.
* @property serverData The raw [ConfigResponseJson] that contains specific data of the
* server configuration
*/
@Serializable
data class ServerConfig(
@SerialName("lastSync")
val lastSync: Long,
@SerialName("serverData")
val serverData: ConfigResponseJson,
)

View File

@ -1,8 +1,8 @@
package com.bitwarden.authenticator.data.platform.manager
import com.bitwarden.authenticator.data.platform.datasource.disk.model.ServerConfig
import com.bitwarden.authenticator.data.platform.manager.model.FlagKey
import com.bitwarden.authenticator.data.platform.repository.ServerConfigRepository
import com.bitwarden.data.datasource.disk.model.ServerConfig
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

View File

@ -1,6 +1,6 @@
package com.bitwarden.authenticator.data.platform.repository
import com.bitwarden.authenticator.data.platform.datasource.disk.model.ServerConfig
import com.bitwarden.data.datasource.disk.model.ServerConfig
import kotlinx.coroutines.flow.StateFlow
/**

View File

@ -1,8 +1,8 @@
package com.bitwarden.authenticator.data.platform.repository
import com.bitwarden.authenticator.data.platform.datasource.disk.ConfigDiskSource
import com.bitwarden.authenticator.data.platform.datasource.disk.model.ServerConfig
import com.bitwarden.authenticator.data.platform.manager.DispatcherManager
import com.bitwarden.data.datasource.disk.ConfigDiskSource
import com.bitwarden.data.datasource.disk.model.ServerConfig
import com.bitwarden.network.service.ConfigService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted

View File

@ -2,7 +2,6 @@ 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.ConfigDiskSource
import com.bitwarden.authenticator.data.platform.datasource.disk.FeatureFlagDiskSource
import com.bitwarden.authenticator.data.platform.datasource.disk.FeatureFlagOverrideDiskSource
import com.bitwarden.authenticator.data.platform.datasource.disk.SettingsDiskSource
@ -16,6 +15,7 @@ import com.bitwarden.authenticator.data.platform.repository.ServerConfigReposito
import com.bitwarden.authenticator.data.platform.repository.ServerConfigRepositoryImpl
import com.bitwarden.authenticator.data.platform.repository.SettingsRepository
import com.bitwarden.authenticator.data.platform.repository.SettingsRepositoryImpl
import com.bitwarden.data.datasource.disk.ConfigDiskSource
import com.bitwarden.network.service.ConfigService
import dagger.Module
import dagger.Provides

View File

@ -1,7 +1,7 @@
package com.bitwarden.authenticator.data.auth.datasource.disk
import com.bitwarden.authenticator.data.platform.base.FakeSharedPreferences
import com.bitwarden.authenticatorbridge.util.generateSecretKey
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue

View File

@ -1,109 +0,0 @@
package com.bitwarden.authenticator.data.platform.base
import android.content.SharedPreferences
/**
* A faked implementation of [SharedPreferences] that is backed by an internal, memory-based map.
*/
class FakeSharedPreferences : SharedPreferences {
private val sharedPreferences: MutableMap<String, Any?> = mutableMapOf()
private val listeners = mutableSetOf<SharedPreferences.OnSharedPreferenceChangeListener>()
override fun contains(key: String): Boolean =
sharedPreferences.containsKey(key)
override fun edit(): SharedPreferences.Editor = Editor()
override fun getAll(): Map<String, *> = sharedPreferences
override fun getBoolean(key: String, defaultValue: Boolean): Boolean =
getValue(key, defaultValue)
override fun getFloat(key: String, defaultValue: Float): Float =
getValue(key, defaultValue)
override fun getInt(key: String, defaultValue: Int): Int =
getValue(key, defaultValue)
override fun getLong(key: String, defaultValue: Long): Long =
getValue(key, defaultValue)
override fun getString(key: String, defaultValue: String?): String? =
getValue(key, defaultValue)
override fun getStringSet(key: String, defaultValue: Set<String>?): Set<String>? =
getValue(key, defaultValue)
override fun registerOnSharedPreferenceChangeListener(
listener: SharedPreferences.OnSharedPreferenceChangeListener,
) {
listeners += listener
}
override fun unregisterOnSharedPreferenceChangeListener(
listener: SharedPreferences.OnSharedPreferenceChangeListener,
) {
listeners -= listener
}
private inline fun <reified T> getValue(
key: String,
defaultValue: T,
): T = sharedPreferences[key] as? T ?: defaultValue
inner class Editor : SharedPreferences.Editor {
private val pendingSharedPreferences = sharedPreferences.toMutableMap()
override fun apply() {
sharedPreferences.apply {
clear()
putAll(pendingSharedPreferences)
// Notify listeners
listeners.forEach { listener ->
pendingSharedPreferences.keys.forEach { key ->
listener.onSharedPreferenceChanged(this@FakeSharedPreferences, key)
}
}
}
}
override fun clear(): SharedPreferences.Editor =
apply { pendingSharedPreferences.clear() }
override fun commit(): Boolean {
apply()
return true
}
override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor =
putValue(key, value)
override fun putFloat(key: String, value: Float): SharedPreferences.Editor =
putValue(key, value)
override fun putInt(key: String, value: Int): SharedPreferences.Editor =
putValue(key, value)
override fun putLong(key: String, value: Long): SharedPreferences.Editor =
putValue(key, value)
override fun putString(key: String, value: String?): SharedPreferences.Editor =
putValue(key, value)
override fun putStringSet(key: String, value: Set<String>?): SharedPreferences.Editor =
putValue(key, value)
override fun remove(key: String): SharedPreferences.Editor =
apply { pendingSharedPreferences.remove(key) }
private inline fun <reified T> putValue(
key: String,
value: T,
): SharedPreferences.Editor = apply {
value
?.let { pendingSharedPreferences[key] = it }
?: pendingSharedPreferences.remove(key)
}
}
}

View File

@ -1,116 +0,0 @@
package com.bitwarden.authenticator.data.platform.datasource.disk
import androidx.core.content.edit
import app.cash.turbine.test
import com.bitwarden.authenticator.data.platform.base.FakeSharedPreferences
import com.bitwarden.authenticator.data.platform.datasource.disk.model.ServerConfig
import com.bitwarden.core.di.CoreModule
import com.bitwarden.network.model.ConfigResponseJson
import com.bitwarden.network.model.ConfigResponseJson.EnvironmentJson
import com.bitwarden.network.model.ConfigResponseJson.ServerJson
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.JsonPrimitive
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test
import java.time.Instant
class ConfigDiskSourceTest {
private val json = CoreModule.providesJson()
private val fakeSharedPreferences = FakeSharedPreferences()
private val configDiskSource = ConfigDiskSourceImpl(
sharedPreferences = fakeSharedPreferences,
json = json,
)
@Test
fun `serverConfig should pull from and update SharedPreferences`() =
runTest {
val serverConfigKey = "bwPreferencesStorage:serverConfigurations"
// Shared preferences and the repository start with the same value.
assertNull(configDiskSource.serverConfig)
assertNull(fakeSharedPreferences.getString(serverConfigKey, null))
// Updating the repository updates shared preferences
configDiskSource.serverConfig = SERVER_CONFIG
assertEquals(
json.parseToJsonElement(
SERVER_CONFIG_JSON,
),
json.parseToJsonElement(
fakeSharedPreferences.getString(serverConfigKey, null)!!,
),
)
// Update SharedPreferences updates the repository
fakeSharedPreferences.edit { putString(serverConfigKey, null) }
assertNull(configDiskSource.serverConfig)
}
@Test
fun `serverConfigFlow should react to changes in serverConfig`() =
runTest {
configDiskSource.serverConfigFlow.test {
// The initial values of the Flow and the property are in sync
assertNull(configDiskSource.serverConfig)
assertNull(awaitItem())
// Updating the repository updates shared preferences
configDiskSource.serverConfig = SERVER_CONFIG
assertEquals(SERVER_CONFIG, awaitItem())
}
}
}
private const val SERVER_CONFIG_JSON = """
{
"lastSync": 1698408000000,
"serverData": {
"version": "2024.7.0",
"gitHash": "25cf6119-dirty",
"server": {
"name": "example",
"url": "https://localhost:8080"
},
"environment": {
"vault": "https://localhost:8080",
"api": "http://localhost:4000",
"identity": "http://localhost:33656",
"notifications": "http://localhost:61840",
"sso": "http://localhost:51822"
},
"featureStates": {
"duo-redirect": true,
"flexible-collections-v-1": false
}
}
}
"""
private val SERVER_CONFIG = ServerConfig(
lastSync = Instant.parse("2023-10-27T12:00:00Z").toEpochMilli(),
serverData = ConfigResponseJson(
type = null,
version = "2024.7.0",
gitHash = "25cf6119-dirty",
server = ServerJson(
name = "example",
url = "https://localhost:8080",
),
environment = EnvironmentJson(
cloudRegion = null,
vaultUrl = "https://localhost:8080",
apiUrl = "http://localhost:4000",
identityUrl = "http://localhost:33656",
notificationsUrl = "http://localhost:61840",
ssoUrl = "http://localhost:51822",
),
featureStates = mapOf(
"duo-redirect" to JsonPrimitive(true),
"flexible-collections-v-1" to JsonPrimitive(false),
),
),
)

View File

@ -2,10 +2,10 @@ package com.bitwarden.authenticator.data.platform.datasource.disk
import androidx.core.content.edit
import app.cash.turbine.test
import com.bitwarden.authenticator.data.platform.base.FakeSharedPreferences
import com.bitwarden.authenticator.data.platform.datasource.disk.model.FeatureFlagsConfiguration
import com.bitwarden.authenticator.data.platform.manager.model.FlagKey
import com.bitwarden.core.di.CoreModule
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.JsonPrimitive
import org.junit.jupiter.api.Assertions.assertEquals

View File

@ -1,8 +1,8 @@
package com.bitwarden.authenticator.data.platform.datasource.disk
import androidx.core.content.edit
import com.bitwarden.authenticator.data.platform.base.FakeSharedPreferences
import com.bitwarden.authenticator.data.platform.manager.model.FlagKey
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNull

View File

@ -2,8 +2,8 @@ package com.bitwarden.authenticator.data.platform.datasource.disk
import androidx.core.content.edit
import app.cash.turbine.test
import com.bitwarden.authenticator.data.platform.base.FakeSharedPreferences
import com.bitwarden.authenticator.ui.platform.feature.settings.data.model.DefaultSaveOption
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse

View File

@ -1,25 +0,0 @@
package com.bitwarden.authenticator.data.platform.datasource.disk.util
import com.bitwarden.authenticator.data.platform.datasource.disk.ConfigDiskSource
import com.bitwarden.authenticator.data.platform.datasource.disk.model.ServerConfig
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onSubscription
class FakeConfigDiskSource : ConfigDiskSource {
private var serverConfigValue: ServerConfig? = null
override var serverConfig: ServerConfig?
get() = serverConfigValue
set(value) {
serverConfigValue = value
mutableServerConfigFlow.tryEmit(value)
}
override val serverConfigFlow: Flow<ServerConfig?>
get() = mutableServerConfigFlow
.onSubscription { emit(serverConfig) }
private val mutableServerConfigFlow =
bufferedMutableSharedFlow<ServerConfig?>(replay = 1)
}

View File

@ -1,9 +1,9 @@
package com.bitwarden.authenticator.data.platform.manager
import app.cash.turbine.test
import com.bitwarden.authenticator.data.platform.datasource.disk.model.ServerConfig
import com.bitwarden.authenticator.data.platform.manager.model.FlagKey
import com.bitwarden.authenticator.data.platform.repository.util.FakeServerConfigRepository
import com.bitwarden.data.datasource.disk.model.ServerConfig
import com.bitwarden.network.model.ConfigResponseJson
import com.bitwarden.network.model.ConfigResponseJson.EnvironmentJson
import com.bitwarden.network.model.ConfigResponseJson.ServerJson

View File

@ -2,8 +2,8 @@ package com.bitwarden.authenticator.data.platform.repository
import app.cash.turbine.test
import com.bitwarden.authenticator.data.platform.datasource.disk.FeatureFlagOverrideDiskSource
import com.bitwarden.authenticator.data.platform.datasource.disk.model.ServerConfig
import com.bitwarden.authenticator.data.platform.manager.model.FlagKey
import com.bitwarden.data.datasource.disk.model.ServerConfig
import com.bitwarden.network.model.ConfigResponseJson
import io.mockk.every
import io.mockk.just

View File

@ -2,10 +2,10 @@ 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.ServerConfig
import com.bitwarden.authenticator.data.platform.datasource.disk.util.FakeConfigDiskSource
import com.bitwarden.authenticator.data.platform.manager.DispatcherManager
import com.bitwarden.core.data.util.asSuccess
import com.bitwarden.data.datasource.disk.model.ServerConfig
import com.bitwarden.data.datasource.disk.util.FakeConfigDiskSource
import com.bitwarden.network.model.ConfigResponseJson
import com.bitwarden.network.model.ConfigResponseJson.EnvironmentJson
import com.bitwarden.network.model.ConfigResponseJson.ServerJson

View File

@ -1,7 +1,7 @@
package com.bitwarden.authenticator.data.platform.repository.util
import com.bitwarden.authenticator.data.platform.datasource.disk.model.ServerConfig
import com.bitwarden.authenticator.data.platform.repository.ServerConfigRepository
import com.bitwarden.data.datasource.disk.model.ServerConfig
import com.bitwarden.network.model.ConfigResponseJson
import com.bitwarden.network.model.ConfigResponseJson.EnvironmentJson
import com.bitwarden.network.model.ConfigResponseJson.ServerJson

View File

@ -4,6 +4,7 @@ plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.hilt)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.ksp)
}
@ -31,6 +32,10 @@ android {
sourceCompatibility(libs.versions.jvmTarget.get())
targetCompatibility(libs.versions.jvmTarget.get())
}
@Suppress("UnstableApiUsage")
testFixtures {
enable = true
}
}
kotlin {
@ -40,8 +45,31 @@ kotlin {
}
dependencies {
implementation(project(":core"))
implementation(project(":network"))
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.security.crypto)
implementation(libs.google.hilt.android)
ksp(libs.google.hilt.compiler)
implementation(libs.kotlinx.serialization)
testImplementation(platform(libs.junit.bom))
testRuntimeOnly(libs.junit.platform.launcher)
testImplementation(libs.junit.junit5)
testImplementation(libs.junit.vintage)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.square.turbine)
testFixturesImplementation(project(":core"))
testFixturesImplementation(libs.kotlinx.coroutines.test)
}
tasks {
withType<Test> {
useJUnitPlatform()
maxHeapSize = "2g"
maxParallelForks = Runtime.getRuntime().availableProcessors()
jvmArgs = jvmArgs.orEmpty() + "-XX:+UseParallelGC"
}
}

View File

@ -1,6 +1,6 @@
package com.x8bit.bitwarden.data.platform.datasource.disk
package com.bitwarden.data.datasource.disk
import com.x8bit.bitwarden.data.platform.datasource.disk.model.ServerConfig
import com.bitwarden.data.datasource.disk.model.ServerConfig
import kotlinx.coroutines.flow.Flow
/**

View File

@ -1,10 +1,9 @@
package com.x8bit.bitwarden.data.platform.datasource.disk
package com.bitwarden.data.datasource.disk
import android.content.SharedPreferences
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.core.data.util.decodeFromStringOrNull
import com.bitwarden.data.datasource.disk.BaseDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.model.ServerConfig
import com.bitwarden.data.datasource.disk.model.ServerConfig
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onSubscription
import kotlinx.serialization.json.Json

View File

@ -1,4 +1,4 @@
package com.x8bit.bitwarden.data.platform.datasource.disk.model
package com.bitwarden.data.datasource.disk.model
import com.bitwarden.network.model.ConfigResponseJson
import kotlinx.serialization.SerialName

View File

@ -1,13 +1,13 @@
package com.x8bit.bitwarden.data.platform.datasource.disk
package com.bitwarden.data.datasource.disk
import androidx.core.content.edit
import app.cash.turbine.test
import com.bitwarden.core.di.CoreModule
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
import com.bitwarden.data.datasource.disk.model.ServerConfig
import com.bitwarden.network.model.ConfigResponseJson
import com.bitwarden.network.model.ConfigResponseJson.EnvironmentJson
import com.bitwarden.network.model.ConfigResponseJson.ServerJson
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
import com.x8bit.bitwarden.data.platform.datasource.disk.model.ServerConfig
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.JsonPrimitive
import org.junit.jupiter.api.Assertions.assertEquals

View File

@ -1,4 +1,4 @@
package com.x8bit.bitwarden.data.platform.base
package com.bitwarden.data.datasource.disk.base
import android.content.SharedPreferences

View File

@ -1,11 +1,14 @@
package com.x8bit.bitwarden.data.platform.datasource.disk.util
package com.bitwarden.data.datasource.disk.util
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.data.platform.datasource.disk.ConfigDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.model.ServerConfig
import com.bitwarden.data.datasource.disk.ConfigDiskSource
import com.bitwarden.data.datasource.disk.model.ServerConfig
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onSubscription
/**
* A faked [ConfigDiskSource] that holds data in memory.
*/
class FakeConfigDiskSource : ConfigDiskSource {
private var serverConfigValue: ServerConfig? = null

View File

@ -101,7 +101,7 @@ complexity:
ignoreStringsRegex: '$^'
TooManyFunctions:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**', '**/testFixtures/**' ]
thresholdInFiles: 11
thresholdInClasses: 11
thresholdInInterfaces: 11