bitwarden-android/app/src/test/java/com/x8bit/bitwarden/AutofillTotpCopyViewModelTest.kt
Lucas Kivi a92d9ff823 BIT-1093: Add TOTP copying to autofill flow (#879)
Co-authored-by: David Perez <david@livefront.com>
2024-06-20 17:08:07 +01:00

238 lines
8.6 KiB
Kotlin

package com.x8bit.bitwarden
import android.content.Intent
import app.cash.turbine.test
import com.bitwarden.core.CipherView
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.autofill.model.AutofillTotpCopyData
import com.x8bit.bitwarden.data.autofill.util.getTotpCopyIntentOrNull
import com.x8bit.bitwarden.data.platform.repository.model.DataState
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockData
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
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
class AutofillTotpCopyViewModelTest : BaseViewModelTest() {
private lateinit var autofillTotpCopyViewModel: AutofillTotpCopyViewModel
private val mutableCiphersStateFlow: MutableStateFlow<DataState<List<CipherView>>> =
MutableStateFlow(DataState.Loading)
private val mutableVaultUnlockDataStateFlow: MutableStateFlow<List<VaultUnlockData>> =
MutableStateFlow(emptyList())
private val authRepository: AuthRepository = mockk {
every { activeUserId } returns null
}
private val vaultRepository: VaultRepository = mockk {
every { ciphersStateFlow } returns mutableCiphersStateFlow
every { vaultUnlockDataStateFlow } returns mutableVaultUnlockDataStateFlow
}
private val intent: Intent = mockk()
@BeforeEach
fun setup() {
mockkStatic(Intent::getTotpCopyIntentOrNull)
autofillTotpCopyViewModel = AutofillTotpCopyViewModel(
authRepository = authRepository,
vaultRepository = vaultRepository,
)
}
@AfterEach
fun teardown() {
unmockkStatic(Intent::getTotpCopyIntentOrNull)
}
@Suppress("MaxLineLength")
@Test
fun `on IntentReceived should emit CompleteAutofill when cipherID is extracted, vault unlocked, and cipherView found`() =
runTest {
// Setup
val cipherView: CipherView = mockk {
every { id } returns CIPHER_ID
}
val totpCopyData = AutofillTotpCopyData(
cipherId = CIPHER_ID,
)
val action = AutofillTotpCopyAction.IntentReceived(
intent = intent,
)
val expectedEvent = AutofillTotpCopyEvent.CompleteAutofill(
cipherView = cipherView,
)
val vaultUnlockData = VaultUnlockData(
userId = ACTIVE_USER_ID,
status = VaultUnlockData.Status.UNLOCKED,
)
every { intent.getTotpCopyIntentOrNull() } returns totpCopyData
every { authRepository.activeUserId } returns ACTIVE_USER_ID
every { vaultRepository.isVaultUnlocked(userId = ACTIVE_USER_ID) } returns true
mutableCiphersStateFlow.value = DataState.Loaded(
listOf(
cipherView,
),
)
mutableVaultUnlockDataStateFlow.value = listOf(vaultUnlockData)
// Test
autofillTotpCopyViewModel.trySendAction(action)
// Verify
autofillTotpCopyViewModel.eventFlow.test {
assertEquals(expectedEvent, awaitItem())
expectNoEvents()
}
}
@Test
fun `on IntentReceived should emit FinishActivity when cipherID is not`() = runTest {
// Setup
val action = AutofillTotpCopyAction.IntentReceived(
intent = intent,
)
val expectedEvent = AutofillTotpCopyEvent.FinishActivity
every { intent.getTotpCopyIntentOrNull() } returns null
// Test
autofillTotpCopyViewModel.trySendAction(action)
// Verify
autofillTotpCopyViewModel.eventFlow.test {
assertEquals(expectedEvent, awaitItem())
expectNoEvents()
}
}
@Suppress("MaxLineLength")
@Test
fun `on IntentReceived should emit FinishActivity when cipherID is extracted and no active user`() =
runTest {
// Setup
val totpCopyData = AutofillTotpCopyData(
cipherId = CIPHER_ID,
)
val action = AutofillTotpCopyAction.IntentReceived(
intent = intent,
)
val expectedEvent = AutofillTotpCopyEvent.FinishActivity
every { intent.getTotpCopyIntentOrNull() } returns totpCopyData
every { authRepository.activeUserId } returns null
// Test
autofillTotpCopyViewModel.trySendAction(action)
// Verify
autofillTotpCopyViewModel.eventFlow.test {
assertEquals(expectedEvent, awaitItem())
expectNoEvents()
}
}
@Suppress("MaxLineLength")
@Test
fun `on IntentReceived should emit FinishActivity when cipherID is extracted and vault locked`() =
runTest {
// Setup
val totpCopyData = AutofillTotpCopyData(
cipherId = CIPHER_ID,
)
val action = AutofillTotpCopyAction.IntentReceived(
intent = intent,
)
val expectedEvent = AutofillTotpCopyEvent.FinishActivity
every { intent.getTotpCopyIntentOrNull() } returns totpCopyData
every { authRepository.activeUserId } returns ACTIVE_USER_ID
every { vaultRepository.isVaultUnlocked(userId = ACTIVE_USER_ID) } returns false
// Test
autofillTotpCopyViewModel.trySendAction(action)
// Verify
autofillTotpCopyViewModel.eventFlow.test {
assertEquals(expectedEvent, awaitItem())
expectNoEvents()
}
}
@Suppress("MaxLineLength")
@Test
fun `on IntentReceived should emit FinishActivity when cipherID is extracted, vault unlocked, and cipherView not found`() =
runTest {
// Setup
val cipherView: CipherView = mockk {
every { id } returns "NEW CIPHER ID"
}
val totpCopyData = AutofillTotpCopyData(
cipherId = CIPHER_ID,
)
val action = AutofillTotpCopyAction.IntentReceived(
intent = intent,
)
val expectedEvent = AutofillTotpCopyEvent.FinishActivity
val vaultUnlockData = VaultUnlockData(
userId = ACTIVE_USER_ID,
status = VaultUnlockData.Status.UNLOCKED,
)
every { intent.getTotpCopyIntentOrNull() } returns totpCopyData
every { authRepository.activeUserId } returns ACTIVE_USER_ID
every { vaultRepository.isVaultUnlocked(userId = ACTIVE_USER_ID) } returns true
mutableCiphersStateFlow.value = DataState.Loaded(
listOf(
cipherView,
),
)
mutableVaultUnlockDataStateFlow.value = listOf(vaultUnlockData)
// Test
autofillTotpCopyViewModel.trySendAction(action)
// Verify
autofillTotpCopyViewModel.eventFlow.test {
assertEquals(expectedEvent, awaitItem())
expectNoEvents()
}
}
@Test
fun `on IntentReceived should emit FinishActivity when timeout is elapsed`() = runTest {
// Setup
val totpCopyData = AutofillTotpCopyData(
cipherId = CIPHER_ID,
)
val action = AutofillTotpCopyAction.IntentReceived(
intent = intent,
)
val expectedEvent = AutofillTotpCopyEvent.FinishActivity
val vaultUnlockData = VaultUnlockData(
userId = ACTIVE_USER_ID,
status = VaultUnlockData.Status.UNLOCKED,
)
every { intent.getTotpCopyIntentOrNull() } returns totpCopyData
every { authRepository.activeUserId } returns ACTIVE_USER_ID
every { vaultRepository.isVaultUnlocked(userId = ACTIVE_USER_ID) } returns true
mutableVaultUnlockDataStateFlow.value = listOf(vaultUnlockData)
// Test
autofillTotpCopyViewModel.trySendAction(action)
// Verify
autofillTotpCopyViewModel.eventFlow.test {
assertEquals(expectedEvent, awaitItem())
expectNoEvents()
}
}
}
private const val ACTIVE_USER_ID: String = "ACTIVE_USER_ID"
private const val CIPHER_ID: String = "1234567890"