mirror of
https://github.com/bitwarden/android.git
synced 2024-11-23 09:56:11 +03:00
BIT-1282: Add UI for Vault Sync (#740)
This commit is contained in:
parent
376278e97a
commit
de99c36b20
8 changed files with 191 additions and 6 deletions
|
@ -6,6 +6,7 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLang
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
|
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides an API for observing and modifying settings state.
|
* Provides an API for observing and modifying settings state.
|
||||||
|
@ -27,6 +28,16 @@ interface SettingsRepository {
|
||||||
*/
|
*/
|
||||||
val appThemeStateFlow: StateFlow<AppTheme>
|
val appThemeStateFlow: StateFlow<AppTheme>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently stored last time the vault was synced.
|
||||||
|
*/
|
||||||
|
var vaultLastSync: Instant?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks changes to the [vaultLastSync].
|
||||||
|
*/
|
||||||
|
val vaultLastSyncStateFlow: StateFlow<Instant?>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current setting for getting login item icons.
|
* The current setting for getting login item icons.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -20,6 +20,7 @@ import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Primary implementation of [SettingsRepository].
|
* Primary implementation of [SettingsRepository].
|
||||||
|
@ -61,6 +62,26 @@ class SettingsRepositoryImpl(
|
||||||
initialValue = settingsDiskSource.appTheme,
|
initialValue = settingsDiskSource.appTheme,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override var vaultLastSync: Instant?
|
||||||
|
get() = vaultLastSyncStateFlow.value
|
||||||
|
set(value) {
|
||||||
|
val userId = activeUserId ?: return
|
||||||
|
settingsDiskSource.storeLastSyncTime(userId = userId, lastSyncTime = value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val vaultLastSyncStateFlow: StateFlow<Instant?>
|
||||||
|
get() = activeUserId
|
||||||
|
?.let {
|
||||||
|
settingsDiskSource
|
||||||
|
.getLastSyncTimeFlow(userId = it)
|
||||||
|
.stateIn(
|
||||||
|
scope = unconfinedScope,
|
||||||
|
started = SharingStarted.Eagerly,
|
||||||
|
initialValue = settingsDiskSource.getLastSyncTime(userId = it),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
?: MutableStateFlow(value = null)
|
||||||
|
|
||||||
override var isIconLoadingDisabled: Boolean
|
override var isIconLoadingDisabled: Boolean
|
||||||
get() = settingsDiskSource.isIconLoadingDisabled ?: false
|
get() = settingsDiskSource.isIconLoadingDisabled ?: false
|
||||||
set(value) {
|
set(value) {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import com.bitwarden.crypto.Kdf
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
|
import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.toUpdatedUserStateJson
|
import com.x8bit.bitwarden.data.auth.repository.util.toUpdatedUserStateJson
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.isNoConnectionError
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.isNoConnectionError
|
||||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||||
|
@ -78,6 +79,7 @@ import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.time.Clock
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -97,9 +99,11 @@ class VaultRepositoryImpl(
|
||||||
private val vaultDiskSource: VaultDiskSource,
|
private val vaultDiskSource: VaultDiskSource,
|
||||||
private val vaultSdkSource: VaultSdkSource,
|
private val vaultSdkSource: VaultSdkSource,
|
||||||
private val authDiskSource: AuthDiskSource,
|
private val authDiskSource: AuthDiskSource,
|
||||||
|
private val settingsDiskSource: SettingsDiskSource,
|
||||||
private val fileManager: FileManager,
|
private val fileManager: FileManager,
|
||||||
private val vaultLockManager: VaultLockManager,
|
private val vaultLockManager: VaultLockManager,
|
||||||
private val totpCodeManager: TotpCodeManager,
|
private val totpCodeManager: TotpCodeManager,
|
||||||
|
private val clock: Clock,
|
||||||
dispatcherManager: DispatcherManager,
|
dispatcherManager: DispatcherManager,
|
||||||
) : VaultRepository,
|
) : VaultRepository,
|
||||||
VaultLockManager by vaultLockManager {
|
VaultLockManager by vaultLockManager {
|
||||||
|
@ -230,6 +234,7 @@ class VaultRepositoryImpl(
|
||||||
unlockVaultForOrganizationsIfNecessary(syncResponse = syncResponse)
|
unlockVaultForOrganizationsIfNecessary(syncResponse = syncResponse)
|
||||||
storeProfileData(syncResponse = syncResponse)
|
storeProfileData(syncResponse = syncResponse)
|
||||||
vaultDiskSource.replaceVaultData(userId = userId, vault = syncResponse)
|
vaultDiskSource.replaceVaultData(userId = userId, vault = syncResponse)
|
||||||
|
settingsDiskSource.storeLastSyncTime(userId = userId, clock.instant())
|
||||||
},
|
},
|
||||||
onFailure = { throwable ->
|
onFailure = { throwable ->
|
||||||
mutableCiphersStateFlow.update { currentState ->
|
mutableCiphersStateFlow.update { currentState ->
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.x8bit.bitwarden.data.vault.repository.di
|
package com.x8bit.bitwarden.data.vault.repository.di
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
|
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
|
||||||
|
@ -16,6 +17,7 @@ import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import java.time.Clock
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,10 +36,12 @@ object VaultRepositoryModule {
|
||||||
vaultDiskSource: VaultDiskSource,
|
vaultDiskSource: VaultDiskSource,
|
||||||
vaultSdkSource: VaultSdkSource,
|
vaultSdkSource: VaultSdkSource,
|
||||||
authDiskSource: AuthDiskSource,
|
authDiskSource: AuthDiskSource,
|
||||||
|
settingsDiskSource: SettingsDiskSource,
|
||||||
fileManager: FileManager,
|
fileManager: FileManager,
|
||||||
vaultLockManager: VaultLockManager,
|
vaultLockManager: VaultLockManager,
|
||||||
dispatcherManager: DispatcherManager,
|
dispatcherManager: DispatcherManager,
|
||||||
totpCodeManager: TotpCodeManager,
|
totpCodeManager: TotpCodeManager,
|
||||||
|
clock: Clock,
|
||||||
): VaultRepository = VaultRepositoryImpl(
|
): VaultRepository = VaultRepositoryImpl(
|
||||||
syncService = syncService,
|
syncService = syncService,
|
||||||
sendsService = sendsService,
|
sendsService = sendsService,
|
||||||
|
@ -45,9 +49,11 @@ object VaultRepositoryModule {
|
||||||
vaultDiskSource = vaultDiskSource,
|
vaultDiskSource = vaultDiskSource,
|
||||||
vaultSdkSource = vaultSdkSource,
|
vaultSdkSource = vaultSdkSource,
|
||||||
authDiskSource = authDiskSource,
|
authDiskSource = authDiskSource,
|
||||||
|
settingsDiskSource = settingsDiskSource,
|
||||||
fileManager = fileManager,
|
fileManager = fileManager,
|
||||||
vaultLockManager = vaultLockManager,
|
vaultLockManager = vaultLockManager,
|
||||||
dispatcherManager = dispatcherManager,
|
dispatcherManager = dispatcherManager,
|
||||||
totpCodeManager = totpCodeManager,
|
totpCodeManager = totpCodeManager,
|
||||||
|
clock = clock,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,24 +2,34 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.other
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
|
import com.x8bit.bitwarden.ui.platform.util.toFormattedPattern
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import java.time.Clock
|
||||||
|
import java.time.Instant
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val KEY_STATE = "state"
|
private const val KEY_STATE = "state"
|
||||||
|
|
||||||
|
private const val VAULT_LAST_SYNC_TIME_PATTERN: String = "M/d/yyyy h:mm a"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View model for the other screen.
|
* View model for the other screen.
|
||||||
*/
|
*/
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class OtherViewModel @Inject constructor(
|
class OtherViewModel @Inject constructor(
|
||||||
|
private val clock: Clock,
|
||||||
private val settingsRepo: SettingsRepository,
|
private val settingsRepo: SettingsRepository,
|
||||||
private val vaultRepo: VaultRepository,
|
private val vaultRepo: VaultRepository,
|
||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
|
@ -29,16 +39,28 @@ class OtherViewModel @Inject constructor(
|
||||||
allowScreenCapture = false,
|
allowScreenCapture = false,
|
||||||
allowSyncOnRefresh = settingsRepo.getPullToRefreshEnabledFlow().value,
|
allowSyncOnRefresh = settingsRepo.getPullToRefreshEnabledFlow().value,
|
||||||
clearClipboardFrequency = OtherState.ClearClipboardFrequency.DEFAULT,
|
clearClipboardFrequency = OtherState.ClearClipboardFrequency.DEFAULT,
|
||||||
lastSyncTime = "5/14/2023 4:52 PM",
|
lastSyncTime = settingsRepo
|
||||||
|
.vaultLastSync
|
||||||
|
?.toFormattedPattern(VAULT_LAST_SYNC_TIME_PATTERN, clock.zone)
|
||||||
|
.orEmpty(),
|
||||||
dialogState = null,
|
dialogState = null,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
|
init {
|
||||||
|
settingsRepo
|
||||||
|
.vaultLastSyncStateFlow
|
||||||
|
.map { OtherAction.Internal.VaultLastSyncReceive(it) }
|
||||||
|
.onEach(::sendAction)
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
override fun handleAction(action: OtherAction): Unit = when (action) {
|
override fun handleAction(action: OtherAction): Unit = when (action) {
|
||||||
is OtherAction.AllowScreenCaptureToggle -> handleAllowScreenCaptureToggled(action)
|
is OtherAction.AllowScreenCaptureToggle -> handleAllowScreenCaptureToggled(action)
|
||||||
is OtherAction.AllowSyncToggle -> handleAllowSyncToggled(action)
|
is OtherAction.AllowSyncToggle -> handleAllowSyncToggled(action)
|
||||||
OtherAction.BackClick -> handleBackClicked()
|
OtherAction.BackClick -> handleBackClicked()
|
||||||
is OtherAction.ClearClipboardFrequencyChange -> handleClearClipboardFrequencyChanged(action)
|
is OtherAction.ClearClipboardFrequencyChange -> handleClearClipboardFrequencyChanged(action)
|
||||||
OtherAction.SyncNowButtonClick -> handleSyncNowButtonClicked()
|
OtherAction.SyncNowButtonClick -> handleSyncNowButtonClicked()
|
||||||
|
is OtherAction.Internal -> handleInternalAction(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleAllowScreenCaptureToggled(action: OtherAction.AllowScreenCaptureToggle) {
|
private fun handleAllowScreenCaptureToggled(action: OtherAction.AllowScreenCaptureToggle) {
|
||||||
|
@ -67,9 +89,29 @@ class OtherViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSyncNowButtonClicked() {
|
private fun handleSyncNowButtonClicked() {
|
||||||
// TODO BIT-1282 add full support and visual feedback
|
mutableStateFlow.update {
|
||||||
|
it.copy(dialogState = OtherState.DialogState.Loading(R.string.syncing.asText()))
|
||||||
|
}
|
||||||
vaultRepo.sync()
|
vaultRepo.sync()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleInternalAction(action: OtherAction.Internal) {
|
||||||
|
when (action) {
|
||||||
|
is OtherAction.Internal.VaultLastSyncReceive -> handleVaultDataReceive(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleVaultDataReceive(action: OtherAction.Internal.VaultLastSyncReceive) {
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
lastSyncTime = action
|
||||||
|
.vaultLastSyncTime
|
||||||
|
?.toFormattedPattern(VAULT_LAST_SYNC_TIME_PATTERN, clock.zone)
|
||||||
|
.orEmpty(),
|
||||||
|
dialogState = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -154,4 +196,16 @@ sealed class OtherAction {
|
||||||
* Indicates that the user clicked the Sync Now button.
|
* Indicates that the user clicked the Sync Now button.
|
||||||
*/
|
*/
|
||||||
data object SyncNowButtonClick : OtherAction()
|
data object SyncNowButtonClick : OtherAction()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models actions that the [OtherViewModel] itself might send.
|
||||||
|
*/
|
||||||
|
sealed class Internal : OtherAction() {
|
||||||
|
/**
|
||||||
|
* Indicates last sync time of the vault has been received.
|
||||||
|
*/
|
||||||
|
data class VaultLastSyncReceive(
|
||||||
|
val vaultLastSyncTime: Instant?,
|
||||||
|
) : Internal()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
import org.junit.jupiter.api.Assertions.assertNull
|
import org.junit.jupiter.api.Assertions.assertNull
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
class SettingsRepositoryTest {
|
class SettingsRepositoryTest {
|
||||||
private val autofillManager: AutofillManager = mockk {
|
private val autofillManager: AutofillManager = mockk {
|
||||||
|
@ -119,6 +120,36 @@ class SettingsRepositoryTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `vaultLastSync should pull from and update SettingsDiskSource`() {
|
||||||
|
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||||
|
assertNull(settingsRepository.vaultLastSync)
|
||||||
|
val instant = Instant.ofEpochMilli(1_698_408_000_000L)
|
||||||
|
|
||||||
|
// Updates to the disk source change the repository value.
|
||||||
|
fakeSettingsDiskSource.storeLastSyncTime(
|
||||||
|
userId = MOCK_USER_STATE.activeUserId,
|
||||||
|
lastSyncTime = instant,
|
||||||
|
)
|
||||||
|
assertEquals(instant, settingsRepository.vaultLastSync)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `vaultLastSyncStateFlow should react to changes in SettingsDiskSource`() = runTest {
|
||||||
|
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||||
|
val instant = Instant.ofEpochMilli(1_698_408_000_000L)
|
||||||
|
settingsRepository
|
||||||
|
.vaultLastSyncStateFlow
|
||||||
|
.test {
|
||||||
|
assertNull(awaitItem())
|
||||||
|
fakeSettingsDiskSource.storeLastSyncTime(
|
||||||
|
userId = MOCK_USER_STATE.activeUserId,
|
||||||
|
lastSyncTime = instant,
|
||||||
|
)
|
||||||
|
assertEquals(instant, awaitItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `isIconLoadingDisabled should pull from and update SettingsDiskSource`() {
|
fun `isIconLoadingDisabled should pull from and update SettingsDiskSource`() {
|
||||||
assertFalse(settingsRepository.isIconLoadingDisabled)
|
assertFalse(settingsRepository.isIconLoadingDisabled)
|
||||||
|
|
|
@ -20,6 +20,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
|
import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
|
||||||
import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants.DEFAULT_PBKDF2_ITERATIONS
|
import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants.DEFAULT_PBKDF2_ITERATIONS
|
||||||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||||
|
@ -95,14 +96,20 @@ import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import java.net.UnknownHostException
|
import java.net.UnknownHostException
|
||||||
|
import java.time.Clock
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
|
||||||
@Suppress("LargeClass")
|
@Suppress("LargeClass")
|
||||||
class VaultRepositoryTest {
|
class VaultRepositoryTest {
|
||||||
|
private val clock: Clock = Clock.fixed(
|
||||||
|
Instant.parse("2023-10-27T12:00:00Z"),
|
||||||
|
ZoneOffset.UTC,
|
||||||
|
)
|
||||||
private val dispatcherManager: DispatcherManager = FakeDispatcherManager()
|
private val dispatcherManager: DispatcherManager = FakeDispatcherManager()
|
||||||
private val fileManager: FileManager = mockk()
|
private val fileManager: FileManager = mockk()
|
||||||
private val fakeAuthDiskSource = FakeAuthDiskSource()
|
private val fakeAuthDiskSource = FakeAuthDiskSource()
|
||||||
|
private val settingsDiskSource = mockk<SettingsDiskSource>()
|
||||||
private val syncService: SyncService = mockk()
|
private val syncService: SyncService = mockk()
|
||||||
private val sendsService: SendsService = mockk()
|
private val sendsService: SendsService = mockk()
|
||||||
private val ciphersService: CiphersService = mockk()
|
private val ciphersService: CiphersService = mockk()
|
||||||
|
@ -132,10 +139,12 @@ class VaultRepositoryTest {
|
||||||
vaultDiskSource = vaultDiskSource,
|
vaultDiskSource = vaultDiskSource,
|
||||||
vaultSdkSource = vaultSdkSource,
|
vaultSdkSource = vaultSdkSource,
|
||||||
authDiskSource = fakeAuthDiskSource,
|
authDiskSource = fakeAuthDiskSource,
|
||||||
|
settingsDiskSource = settingsDiskSource,
|
||||||
vaultLockManager = vaultLockManager,
|
vaultLockManager = vaultLockManager,
|
||||||
dispatcherManager = dispatcherManager,
|
dispatcherManager = dispatcherManager,
|
||||||
totpCodeManager = totpCodeManager,
|
totpCodeManager = totpCodeManager,
|
||||||
fileManager = fileManager,
|
fileManager = fileManager,
|
||||||
|
clock = clock,
|
||||||
)
|
)
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
@ -412,6 +421,9 @@ class VaultRepositoryTest {
|
||||||
vault = mockSyncResponse,
|
vault = mockSyncResponse,
|
||||||
)
|
)
|
||||||
} just runs
|
} just runs
|
||||||
|
every {
|
||||||
|
settingsDiskSource.storeLastSyncTime(MOCK_USER_STATE.activeUserId, clock.instant())
|
||||||
|
} just runs
|
||||||
|
|
||||||
vaultRepository.sync()
|
vaultRepository.sync()
|
||||||
|
|
||||||
|
@ -1051,6 +1063,9 @@ class VaultRepositoryTest {
|
||||||
vault = mockSyncResponse,
|
vault = mockSyncResponse,
|
||||||
)
|
)
|
||||||
} just runs
|
} just runs
|
||||||
|
every {
|
||||||
|
settingsDiskSource.storeLastSyncTime(MOCK_USER_STATE.activeUserId, clock.instant())
|
||||||
|
} just runs
|
||||||
coEvery {
|
coEvery {
|
||||||
vaultSdkSource.decryptSendList(
|
vaultSdkSource.decryptSendList(
|
||||||
userId = userId,
|
userId = userId,
|
||||||
|
@ -2217,6 +2232,9 @@ class VaultRepositoryTest {
|
||||||
vault = mockSyncResponse,
|
vault = mockSyncResponse,
|
||||||
)
|
)
|
||||||
} just runs
|
} just runs
|
||||||
|
every {
|
||||||
|
settingsDiskSource.storeLastSyncTime(MOCK_USER_STATE.activeUserId, clock.instant())
|
||||||
|
} just runs
|
||||||
|
|
||||||
val stateFlow = MutableStateFlow<DataState<List<VerificationCodeItem>>>(
|
val stateFlow = MutableStateFlow<DataState<List<VerificationCodeItem>>>(
|
||||||
DataState.Loading,
|
DataState.Loading,
|
||||||
|
|
|
@ -2,9 +2,11 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.other
|
||||||
|
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
|
import com.x8bit.bitwarden.R
|
||||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.just
|
import io.mockk.just
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
@ -14,11 +16,22 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.time.Clock
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
|
||||||
class OtherViewModelTest : BaseViewModelTest() {
|
class OtherViewModelTest : BaseViewModelTest() {
|
||||||
|
private val clock: Clock = Clock.fixed(
|
||||||
|
Instant.parse("2023-10-27T12:00:00Z"),
|
||||||
|
ZoneOffset.UTC,
|
||||||
|
)
|
||||||
private val mutablePullToRefreshStateFlow = MutableStateFlow(false)
|
private val mutablePullToRefreshStateFlow = MutableStateFlow(false)
|
||||||
|
private val instant: Instant = Instant.parse("2023-10-26T12:00:00Z")
|
||||||
|
private val mutableVaultLastSyncStateFlow = MutableStateFlow(instant)
|
||||||
private val settingsRepository = mockk<SettingsRepository> {
|
private val settingsRepository = mockk<SettingsRepository> {
|
||||||
every { getPullToRefreshEnabledFlow() } returns mutablePullToRefreshStateFlow
|
every { getPullToRefreshEnabledFlow() } returns mutablePullToRefreshStateFlow
|
||||||
|
every { vaultLastSyncStateFlow } returns mutableVaultLastSyncStateFlow
|
||||||
|
every { vaultLastSync } returns instant
|
||||||
}
|
}
|
||||||
private val vaultRepository = mockk<VaultRepository>()
|
private val vaultRepository = mockk<VaultRepository>()
|
||||||
|
|
||||||
|
@ -103,13 +116,38 @@ class OtherViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on VaultLastSyncReceive should sync repo`() = runTest {
|
||||||
|
val newSyncTime = Instant.parse("2023-10-27T12:00:00Z")
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
viewModel.stateFlow.test {
|
||||||
|
assertEquals(DEFAULT_STATE, awaitItem())
|
||||||
|
mutableVaultLastSyncStateFlow.tryEmit(newSyncTime)
|
||||||
|
assertEquals(
|
||||||
|
DEFAULT_STATE.copy(
|
||||||
|
lastSyncTime = "10/27/2023 12:00 PM",
|
||||||
|
dialogState = null,
|
||||||
|
),
|
||||||
|
awaitItem(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on SyncNowButtonClick should sync repo`() = runTest {
|
fun `on SyncNowButtonClick should sync repo`() = runTest {
|
||||||
every { vaultRepository.sync() } just runs
|
every { vaultRepository.sync() } just runs
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
viewModel.eventFlow.test {
|
viewModel.stateFlow.test {
|
||||||
|
assertEquals(DEFAULT_STATE, awaitItem())
|
||||||
viewModel.trySendAction(OtherAction.SyncNowButtonClick)
|
viewModel.trySendAction(OtherAction.SyncNowButtonClick)
|
||||||
expectNoEvents()
|
assertEquals(
|
||||||
|
DEFAULT_STATE.copy(
|
||||||
|
dialogState = OtherState.DialogState.Loading(
|
||||||
|
message = R.string.syncing.asText(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
awaitItem(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
verify { vaultRepository.sync() }
|
verify { vaultRepository.sync() }
|
||||||
}
|
}
|
||||||
|
@ -117,6 +155,7 @@ class OtherViewModelTest : BaseViewModelTest() {
|
||||||
private fun createViewModel(
|
private fun createViewModel(
|
||||||
state: OtherState? = null,
|
state: OtherState? = null,
|
||||||
) = OtherViewModel(
|
) = OtherViewModel(
|
||||||
|
clock = clock,
|
||||||
settingsRepo = settingsRepository,
|
settingsRepo = settingsRepository,
|
||||||
vaultRepo = vaultRepository,
|
vaultRepo = vaultRepository,
|
||||||
savedStateHandle = SavedStateHandle().apply {
|
savedStateHandle = SavedStateHandle().apply {
|
||||||
|
@ -129,7 +168,7 @@ class OtherViewModelTest : BaseViewModelTest() {
|
||||||
allowScreenCapture = false,
|
allowScreenCapture = false,
|
||||||
allowSyncOnRefresh = false,
|
allowSyncOnRefresh = false,
|
||||||
clearClipboardFrequency = OtherState.ClearClipboardFrequency.DEFAULT,
|
clearClipboardFrequency = OtherState.ClearClipboardFrequency.DEFAULT,
|
||||||
lastSyncTime = "5/14/2023 4:52 PM",
|
lastSyncTime = "10/26/2023 12:00 PM",
|
||||||
dialogState = null,
|
dialogState = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue