mirror of
https://github.com/bitwarden/android.git
synced 2026-08-24 14:28:58 -05:00
Improve clock usage patterns (#6336)
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
package com.x8bit.bitwarden.data.vault.repository
|
||||
|
||||
import com.bitwarden.core.DateTime
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
import com.bitwarden.exporters.ExportFormat
|
||||
import com.bitwarden.fido.Fido2CredentialAutofillView
|
||||
@@ -24,6 +23,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.time.Instant
|
||||
import javax.crypto.Cipher
|
||||
|
||||
/**
|
||||
@@ -135,7 +135,7 @@ interface VaultRepository :
|
||||
/**
|
||||
* Attempt to get the verification code and the period.
|
||||
*/
|
||||
suspend fun generateTotp(cipherId: String, time: DateTime): GenerateTotpResult
|
||||
suspend fun generateTotp(cipherId: String, time: Instant): GenerateTotpResult
|
||||
|
||||
/**
|
||||
* Attempt to get the user's vault data for export.
|
||||
|
||||
+2
-2
@@ -1,6 +1,5 @@
|
||||
package com.x8bit.bitwarden.data.vault.repository
|
||||
|
||||
import com.bitwarden.core.DateTime
|
||||
import com.bitwarden.core.InitUserCryptoMethod
|
||||
import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
|
||||
import com.bitwarden.core.data.repository.error.MissingPropertyException
|
||||
@@ -63,6 +62,7 @@ import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.security.GeneralSecurityException
|
||||
import java.time.Instant
|
||||
import javax.crypto.Cipher
|
||||
|
||||
/**
|
||||
@@ -408,7 +408,7 @@ class VaultRepositoryImpl(
|
||||
|
||||
override suspend fun generateTotp(
|
||||
cipherId: String,
|
||||
time: DateTime,
|
||||
time: Instant,
|
||||
): GenerateTotpResult {
|
||||
val userId = activeUserId
|
||||
?: return GenerateTotpResult.Error(error = NoActiveUserException())
|
||||
|
||||
+4
-3
@@ -3,7 +3,6 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.folders.addedit
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.core.DateTime
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
import com.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData
|
||||
@@ -25,6 +24,7 @@ import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.time.Clock
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val KEY_STATE = "state"
|
||||
@@ -37,6 +37,7 @@ private const val KEY_STATE = "state"
|
||||
@Suppress("TooManyFunctions", "LargeClass")
|
||||
class FolderAddEditViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val clock: Clock,
|
||||
private val vaultRepository: VaultRepository,
|
||||
private val relayManager: SnackbarRelayManager<SnackbarRelay>,
|
||||
) : BaseViewModel<FolderAddEditState, FolderAddEditEvent, FolderAddEditAction>(
|
||||
@@ -124,7 +125,7 @@ class FolderAddEditViewModel @Inject constructor(
|
||||
.orEmpty() +
|
||||
content.folderName,
|
||||
id = folderAddEditType.folderId,
|
||||
revisionDate = DateTime.now(),
|
||||
revisionDate = clock.instant(),
|
||||
),
|
||||
)
|
||||
sendAction(FolderAddEditAction.Internal.CreateFolderResultReceive(result))
|
||||
@@ -136,7 +137,7 @@ class FolderAddEditViewModel @Inject constructor(
|
||||
FolderView(
|
||||
name = content.folderName,
|
||||
id = folderAddEditType.folderId,
|
||||
revisionDate = DateTime.now(),
|
||||
revisionDate = clock.instant(),
|
||||
),
|
||||
)
|
||||
sendAction(FolderAddEditAction.Internal.UpdateFolderResultReceive(result))
|
||||
|
||||
+7
-8
@@ -5,7 +5,6 @@ import androidx.credentials.CreatePublicKeyCredentialRequest
|
||||
import androidx.credentials.provider.CallingAppInfo
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.core.DateTime
|
||||
import com.bitwarden.core.data.manager.toast.ToastManager
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
import com.bitwarden.core.data.repository.util.takeUntilLoaded
|
||||
@@ -415,7 +414,7 @@ class VaultAddEditViewModel @Inject constructor(
|
||||
handleCreatePublicKeyCredentialRequest(
|
||||
request = createPublicKeyCredentialRequest,
|
||||
callingAppInfo = this.callingAppInfo,
|
||||
cipherView = content.toCipherView(),
|
||||
cipherView = content.toCipherView(clock = clock),
|
||||
)
|
||||
return@onContent
|
||||
}
|
||||
@@ -431,7 +430,7 @@ class VaultAddEditViewModel @Inject constructor(
|
||||
is VaultAddEditType.EditItem -> {
|
||||
val result = vaultRepository.updateCipher(
|
||||
cipherId = vaultAddEditType.vaultItemId,
|
||||
cipherView = content.toCipherView(),
|
||||
cipherView = content.toCipherView(clock = clock),
|
||||
)
|
||||
sendAction(VaultAddEditAction.Internal.UpdateCipherResultReceive(result))
|
||||
}
|
||||
@@ -610,7 +609,7 @@ class VaultAddEditViewModel @Inject constructor(
|
||||
handleCreatePublicKeyCredentialRequest(
|
||||
request = createPublicKeyCredentialRequest,
|
||||
callingAppInfo = request.callingAppInfo,
|
||||
cipherView = content.toCipherView(),
|
||||
cipherView = content.toCipherView(clock = clock),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -641,7 +640,7 @@ class VaultAddEditViewModel @Inject constructor(
|
||||
handleCreatePublicKeyCredentialRequest(
|
||||
request = createPublicKeyCredentialRequest,
|
||||
callingAppInfo = request.callingAppInfo,
|
||||
cipherView = content.toCipherView(),
|
||||
cipherView = content.toCipherView(clock = clock),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -817,7 +816,7 @@ class VaultAddEditViewModel @Inject constructor(
|
||||
FolderView(
|
||||
name = action.newFolderName,
|
||||
id = null,
|
||||
revisionDate = DateTime.now(),
|
||||
revisionDate = clock.instant(),
|
||||
),
|
||||
)
|
||||
sendAction(VaultAddEditAction.Internal.AddFolderResultReceive(result = result))
|
||||
@@ -2191,11 +2190,11 @@ class VaultAddEditViewModel @Inject constructor(
|
||||
?.map { it.id }
|
||||
?.let {
|
||||
vaultRepository.createCipherInOrganization(
|
||||
cipherView = toCipherView(),
|
||||
cipherView = toCipherView(clock = clock),
|
||||
collectionIds = it,
|
||||
)
|
||||
}
|
||||
?: vaultRepository.createCipher(cipherView = toCipherView())
|
||||
?: vaultRepository.createCipher(cipherView = toCipherView(clock = clock))
|
||||
}
|
||||
|
||||
private fun List<VaultAddEditState.Owner>.toUpdatedOwners(
|
||||
|
||||
+24
-17
@@ -2,7 +2,6 @@
|
||||
|
||||
package com.x8bit.bitwarden.ui.vault.feature.vault.util
|
||||
|
||||
import com.bitwarden.core.DateTime
|
||||
import com.bitwarden.ui.platform.base.util.orNullIfBlank
|
||||
import com.bitwarden.vault.CardView
|
||||
import com.bitwarden.vault.CipherRepromptType
|
||||
@@ -23,12 +22,13 @@ import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
import com.x8bit.bitwarden.ui.vault.util.stringLongNameOrNull
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Transforms [VaultAddEditState.ViewState.Content] into [CipherView].
|
||||
*/
|
||||
fun VaultAddEditState.ViewState.Content.toCipherView(): CipherView =
|
||||
fun VaultAddEditState.ViewState.Content.toCipherView(clock: Clock): CipherView =
|
||||
CipherView(
|
||||
// Pulled from original cipher when editing, otherwise uses defaults
|
||||
id = common.originalCipher?.id,
|
||||
@@ -39,18 +39,18 @@ fun VaultAddEditState.ViewState.Content.toCipherView(): CipherView =
|
||||
localData = common.originalCipher?.localData,
|
||||
attachments = common.originalCipher?.attachments,
|
||||
organizationUseTotp = common.originalCipher?.organizationUseTotp ?: false,
|
||||
passwordHistory = toPasswordHistory(),
|
||||
passwordHistory = toPasswordHistory(clock = clock),
|
||||
permissions = common.originalCipher?.permissions,
|
||||
creationDate = common.originalCipher?.creationDate ?: Instant.now(),
|
||||
creationDate = common.originalCipher?.creationDate ?: clock.instant(),
|
||||
deletedDate = common.originalCipher?.deletedDate,
|
||||
revisionDate = common.originalCipher?.revisionDate ?: Instant.now(),
|
||||
revisionDate = common.originalCipher?.revisionDate ?: clock.instant(),
|
||||
archivedDate = common.originalCipher?.archivedDate,
|
||||
|
||||
// Type specific section
|
||||
type = type.toCipherType(),
|
||||
identity = type.toIdentityView(),
|
||||
secureNote = type.toSecureNotesView(),
|
||||
login = type.toLoginView(common = common),
|
||||
login = type.toLoginView(common = common, clock = clock),
|
||||
card = type.toCardView(),
|
||||
sshKey = type.toSshKeyView(),
|
||||
|
||||
@@ -134,21 +134,23 @@ private fun VaultAddEditState.ViewState.Content.ItemType.toIdentityView(): Ident
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun VaultAddEditState.ViewState.Content.toPasswordHistory(): List<PasswordHistoryView>? {
|
||||
private fun VaultAddEditState.ViewState.Content.toPasswordHistory(
|
||||
clock: Clock,
|
||||
): List<PasswordHistoryView>? {
|
||||
val loginCipher = type as? VaultAddEditState.ViewState.Content.ItemType.Login
|
||||
val oldPassword = common.originalCipher?.login?.password
|
||||
val timestamp = Instant.now()
|
||||
val timestamp = clock.instant()
|
||||
|
||||
val newPasswordHistory = getPasswordHistory(
|
||||
loginCipher,
|
||||
oldPassword,
|
||||
timestamp,
|
||||
loginCipher = loginCipher,
|
||||
oldPassword = oldPassword,
|
||||
timestamp = timestamp,
|
||||
)
|
||||
|
||||
val newHiddenFieldHistory = getHiddenFieldHistory(
|
||||
timestamp,
|
||||
common.originalCipher?.fields,
|
||||
common.customFieldData,
|
||||
timestamp = timestamp,
|
||||
oldFields = common.originalCipher?.fields,
|
||||
newFields = common.customFieldData,
|
||||
)
|
||||
|
||||
return listOf(
|
||||
@@ -204,12 +206,16 @@ private fun getHiddenFieldHistory(
|
||||
|
||||
private fun VaultAddEditState.ViewState.Content.ItemType.toLoginView(
|
||||
common: VaultAddEditState.ViewState.Content.Common,
|
||||
clock: Clock,
|
||||
): LoginView? =
|
||||
(this as? VaultAddEditState.ViewState.Content.ItemType.Login)?.let {
|
||||
LoginView(
|
||||
username = it.username.orNullIfBlank(),
|
||||
password = it.password.orNullIfBlank(),
|
||||
passwordRevisionDate = it.getRevisionDate(common.originalCipher),
|
||||
passwordRevisionDate = it.getRevisionDate(
|
||||
originalCipher = common.originalCipher,
|
||||
clock = clock,
|
||||
),
|
||||
uris = it.uriList.toLoginUriView(),
|
||||
totp = it.totp,
|
||||
autofillOnPageLoad = common.originalCipher?.login?.autofillOnPageLoad,
|
||||
@@ -280,12 +286,13 @@ private fun List<UriItem>?.toLoginUriView(): List<LoginUriView>? =
|
||||
|
||||
private fun VaultAddEditState.ViewState.Content.ItemType.Login.getRevisionDate(
|
||||
originalCipher: CipherView?,
|
||||
): DateTime? {
|
||||
clock: Clock,
|
||||
): Instant? {
|
||||
val isOriginalPasswordNull = originalCipher?.login?.password.isNullOrEmpty()
|
||||
val hasPasswordHistory = originalCipher?.passwordHistory?.any() ?: false
|
||||
val isOriginalAndNewPasswordEqual = originalCipher?.login?.password == this.password
|
||||
return if ((!isOriginalPasswordNull || hasPasswordHistory) && !isOriginalAndNewPasswordEqual) {
|
||||
Instant.now()
|
||||
clock.instant()
|
||||
} else {
|
||||
originalCipher?.login?.passwordRevisionDate
|
||||
}
|
||||
|
||||
+2
-2
@@ -47,7 +47,7 @@ class PushDiskSourceTest {
|
||||
)
|
||||
pushDiskSource.storeLastPushTokenRegistrationDate(
|
||||
userId = userId,
|
||||
registrationDate = ZonedDateTime.now(),
|
||||
registrationDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||
)
|
||||
|
||||
pushDiskSource.clearData(userId = userId)
|
||||
@@ -138,7 +138,7 @@ class PushDiskSourceTest {
|
||||
fun `storeLastPushTokenRegistrationDate for null values should clear SharedPreferences`() {
|
||||
val lastPushTokenBaseKey = "bwPreferencesStorage:pushLastRegistrationDate"
|
||||
val mockUserId = "mockUserId"
|
||||
val mockLastPushTokenRegistration = ZonedDateTime.now()
|
||||
val mockLastPushTokenRegistration = ZonedDateTime.parse("2023-10-27T12:00:00Z")
|
||||
val lastPushTokenKey = "${lastPushTokenBaseKey}_$mockUserId"
|
||||
fakeSharedPreferences.edit {
|
||||
putLong(lastPushTokenKey, mockLastPushTokenRegistration.toEpochSecond())
|
||||
|
||||
+1
-1
@@ -506,7 +506,7 @@ private val USER_1_TOTP_CIPHER = mockk<SyncResponseJson.Cipher> {
|
||||
private val USER_1_DELETED_TOTP_CIPHER = mockk<SyncResponseJson.Cipher> {
|
||||
every { login?.totp } returns "encryptedTotp1Deleted"
|
||||
every { login?.username } returns "username"
|
||||
every { deletedDate } returns ZonedDateTime.now()
|
||||
every { deletedDate } returns ZonedDateTime.parse("2023-10-27T12:00:00Z")
|
||||
every { name } returns "cipher1"
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ fun createMockFido2CredentialView(number: Int): Fido2CredentialView = Fido2Crede
|
||||
rpName = "mockRpName-$number",
|
||||
userDisplayName = "mockUserDisplayName-$number",
|
||||
discoverable = "mockDiscoverable-$number",
|
||||
creationDate = Instant.now(),
|
||||
creationDate = Instant.parse("2023-10-27T12:00:00Z"),
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
@@ -933,7 +933,6 @@ class CipherManagerTest {
|
||||
)
|
||||
.toEncryptedNetworkCipherResponse()
|
||||
} returns createMockCipher(number = 1)
|
||||
val fixedInstant = Instant.parse("2021-01-01T00:00:00Z")
|
||||
val userId = "mockId-1"
|
||||
val cipherId = "mockId-1"
|
||||
val attachmentId = "mockId-1"
|
||||
@@ -960,8 +959,6 @@ class CipherManagerTest {
|
||||
)
|
||||
} just runs
|
||||
val cipherView = createMockCipherView(number = 1)
|
||||
mockkStatic(Instant::class)
|
||||
every { Instant.now() } returns fixedInstant
|
||||
|
||||
val result = cipherManager.deleteCipherAttachment(
|
||||
cipherId = cipherId,
|
||||
@@ -970,7 +967,6 @@ class CipherManagerTest {
|
||||
)
|
||||
|
||||
assertEquals(DeleteAttachmentResult.Success, result)
|
||||
unmockkStatic(Instant::class)
|
||||
unmockkStatic(Cipher::toEncryptedNetworkCipherResponse)
|
||||
}
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ class FolderManagerTest {
|
||||
val folderView = FolderView(
|
||||
id = null,
|
||||
name = "TestName",
|
||||
revisionDate = Instant.now(FIXED_CLOCK),
|
||||
revisionDate = FIXED_CLOCK.instant(),
|
||||
)
|
||||
val error = IllegalStateException()
|
||||
|
||||
@@ -130,7 +130,7 @@ class FolderManagerTest {
|
||||
@Test
|
||||
fun `createFolder with folderService failure should return CreateFolderResult failure`() =
|
||||
runTest {
|
||||
val date = Instant.now(FIXED_CLOCK)
|
||||
val date = FIXED_CLOCK.instant()
|
||||
val testFolderName = "TestName"
|
||||
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
@@ -157,7 +157,7 @@ class FolderManagerTest {
|
||||
fun `createFolder with folderService createFolder should return CreateFolderResult success`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val date = Instant.now(FIXED_CLOCK)
|
||||
val date = FIXED_CLOCK.instant()
|
||||
val testFolderName = "TestName"
|
||||
val folderView = FolderView(
|
||||
id = null,
|
||||
@@ -270,7 +270,7 @@ class FolderManagerTest {
|
||||
val folderView = FolderView(
|
||||
id = folderId,
|
||||
name = "TestName",
|
||||
revisionDate = Instant.now(FIXED_CLOCK),
|
||||
revisionDate = FIXED_CLOCK.instant(),
|
||||
)
|
||||
val error = IllegalStateException()
|
||||
|
||||
@@ -286,7 +286,7 @@ class FolderManagerTest {
|
||||
@Test
|
||||
fun `updateFolder with folderService failure should return UpdateFolderResult failure`() =
|
||||
runTest {
|
||||
val date = Instant.now(FIXED_CLOCK)
|
||||
val date = FIXED_CLOCK.instant()
|
||||
val testFolderName = "TestName"
|
||||
val folderId = "testId"
|
||||
|
||||
@@ -317,7 +317,7 @@ class FolderManagerTest {
|
||||
@Test
|
||||
fun `updateFolder with folderService updateFolder Invalid response should return UpdateFolderResult Error with a non-null message`() =
|
||||
runTest {
|
||||
val date = Instant.now(FIXED_CLOCK)
|
||||
val date = FIXED_CLOCK.instant()
|
||||
val testFolderName = "TestName"
|
||||
val folderId = "testId"
|
||||
|
||||
@@ -358,7 +358,7 @@ class FolderManagerTest {
|
||||
@Test
|
||||
fun `updateFolder with folderService updateFolder success should return UpdateFolderResult success`() =
|
||||
runTest {
|
||||
val date = Instant.now(FIXED_CLOCK)
|
||||
val date = FIXED_CLOCK.instant()
|
||||
val testFolderName = "TestName"
|
||||
val folderId = "testId"
|
||||
|
||||
|
||||
+3
-3
@@ -2,7 +2,6 @@ package com.x8bit.bitwarden.data.vault.repository
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.collections.CollectionView
|
||||
import com.bitwarden.core.DateTime
|
||||
import com.bitwarden.core.InitUserCryptoMethod
|
||||
import com.bitwarden.core.MasterPasswordUnlockData
|
||||
import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
|
||||
@@ -77,6 +76,7 @@ import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.security.GeneralSecurityException
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
import javax.crypto.BadPaddingException
|
||||
import javax.crypto.Cipher
|
||||
@@ -996,7 +996,7 @@ class VaultRepositoryTest {
|
||||
|
||||
val result = vaultRepository.generateTotp(
|
||||
cipherId = "totpCode",
|
||||
time = DateTime.now(),
|
||||
time = Instant.parse("2023-10-27T12:00:00Z"),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
@@ -1023,7 +1023,7 @@ class VaultRepositoryTest {
|
||||
|
||||
val result = vaultRepository.generateTotp(
|
||||
cipherId = "mockId-1",
|
||||
time = DateTime.now(),
|
||||
time = Instant.parse("2023-10-27T12:00:00Z"),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
||||
+4
-4
@@ -263,7 +263,7 @@ class PendingRequestsViewModelTest : BaseViewModelTest() {
|
||||
ipAddress = "192.168.0.2",
|
||||
key = "publicKey",
|
||||
masterPasswordHash = "verySecureHash",
|
||||
creationDate = ZonedDateTime.now().minusMinutes(5),
|
||||
creationDate = ZonedDateTime.now(fixedClock).minusMinutes(5),
|
||||
responseDate = null,
|
||||
requestApproved = false,
|
||||
originUrl = "www.bitwarden.com",
|
||||
@@ -276,7 +276,7 @@ class PendingRequestsViewModelTest : BaseViewModelTest() {
|
||||
ipAddress = "192.168.0.3",
|
||||
key = "publicKey",
|
||||
masterPasswordHash = "verySecureHash",
|
||||
creationDate = ZonedDateTime.now(),
|
||||
creationDate = ZonedDateTime.now(fixedClock),
|
||||
responseDate = null,
|
||||
requestApproved = false,
|
||||
originUrl = "www.bitwarden.com",
|
||||
@@ -291,7 +291,7 @@ class PendingRequestsViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
} returns AuthRequestResult.Success(
|
||||
authRequest1.copy(
|
||||
responseDate = ZonedDateTime.now(),
|
||||
responseDate = ZonedDateTime.now(fixedClock),
|
||||
),
|
||||
)
|
||||
coEvery {
|
||||
@@ -303,7 +303,7 @@ class PendingRequestsViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
} returns AuthRequestResult.Success(
|
||||
authRequest2.copy(
|
||||
responseDate = ZonedDateTime.now(),
|
||||
responseDate = ZonedDateTime.now(fixedClock),
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
|
||||
+6
-2
@@ -1,7 +1,6 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.settings.folders
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.DateTime
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
@@ -20,6 +19,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.Instant
|
||||
|
||||
class FoldersViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@@ -195,5 +195,9 @@ class FoldersViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
}
|
||||
|
||||
private val DEFAULT_FOLDER_VIEW = FolderView("1", "test", revisionDate = DateTime.now())
|
||||
private val DEFAULT_FOLDER_VIEW = FolderView(
|
||||
id = "1",
|
||||
name = "test",
|
||||
revisionDate = Instant.parse("2025-04-11T10:15:30.00Z"),
|
||||
)
|
||||
private val DEFAULT__DISPLAY_FOLDER = FolderDisplayItem("1", "test")
|
||||
|
||||
+31
-30
@@ -2,7 +2,6 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.folders.addedit
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.DateTime
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
import com.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData
|
||||
@@ -32,7 +31,9 @@ import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class FolderAddEditViewModelTest : BaseViewModelTest() {
|
||||
@@ -120,9 +121,9 @@ class FolderAddEditViewModelTest : BaseViewModelTest() {
|
||||
mutableFoldersStateFlow.value =
|
||||
DataState.Loaded(
|
||||
FolderView(
|
||||
DEFAULT_EDIT_ITEM_ID,
|
||||
DEFAULT_FOLDER_NAME,
|
||||
DateTime.now(),
|
||||
id = DEFAULT_EDIT_ITEM_ID,
|
||||
name = DEFAULT_FOLDER_NAME,
|
||||
revisionDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -174,9 +175,9 @@ class FolderAddEditViewModelTest : BaseViewModelTest() {
|
||||
mutableFoldersStateFlow.value =
|
||||
DataState.Loaded(
|
||||
FolderView(
|
||||
DEFAULT_EDIT_ITEM_ID,
|
||||
DEFAULT_FOLDER_NAME,
|
||||
DateTime.now(),
|
||||
id = DEFAULT_EDIT_ITEM_ID,
|
||||
name = DEFAULT_FOLDER_NAME,
|
||||
revisionDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -253,9 +254,9 @@ class FolderAddEditViewModelTest : BaseViewModelTest() {
|
||||
mutableFoldersStateFlow.value =
|
||||
DataState.Loaded(
|
||||
FolderView(
|
||||
DEFAULT_EDIT_ITEM_ID,
|
||||
DEFAULT_FOLDER_NAME,
|
||||
DateTime.now(),
|
||||
id = DEFAULT_EDIT_ITEM_ID,
|
||||
name = DEFAULT_FOLDER_NAME,
|
||||
revisionDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -350,8 +351,6 @@ class FolderAddEditViewModelTest : BaseViewModelTest() {
|
||||
@Test
|
||||
fun `in add mode, SaveClick createFolder with no parentFolderNamePresent should just create folder with entered name`() =
|
||||
runTest {
|
||||
mockkStatic(DateTime::class)
|
||||
every { DateTime.now() } returns Instant.MIN
|
||||
val viewModel =
|
||||
createViewModel(
|
||||
createSavedStateHandleWithState(
|
||||
@@ -375,7 +374,7 @@ class FolderAddEditViewModelTest : BaseViewModelTest() {
|
||||
folderView = FolderView(
|
||||
name = DEFAULT_FOLDER_NAME,
|
||||
id = null,
|
||||
revisionDate = Instant.MIN,
|
||||
revisionDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -385,15 +384,12 @@ class FolderAddEditViewModelTest : BaseViewModelTest() {
|
||||
relay = SnackbarRelay.FOLDER_CREATED,
|
||||
)
|
||||
}
|
||||
unmockkStatic(DateTime::class)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in add mode, SaveClick createFolder with a parentFolderNamePresent should prepend the parent folder to the entered name`() =
|
||||
runTest {
|
||||
mockkStatic(DateTime::class)
|
||||
every { DateTime.now() } returns Instant.MIN
|
||||
val parentFolderName = "parent/folder"
|
||||
val viewModel =
|
||||
createViewModel(
|
||||
@@ -418,7 +414,7 @@ class FolderAddEditViewModelTest : BaseViewModelTest() {
|
||||
folderView = FolderView(
|
||||
name = "$parentFolderName/$DEFAULT_FOLDER_NAME",
|
||||
id = null,
|
||||
revisionDate = Instant.MIN,
|
||||
revisionDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -428,7 +424,6 @@ class FolderAddEditViewModelTest : BaseViewModelTest() {
|
||||
relay = SnackbarRelay.FOLDER_CREATED,
|
||||
)
|
||||
}
|
||||
unmockkStatic(DateTime::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -497,9 +492,9 @@ class FolderAddEditViewModelTest : BaseViewModelTest() {
|
||||
mutableFoldersStateFlow.value =
|
||||
DataState.Loaded(
|
||||
FolderView(
|
||||
DEFAULT_EDIT_ITEM_ID,
|
||||
DEFAULT_FOLDER_NAME,
|
||||
DateTime.now(),
|
||||
id = DEFAULT_EDIT_ITEM_ID,
|
||||
name = DEFAULT_FOLDER_NAME,
|
||||
revisionDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -542,9 +537,9 @@ class FolderAddEditViewModelTest : BaseViewModelTest() {
|
||||
mutableFoldersStateFlow.value =
|
||||
DataState.Loaded(
|
||||
FolderView(
|
||||
DEFAULT_EDIT_ITEM_ID,
|
||||
DEFAULT_FOLDER_NAME,
|
||||
DateTime.now(),
|
||||
id = DEFAULT_EDIT_ITEM_ID,
|
||||
name = DEFAULT_FOLDER_NAME,
|
||||
revisionDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -640,9 +635,9 @@ class FolderAddEditViewModelTest : BaseViewModelTest() {
|
||||
mutableFoldersStateFlow.tryEmit(
|
||||
DataState.Loaded(
|
||||
FolderView(
|
||||
DEFAULT_EDIT_ITEM_ID,
|
||||
DEFAULT_FOLDER_NAME,
|
||||
revisionDate = DateTime.now(),
|
||||
id = DEFAULT_EDIT_ITEM_ID,
|
||||
name = DEFAULT_FOLDER_NAME,
|
||||
revisionDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -738,9 +733,9 @@ class FolderAddEditViewModelTest : BaseViewModelTest() {
|
||||
mutableFoldersStateFlow.tryEmit(
|
||||
value = DataState.Pending(
|
||||
FolderView(
|
||||
DEFAULT_EDIT_ITEM_ID,
|
||||
DEFAULT_FOLDER_NAME,
|
||||
revisionDate = DateTime.now(),
|
||||
id = DEFAULT_EDIT_ITEM_ID,
|
||||
name = DEFAULT_FOLDER_NAME,
|
||||
revisionDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -793,6 +788,7 @@ class FolderAddEditViewModelTest : BaseViewModelTest() {
|
||||
savedStateHandle: SavedStateHandle = createSavedStateHandleWithState(),
|
||||
): FolderAddEditViewModel = FolderAddEditViewModel(
|
||||
savedStateHandle = savedStateHandle,
|
||||
clock = FIXED_CLOCK,
|
||||
vaultRepository = vaultRepository,
|
||||
relayManager = relayManager,
|
||||
)
|
||||
@@ -805,5 +801,10 @@ private val DEFAULT_STATE = FolderAddEditState(
|
||||
parentFolderName = null,
|
||||
)
|
||||
|
||||
private val FIXED_CLOCK = Clock.fixed(
|
||||
Instant.parse("2025-04-11T10:15:30.00Z"),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
|
||||
private const val DEFAULT_EDIT_ITEM_ID = "edit_id"
|
||||
private const val DEFAULT_FOLDER_NAME = "test_name"
|
||||
|
||||
+2
-2
@@ -187,7 +187,7 @@ class PasswordHistoryViewModelTest : BaseViewModelTest() {
|
||||
fun `when password history updates the state updates correctly`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
||||
val passwordHistoryView = PasswordHistoryView("password", Instant.now())
|
||||
val passwordHistoryView = PasswordHistoryView("password", fixedClock.instant())
|
||||
fakeGeneratorRepository.storePasswordHistory(passwordHistoryView)
|
||||
|
||||
val expectedState = createPasswordHistoryState(
|
||||
@@ -241,7 +241,7 @@ class PasswordHistoryViewModelTest : BaseViewModelTest() {
|
||||
fun `PasswordClearClick action should update to Empty ViewState`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
||||
val passwordHistoryView = PasswordHistoryView("password", Instant.now())
|
||||
val passwordHistoryView = PasswordHistoryView("password", fixedClock.instant())
|
||||
fakeGeneratorRepository.storePasswordHistory(passwordHistoryView)
|
||||
|
||||
viewModel.trySendAction(PasswordHistoryAction.PasswordClearClick)
|
||||
|
||||
+4
-11
@@ -7,7 +7,6 @@ import androidx.credentials.provider.ProviderCreateCredentialRequest
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.collections.CollectionView
|
||||
import com.bitwarden.core.DateTime
|
||||
import com.bitwarden.core.data.manager.dispatcher.FakeDispatcherManager
|
||||
import com.bitwarden.core.data.manager.toast.ToastManager
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
@@ -3363,14 +3362,11 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
fun `AddNewFolder action calls create folder from vault repository`() = runTest {
|
||||
mockkStatic(DateTime::class)
|
||||
every { DateTime.now() } returns Instant.MIN
|
||||
|
||||
val folderName = "folderName"
|
||||
val expectedFolderResult = FolderView(
|
||||
id = "123",
|
||||
name = folderName,
|
||||
revisionDate = DateTime.now(),
|
||||
revisionDate = fixedClock.instant(),
|
||||
)
|
||||
coEvery {
|
||||
vaultRepository.createFolder(any())
|
||||
@@ -3381,23 +3377,20 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
FolderView(
|
||||
name = folderName,
|
||||
id = null,
|
||||
revisionDate = Instant.MIN,
|
||||
revisionDate = fixedClock.instant(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
unmockkStatic(DateTime::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AddNewFolder updates dialog states and selected folder id on success`() = runTest {
|
||||
|
||||
val folderId = "123"
|
||||
val folderName = "folderName"
|
||||
val expectedFolderResult = FolderView(
|
||||
id = folderId,
|
||||
name = folderName,
|
||||
revisionDate = DateTime.now(),
|
||||
revisionDate = fixedClock.instant(),
|
||||
)
|
||||
coEvery {
|
||||
vaultRepository.createFolder(any())
|
||||
@@ -3433,7 +3426,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
data = listOf(
|
||||
FolderView(
|
||||
name = "folder",
|
||||
revisionDate = DateTime.now(),
|
||||
revisionDate = fixedClock.instant(),
|
||||
id = null,
|
||||
),
|
||||
),
|
||||
|
||||
+63
-76
@@ -21,30 +21,19 @@ import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNotEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class VaultAddItemStateExtensionsTest {
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
// Some individual tests call mockkStatic so we will make sure this is always undone.
|
||||
unmockkStatic(Instant::class)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toCipherView should transform Login ItemType to CipherView`() {
|
||||
mockkStatic(Instant::class)
|
||||
every { Instant.now() } returns Instant.MIN
|
||||
|
||||
val loginItemType = VaultAddEditState.ViewState.Content(
|
||||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
name = "mockName-1",
|
||||
@@ -71,7 +60,7 @@ class VaultAddItemStateExtensionsTest {
|
||||
),
|
||||
)
|
||||
|
||||
val result = loginItemType.toCipherView()
|
||||
val result = loginItemType.toCipherView(clock = FIXED_CLOCK)
|
||||
|
||||
assertEquals(
|
||||
CipherView(
|
||||
@@ -111,9 +100,9 @@ class VaultAddItemStateExtensionsTest {
|
||||
fields = emptyList(),
|
||||
passwordHistory = null,
|
||||
permissions = null,
|
||||
creationDate = Instant.MIN,
|
||||
creationDate = FIXED_CLOCK.instant(),
|
||||
deletedDate = null,
|
||||
revisionDate = Instant.MIN,
|
||||
revisionDate = FIXED_CLOCK.instant(),
|
||||
archivedDate = null,
|
||||
sshKey = null,
|
||||
),
|
||||
@@ -124,8 +113,6 @@ class VaultAddItemStateExtensionsTest {
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toCipherView should transform Login ItemType to CipherView with original cipher`() {
|
||||
mockkStatic(Instant::class)
|
||||
every { Instant.now() } returns Instant.MIN
|
||||
val cipherView = DEFAULT_LOGIN_CIPHER_VIEW
|
||||
val viewState = VaultAddEditState.ViewState.Content(
|
||||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
@@ -158,7 +145,7 @@ class VaultAddItemStateExtensionsTest {
|
||||
),
|
||||
)
|
||||
|
||||
val result = viewState.toCipherView()
|
||||
val result = viewState.toCipherView(clock = FIXED_CLOCK)
|
||||
|
||||
assertEquals(
|
||||
@Suppress("MaxLineLength")
|
||||
@@ -171,7 +158,7 @@ class VaultAddItemStateExtensionsTest {
|
||||
login = LoginView(
|
||||
username = "mockUsername-1",
|
||||
password = "mockPassword-1",
|
||||
passwordRevisionDate = Instant.MIN,
|
||||
passwordRevisionDate = FIXED_CLOCK.instant(),
|
||||
uris = listOf(
|
||||
LoginUriView(
|
||||
uri = "mockUri-1",
|
||||
@@ -214,15 +201,15 @@ class VaultAddItemStateExtensionsTest {
|
||||
passwordHistory = listOf(
|
||||
PasswordHistoryView(
|
||||
password = "old_password",
|
||||
lastUsedDate = Instant.MIN,
|
||||
lastUsedDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
PasswordHistoryView(
|
||||
password = "password",
|
||||
lastUsedDate = Instant.MIN,
|
||||
lastUsedDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
PasswordHistoryView(
|
||||
password = "hidden: value",
|
||||
lastUsedDate = Instant.MIN,
|
||||
lastUsedDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -232,8 +219,6 @@ class VaultAddItemStateExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `toCipherView should transform SecureNotes ItemType to CipherView`() {
|
||||
mockkStatic(Instant::class)
|
||||
every { Instant.now() } returns Instant.MIN
|
||||
val viewState = VaultAddEditState.ViewState.Content(
|
||||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
name = "mockName-1",
|
||||
@@ -252,7 +237,7 @@ class VaultAddItemStateExtensionsTest {
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.SecureNotes,
|
||||
)
|
||||
|
||||
val result = viewState.toCipherView()
|
||||
val result = viewState.toCipherView(clock = FIXED_CLOCK)
|
||||
|
||||
assertEquals(
|
||||
CipherView(
|
||||
@@ -297,9 +282,9 @@ class VaultAddItemStateExtensionsTest {
|
||||
),
|
||||
passwordHistory = null,
|
||||
permissions = null,
|
||||
creationDate = Instant.MIN,
|
||||
creationDate = FIXED_CLOCK.instant(),
|
||||
deletedDate = null,
|
||||
revisionDate = Instant.MIN,
|
||||
revisionDate = FIXED_CLOCK.instant(),
|
||||
archivedDate = null,
|
||||
sshKey = null,
|
||||
),
|
||||
@@ -309,8 +294,6 @@ class VaultAddItemStateExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `toCipherView should transform SecureNotes ItemType to CipherView with original cipher`() {
|
||||
mockkStatic(Instant::class)
|
||||
every { Instant.now() } returns Instant.MIN
|
||||
val cipherView = DEFAULT_SECURE_NOTES_CIPHER_VIEW.copy(passwordHistory = null)
|
||||
val viewState = VaultAddEditState.ViewState.Content(
|
||||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
@@ -327,7 +310,7 @@ class VaultAddItemStateExtensionsTest {
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.SecureNotes,
|
||||
)
|
||||
|
||||
val result = viewState.toCipherView()
|
||||
val result = viewState.toCipherView(clock = FIXED_CLOCK)
|
||||
|
||||
assertEquals(
|
||||
cipherView.copy(
|
||||
@@ -342,7 +325,7 @@ class VaultAddItemStateExtensionsTest {
|
||||
passwordHistory = listOf(
|
||||
PasswordHistoryView(
|
||||
password = "hidden: value",
|
||||
lastUsedDate = Instant.MIN,
|
||||
lastUsedDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -352,8 +335,6 @@ class VaultAddItemStateExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `toCipherView should transform Identity ItemType to CipherView`() {
|
||||
mockkStatic(Instant::class)
|
||||
every { Instant.now() } returns Instant.MIN
|
||||
val viewState = VaultAddEditState.ViewState.Content(
|
||||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
name = "mockName-1",
|
||||
@@ -386,7 +367,7 @@ class VaultAddItemStateExtensionsTest {
|
||||
),
|
||||
)
|
||||
|
||||
val result = viewState.toCipherView()
|
||||
val result = viewState.toCipherView(clock = FIXED_CLOCK)
|
||||
|
||||
assertEquals(
|
||||
CipherView(
|
||||
@@ -431,9 +412,9 @@ class VaultAddItemStateExtensionsTest {
|
||||
fields = emptyList(),
|
||||
passwordHistory = null,
|
||||
permissions = null,
|
||||
creationDate = Instant.MIN,
|
||||
creationDate = FIXED_CLOCK.instant(),
|
||||
deletedDate = null,
|
||||
revisionDate = Instant.MIN,
|
||||
revisionDate = FIXED_CLOCK.instant(),
|
||||
archivedDate = null,
|
||||
sshKey = null,
|
||||
),
|
||||
@@ -443,8 +424,6 @@ class VaultAddItemStateExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `toCipherView should transform Identity ItemType to CipherView with original cipher`() {
|
||||
mockkStatic(Instant::class)
|
||||
every { Instant.now() } returns Instant.MIN
|
||||
val cipherView = DEFAULT_IDENTITY_CIPHER_VIEW
|
||||
val viewState = VaultAddEditState.ViewState.Content(
|
||||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
@@ -489,7 +468,7 @@ class VaultAddItemStateExtensionsTest {
|
||||
),
|
||||
)
|
||||
|
||||
val result = viewState.toCipherView()
|
||||
val result = viewState.toCipherView(clock = FIXED_CLOCK)
|
||||
|
||||
assertEquals(
|
||||
@Suppress("MaxLineLength")
|
||||
@@ -550,11 +529,11 @@ class VaultAddItemStateExtensionsTest {
|
||||
passwordHistory = listOf(
|
||||
PasswordHistoryView(
|
||||
password = "old_password",
|
||||
lastUsedDate = Instant.MIN,
|
||||
lastUsedDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
PasswordHistoryView(
|
||||
password = "hidden: value",
|
||||
lastUsedDate = Instant.MIN,
|
||||
lastUsedDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -564,8 +543,6 @@ class VaultAddItemStateExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `toCipherView should transform Card ItemType to CipherView`() {
|
||||
mockkStatic(Instant::class)
|
||||
every { Instant.now() } returns Instant.MIN
|
||||
val viewState = VaultAddEditState.ViewState.Content(
|
||||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
name = "mockName-1",
|
||||
@@ -586,7 +563,7 @@ class VaultAddItemStateExtensionsTest {
|
||||
),
|
||||
)
|
||||
|
||||
val result = viewState.toCipherView()
|
||||
val result = viewState.toCipherView(clock = FIXED_CLOCK)
|
||||
|
||||
assertEquals(
|
||||
CipherView(
|
||||
@@ -619,9 +596,9 @@ class VaultAddItemStateExtensionsTest {
|
||||
fields = emptyList(),
|
||||
passwordHistory = null,
|
||||
permissions = null,
|
||||
creationDate = Instant.MIN,
|
||||
creationDate = FIXED_CLOCK.instant(),
|
||||
deletedDate = null,
|
||||
revisionDate = Instant.MIN,
|
||||
revisionDate = FIXED_CLOCK.instant(),
|
||||
archivedDate = null,
|
||||
sshKey = null,
|
||||
),
|
||||
@@ -631,8 +608,6 @@ class VaultAddItemStateExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `toCipherView should transform Card ItemType to CipherView with original cipher`() {
|
||||
mockkStatic(Instant::class)
|
||||
every { Instant.now() } returns Instant.MIN
|
||||
val cipherView = DEFAULT_CARD_CIPHER_VIEW
|
||||
val viewState = VaultAddEditState.ViewState.Content(
|
||||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
@@ -665,7 +640,7 @@ class VaultAddItemStateExtensionsTest {
|
||||
),
|
||||
)
|
||||
|
||||
val result = viewState.toCipherView()
|
||||
val result = viewState.toCipherView(clock = FIXED_CLOCK)
|
||||
|
||||
assertEquals(
|
||||
cipherView.copy(
|
||||
@@ -704,11 +679,11 @@ class VaultAddItemStateExtensionsTest {
|
||||
passwordHistory = listOf(
|
||||
PasswordHistoryView(
|
||||
password = "old_password",
|
||||
lastUsedDate = Instant.MIN,
|
||||
lastUsedDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
PasswordHistoryView(
|
||||
password = "hidden: value",
|
||||
lastUsedDate = Instant.MIN,
|
||||
lastUsedDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -718,8 +693,6 @@ class VaultAddItemStateExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `toCipherView should transform SSH Key ItemType to CipherView`() {
|
||||
mockkStatic(Instant::class)
|
||||
every { Instant.now() } returns Instant.MIN
|
||||
val viewState = VaultAddEditState.ViewState.Content(
|
||||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
name = "mockName-1",
|
||||
@@ -737,7 +710,7 @@ class VaultAddItemStateExtensionsTest {
|
||||
),
|
||||
)
|
||||
|
||||
val result = viewState.toCipherView()
|
||||
val result = viewState.toCipherView(clock = FIXED_CLOCK)
|
||||
|
||||
assertEquals(
|
||||
CipherView(
|
||||
@@ -763,9 +736,9 @@ class VaultAddItemStateExtensionsTest {
|
||||
fields = emptyList(),
|
||||
passwordHistory = null,
|
||||
permissions = null,
|
||||
creationDate = Instant.MIN,
|
||||
creationDate = FIXED_CLOCK.instant(),
|
||||
deletedDate = null,
|
||||
revisionDate = Instant.MIN,
|
||||
revisionDate = FIXED_CLOCK.instant(),
|
||||
archivedDate = null,
|
||||
sshKey = SshKeyView(
|
||||
publicKey = "mockPublicKey-1",
|
||||
@@ -787,7 +760,7 @@ class VaultAddItemStateExtensionsTest {
|
||||
login = LoginView(
|
||||
username = "mockUsername-1",
|
||||
password = "mockPassword-1",
|
||||
passwordRevisionDate = Instant.MIN,
|
||||
passwordRevisionDate = FIXED_CLOCK.instant(),
|
||||
uris = null,
|
||||
totp = null,
|
||||
autofillOnPageLoad = false,
|
||||
@@ -811,7 +784,7 @@ class VaultAddItemStateExtensionsTest {
|
||||
),
|
||||
)
|
||||
|
||||
val result = viewState.toCipherView()
|
||||
val result = viewState.toCipherView(clock = FIXED_CLOCK)
|
||||
|
||||
assertEquals(
|
||||
cipherView.copy(
|
||||
@@ -822,7 +795,7 @@ class VaultAddItemStateExtensionsTest {
|
||||
totp = null,
|
||||
fido2Credentials = null,
|
||||
uris = null,
|
||||
passwordRevisionDate = Instant.MIN,
|
||||
passwordRevisionDate = FIXED_CLOCK.instant(),
|
||||
autofillOnPageLoad = false,
|
||||
),
|
||||
),
|
||||
@@ -832,9 +805,6 @@ class VaultAddItemStateExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `toLoginView should update revision date when password differs`() {
|
||||
mockkStatic(Instant::class)
|
||||
every { Instant.now() } returns Instant.MAX
|
||||
|
||||
val cipherView = DEFAULT_LOGIN_CIPHER_VIEW
|
||||
|
||||
val viewState = VaultAddEditState.ViewState.Content(
|
||||
@@ -851,8 +821,14 @@ class VaultAddItemStateExtensionsTest {
|
||||
password = "mockPassword-1",
|
||||
),
|
||||
)
|
||||
val futureClock = Clock.fixed(
|
||||
Instant.parse("2023-11-27T12:00:00Z"),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
|
||||
val result = viewState.toCipherView()
|
||||
// We need to pass in a future clock to make sure that when the
|
||||
// revision date is updated it is updated to a new time
|
||||
val result = viewState.toCipherView(clock = futureClock)
|
||||
|
||||
assertNotEquals(
|
||||
viewState.common.originalCipher?.login?.passwordRevisionDate,
|
||||
@@ -862,9 +838,6 @@ class VaultAddItemStateExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `toLoginView should keep revision date when password is equal`() {
|
||||
mockkStatic(Instant::class)
|
||||
every { Instant.now() } returns Instant.MAX
|
||||
|
||||
val cipherView = DEFAULT_LOGIN_CIPHER_VIEW
|
||||
|
||||
val viewState = VaultAddEditState.ViewState.Content(
|
||||
@@ -881,8 +854,14 @@ class VaultAddItemStateExtensionsTest {
|
||||
password = cipherView.login?.password ?: "",
|
||||
),
|
||||
)
|
||||
val futureClock = Clock.fixed(
|
||||
Instant.parse("2023-11-27T12:00:00Z"),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
|
||||
val result = viewState.toCipherView()
|
||||
// We need to pass in a future clock to make sure that if the
|
||||
// revision date were to be updated it would be updated to a new time
|
||||
val result = viewState.toCipherView(clock = futureClock)
|
||||
|
||||
assertEquals(
|
||||
viewState.common.originalCipher?.login?.passwordRevisionDate,
|
||||
@@ -892,9 +871,6 @@ class VaultAddItemStateExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `toLoginView should not update revision date when password is null and has no history`() {
|
||||
mockkStatic(Instant::class)
|
||||
every { Instant.now() } returns Instant.MAX
|
||||
|
||||
val cipherView = DEFAULT_LOGIN_CIPHER_VIEW.copy(
|
||||
passwordHistory = null,
|
||||
login = DEFAULT_LOGIN_CIPHER_VIEW.login?.copy(password = null),
|
||||
@@ -914,8 +890,14 @@ class VaultAddItemStateExtensionsTest {
|
||||
password = "updated password",
|
||||
),
|
||||
)
|
||||
val futureClock = Clock.fixed(
|
||||
Instant.parse("2023-11-27T12:00:00Z"),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
|
||||
val result = viewState.toCipherView()
|
||||
// We need to pass in a future clock to make sure that if the
|
||||
// revision date were to be updated it would be updated to a new time
|
||||
val result = viewState.toCipherView(clock = futureClock)
|
||||
|
||||
assertEquals(
|
||||
viewState.common.originalCipher?.login?.passwordRevisionDate,
|
||||
@@ -924,6 +906,11 @@ class VaultAddItemStateExtensionsTest {
|
||||
}
|
||||
}
|
||||
|
||||
private val FIXED_CLOCK: Clock = Clock.fixed(
|
||||
Instant.parse("2023-10-27T12:00:00Z"),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
|
||||
private val DEFAULT_BASE_CIPHER_VIEW: CipherView = CipherView(
|
||||
id = "id1234",
|
||||
organizationId = null,
|
||||
@@ -979,13 +966,13 @@ private val DEFAULT_BASE_CIPHER_VIEW: CipherView = CipherView(
|
||||
passwordHistory = listOf(
|
||||
PasswordHistoryView(
|
||||
password = "old_password",
|
||||
lastUsedDate = Instant.MIN,
|
||||
lastUsedDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
),
|
||||
permissions = null,
|
||||
creationDate = Instant.MIN,
|
||||
creationDate = FIXED_CLOCK.instant(),
|
||||
deletedDate = null,
|
||||
revisionDate = Instant.MIN,
|
||||
revisionDate = FIXED_CLOCK.instant(),
|
||||
archivedDate = null,
|
||||
sshKey = null,
|
||||
)
|
||||
@@ -995,7 +982,7 @@ private val DEFAULT_LOGIN_CIPHER_VIEW: CipherView = DEFAULT_BASE_CIPHER_VIEW.cop
|
||||
login = LoginView(
|
||||
username = "username",
|
||||
password = "password",
|
||||
passwordRevisionDate = Instant.MIN,
|
||||
passwordRevisionDate = FIXED_CLOCK.instant(),
|
||||
uris = listOf(
|
||||
LoginUriView(
|
||||
uri = "www.example.com",
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
package com.bitwarden.authenticator.data.authenticator.datasource.sdk
|
||||
|
||||
import com.bitwarden.core.DateTime
|
||||
import com.bitwarden.vault.TotpResponse
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Source of authenticator information from the Bitwarden SDK.
|
||||
@@ -13,7 +13,7 @@ interface AuthenticatorSdkSource {
|
||||
*/
|
||||
suspend fun generateTotp(
|
||||
totp: String,
|
||||
time: DateTime,
|
||||
time: Instant,
|
||||
): Result<TotpResponse>
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
package com.bitwarden.authenticator.data.authenticator.datasource.sdk
|
||||
|
||||
import com.bitwarden.authenticator.data.platform.manager.SdkClientManager
|
||||
import com.bitwarden.core.DateTime
|
||||
import com.bitwarden.generators.PasswordGeneratorRequest
|
||||
import com.bitwarden.sdk.Client
|
||||
import com.bitwarden.vault.TotpResponse
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
@@ -16,7 +16,7 @@ class AuthenticatorSdkSourceImpl @Inject constructor(
|
||||
|
||||
override suspend fun generateTotp(
|
||||
totp: String,
|
||||
time: DateTime,
|
||||
time: Instant,
|
||||
): Result<TotpResponse> = runCatching {
|
||||
getClient()
|
||||
.vault()
|
||||
|
||||
+1
-2
@@ -3,7 +3,6 @@ package com.bitwarden.authenticator.data.authenticator.manager
|
||||
import com.bitwarden.authenticator.data.authenticator.datasource.sdk.AuthenticatorSdkSource
|
||||
import com.bitwarden.authenticator.data.authenticator.manager.model.VerificationCodeItem
|
||||
import com.bitwarden.authenticator.data.authenticator.repository.model.AuthenticatorItem
|
||||
import com.bitwarden.core.DateTime
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -46,7 +45,7 @@ class TotpCodeManagerImpl @Inject constructor(
|
||||
// If the item is expired or we haven't generated our first item,
|
||||
// generate a new code using the SDK:
|
||||
item = authenticatorSdkSource
|
||||
.generateTotp(otpUri, DateTime.now())
|
||||
.generateTotp(otpUri, clock.instant())
|
||||
.getOrNull()
|
||||
?.let { response ->
|
||||
VerificationCodeItem(
|
||||
|
||||
Reference in New Issue
Block a user