mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
BIT-1335: Adding plus addressed email generation (#501)
This commit is contained in:
parent
db5c19d971
commit
273763b219
11 changed files with 374 additions and 46 deletions
|
@ -19,6 +19,13 @@ interface GeneratorSdkSource {
|
|||
*/
|
||||
suspend fun generatePassphrase(request: PassphraseGeneratorRequest): Result<String>
|
||||
|
||||
/**
|
||||
* Generates a plus addressed email returning a [String] wrapped in a [Result].
|
||||
*/
|
||||
suspend fun generatePlusAddressedEmail(
|
||||
request: UsernameGeneratorRequest.Subaddress,
|
||||
): Result<String>
|
||||
|
||||
/**
|
||||
* Generates a forwarded service email returning a [String] wrapped in a [Result].
|
||||
*/
|
||||
|
|
|
@ -26,6 +26,12 @@ class GeneratorSdkSourceImpl(
|
|||
clientGenerator.passphrase(request)
|
||||
}
|
||||
|
||||
override suspend fun generatePlusAddressedEmail(
|
||||
request: UsernameGeneratorRequest.Subaddress,
|
||||
): Result<String> = runCatching {
|
||||
clientGenerator.username(request)
|
||||
}
|
||||
|
||||
override suspend fun generateForwardedServiceEmail(
|
||||
request: UsernameGeneratorRequest.Forwarded,
|
||||
): Result<String> = runCatching {
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.x8bit.bitwarden.data.platform.repository.model.LocalDataState
|
|||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedForwardedServiceUsernameResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPassphraseResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPasswordResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPlusAddressedUsernameResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
|
@ -38,6 +39,13 @@ interface GeneratorRepository {
|
|||
passphraseGeneratorRequest: PassphraseGeneratorRequest,
|
||||
): GeneratedPassphraseResult
|
||||
|
||||
/**
|
||||
* Attempt to generate a forwarded service username.
|
||||
*/
|
||||
suspend fun generatePlusAddressedEmail(
|
||||
plusAddressedEmailGeneratorRequest: UsernameGeneratorRequest.Subaddress,
|
||||
): GeneratedPlusAddressedUsernameResult
|
||||
|
||||
/**
|
||||
* Attempt to generate a forwarded service username.
|
||||
*/
|
||||
|
|
|
@ -16,6 +16,7 @@ import com.x8bit.bitwarden.data.tools.generator.datasource.sdk.GeneratorSdkSourc
|
|||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedForwardedServiceUsernameResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPassphraseResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPasswordResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPlusAddressedUsernameResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
@ -126,6 +127,19 @@ class GeneratorRepositoryImpl(
|
|||
onFailure = { GeneratedPassphraseResult.InvalidRequest },
|
||||
)
|
||||
|
||||
override suspend fun generatePlusAddressedEmail(
|
||||
plusAddressedEmailGeneratorRequest: UsernameGeneratorRequest.Subaddress,
|
||||
): GeneratedPlusAddressedUsernameResult =
|
||||
generatorSdkSource.generatePlusAddressedEmail(plusAddressedEmailGeneratorRequest)
|
||||
.fold(
|
||||
onSuccess = { generatedEmail ->
|
||||
GeneratedPlusAddressedUsernameResult.Success(generatedEmail)
|
||||
},
|
||||
onFailure = {
|
||||
GeneratedPlusAddressedUsernameResult.InvalidRequest
|
||||
},
|
||||
)
|
||||
|
||||
override suspend fun generateForwardedServiceUsername(
|
||||
forwardedServiceGeneratorRequest: UsernameGeneratorRequest.Forwarded,
|
||||
): GeneratedForwardedServiceUsernameResult =
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package com.x8bit.bitwarden.data.tools.generator.repository.model
|
||||
|
||||
/**
|
||||
* Represents the outcome of a generator operation.
|
||||
*/
|
||||
sealed class GeneratedPlusAddressedUsernameResult {
|
||||
/**
|
||||
* Operation succeeded with a value.
|
||||
*/
|
||||
data class Success(
|
||||
val generatedEmailAddress: String,
|
||||
) : GeneratedPlusAddressedUsernameResult()
|
||||
|
||||
/**
|
||||
* There was an error during the operation.
|
||||
*/
|
||||
data object InvalidRequest : GeneratedPlusAddressedUsernameResult()
|
||||
}
|
|
@ -5,13 +5,17 @@ package com.x8bit.bitwarden.ui.tools.feature.generator
|
|||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.core.AppendType
|
||||
import com.bitwarden.core.PassphraseGeneratorRequest
|
||||
import com.bitwarden.core.PasswordGeneratorRequest
|
||||
import com.bitwarden.core.UsernameGeneratorRequest
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.GeneratorRepository
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedForwardedServiceUsernameResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPassphraseResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPasswordResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPlusAddressedUsernameResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
|
@ -56,8 +60,16 @@ private const val KEY_STATE = "state"
|
|||
class GeneratorViewModel @Inject constructor(
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
private val generatorRepository: GeneratorRepository,
|
||||
private val authRepository: AuthRepository,
|
||||
) : BaseViewModel<GeneratorState, GeneratorEvent, GeneratorAction>(
|
||||
initialState = savedStateHandle[KEY_STATE] ?: INITIAL_STATE,
|
||||
initialState = savedStateHandle[KEY_STATE] ?: GeneratorState(
|
||||
generatedText = PLACEHOLDER_GENERATED_TEXT,
|
||||
selectedType = Passcode(
|
||||
selectedType = Password(),
|
||||
),
|
||||
currentEmailAddress =
|
||||
requireNotNull(authRepository.userStateFlow.value?.activeAccount?.email),
|
||||
),
|
||||
) {
|
||||
|
||||
//region Initialization and Overrides
|
||||
|
@ -111,8 +123,12 @@ class GeneratorViewModel @Inject constructor(
|
|||
handleUpdateGeneratedPassphraseResult(action)
|
||||
}
|
||||
|
||||
is GeneratorAction.Internal.UpdateGeneratedUsernameResult -> {
|
||||
handleUpdateGeneratedUsernameResult(action)
|
||||
is GeneratorAction.Internal.UpdateGeneratedPlusAddessedUsernameResult -> {
|
||||
handleUpdatePlusAddressedGeneratedUsernameResult(action)
|
||||
}
|
||||
|
||||
is GeneratorAction.Internal.UpdateGeneratedForwardedServiceUsernameResult -> {
|
||||
handleUpdateForwadedServiceGeneratedUsernameResult(action)
|
||||
}
|
||||
|
||||
is GeneratorAction.MainType.Username.UsernameTypeOptionSelect -> {
|
||||
|
@ -205,10 +221,21 @@ class GeneratorViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun loadUsernameOptions(selectedType: Username) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(selectedType = selectedType)
|
||||
val updatedSelectedType = when (selectedType.selectedType) {
|
||||
is PlusAddressedEmail -> Username(
|
||||
selectedType = PlusAddressedEmail(
|
||||
// For convenience the default is an empty email value. We can supply the
|
||||
// dynamic value here before updating the state.
|
||||
email = state.currentEmailAddress,
|
||||
),
|
||||
)
|
||||
|
||||
else -> selectedType
|
||||
}
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(selectedType = updatedSelectedType)
|
||||
}
|
||||
// TODO: Generate different username types. Plus addressed email: BIT-655
|
||||
}
|
||||
|
||||
private fun savePasswordOptionsToDisk(password: Password) {
|
||||
|
@ -336,8 +363,24 @@ class GeneratorViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleUpdateGeneratedUsernameResult(
|
||||
action: GeneratorAction.Internal.UpdateGeneratedUsernameResult,
|
||||
private fun handleUpdatePlusAddressedGeneratedUsernameResult(
|
||||
action: GeneratorAction.Internal.UpdateGeneratedPlusAddessedUsernameResult,
|
||||
) {
|
||||
when (val result = action.result) {
|
||||
is GeneratedPlusAddressedUsernameResult.Success -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(generatedText = result.generatedEmailAddress)
|
||||
}
|
||||
}
|
||||
|
||||
GeneratedPlusAddressedUsernameResult.InvalidRequest -> {
|
||||
sendEvent(GeneratorEvent.ShowSnackbar(R.string.an_error_has_occurred.asText()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleUpdateForwadedServiceGeneratedUsernameResult(
|
||||
action: GeneratorAction.Internal.UpdateGeneratedForwardedServiceUsernameResult,
|
||||
) {
|
||||
when (val result = action.result) {
|
||||
is GeneratedForwardedServiceUsernameResult.Success -> {
|
||||
|
@ -885,7 +928,9 @@ class GeneratorViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
is PlusAddressedEmail -> {
|
||||
// TODO: Implement plus addressed email generation (BIT-1335)
|
||||
if (isManualRegeneration) {
|
||||
generatePlusAddressedEmail(selectedType)
|
||||
}
|
||||
}
|
||||
|
||||
is RandomWord -> {
|
||||
|
@ -899,7 +944,17 @@ class GeneratorViewModel @Inject constructor(
|
|||
private suspend fun generateForwardedEmailAlias(alias: ForwardedEmailAlias) {
|
||||
val request = alias.selectedServiceType?.toUsernameGeneratorRequest() ?: return
|
||||
val result = generatorRepository.generateForwardedServiceUsername(request)
|
||||
sendAction(GeneratorAction.Internal.UpdateGeneratedUsernameResult(result))
|
||||
sendAction(GeneratorAction.Internal.UpdateGeneratedForwardedServiceUsernameResult(result))
|
||||
}
|
||||
|
||||
private suspend fun generatePlusAddressedEmail(plusAddressedEmail: PlusAddressedEmail) {
|
||||
val result = generatorRepository.generatePlusAddressedEmail(
|
||||
UsernameGeneratorRequest.Subaddress(
|
||||
type = AppendType.Random,
|
||||
email = plusAddressedEmail.email,
|
||||
),
|
||||
)
|
||||
sendAction(GeneratorAction.Internal.UpdateGeneratedPlusAddessedUsernameResult(result))
|
||||
}
|
||||
|
||||
private inline fun updateGeneratorMainTypePasscode(
|
||||
|
@ -1119,13 +1174,6 @@ class GeneratorViewModel @Inject constructor(
|
|||
|
||||
companion object {
|
||||
private const val PLACEHOLDER_GENERATED_TEXT = "Placeholder"
|
||||
|
||||
private val INITIAL_STATE: GeneratorState = GeneratorState(
|
||||
generatedText = PLACEHOLDER_GENERATED_TEXT,
|
||||
selectedType = Passcode(
|
||||
selectedType = Password(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1135,11 +1183,13 @@ class GeneratorViewModel @Inject constructor(
|
|||
*
|
||||
* @param generatedText The text that is generated based on the selected options.
|
||||
* @param selectedType The currently selected main type for generating text.
|
||||
* @param currentEmailAddress The email address for the current user.
|
||||
*/
|
||||
@Parcelize
|
||||
data class GeneratorState(
|
||||
val generatedText: String,
|
||||
val selectedType: MainType,
|
||||
val currentEmailAddress: String,
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
|
@ -1334,7 +1384,7 @@ data class GeneratorState(
|
|||
*/
|
||||
@Parcelize
|
||||
data class PlusAddressedEmail(
|
||||
val email: String = "PLACEHOLDER",
|
||||
val email: String = "",
|
||||
) : UsernameType(), Parcelable {
|
||||
override val displayStringResId: Int
|
||||
get() = UsernameTypeOption.PLUS_ADDRESSED_EMAIL.labelRes
|
||||
|
@ -1873,7 +1923,14 @@ sealed class GeneratorAction {
|
|||
/**
|
||||
* Indicates a generated text update is received.
|
||||
*/
|
||||
data class UpdateGeneratedUsernameResult(
|
||||
data class UpdateGeneratedPlusAddessedUsernameResult(
|
||||
val result: GeneratedPlusAddressedUsernameResult,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates a generated text update is received.
|
||||
*/
|
||||
data class UpdateGeneratedForwardedServiceUsernameResult(
|
||||
val result: GeneratedForwardedServiceUsernameResult,
|
||||
) : Internal()
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.tools.generator.datasource.sdk
|
||||
|
||||
import com.bitwarden.core.AppendType
|
||||
import com.bitwarden.core.ForwarderServiceType
|
||||
import com.bitwarden.core.PassphraseGeneratorRequest
|
||||
import com.bitwarden.core.PasswordGeneratorRequest
|
||||
|
@ -70,6 +71,28 @@ class GeneratorSdkSourceTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `generatePlusAddressedEmail should call SDK and return a Result with the generated email`() =
|
||||
runBlocking {
|
||||
val request = UsernameGeneratorRequest.Subaddress(
|
||||
type = AppendType.Random,
|
||||
email = "user@example.com",
|
||||
)
|
||||
val expectedResult = "user+generated@example.com"
|
||||
|
||||
coEvery {
|
||||
clientGenerators.username(request)
|
||||
} returns expectedResult
|
||||
|
||||
val result = generatorSdkSource.generatePlusAddressedEmail(request)
|
||||
|
||||
assertEquals(Result.success(expectedResult), result)
|
||||
coVerify {
|
||||
clientGenerators.username(request)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `generateForwardedServiceEmail should call SDK and return a Result with the generated email`() =
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.x8bit.bitwarden.data.tools.generator.repository
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.AppendType
|
||||
import com.bitwarden.core.ForwarderServiceType
|
||||
import com.bitwarden.core.PassphraseGeneratorRequest
|
||||
import com.bitwarden.core.PasswordGeneratorRequest
|
||||
|
@ -26,6 +27,7 @@ import com.x8bit.bitwarden.data.tools.generator.datasource.sdk.GeneratorSdkSourc
|
|||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedForwardedServiceUsernameResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPassphraseResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPasswordResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPlusAddressedUsernameResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
import io.mockk.coEvery
|
||||
|
@ -260,6 +262,47 @@ class GeneratorRepositoryTest {
|
|||
coVerify { generatorSdkSource.generatePassphrase(request) }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `generatePlusAddressedEmail should return Success with generated email when SDK call is successful`() = runTest {
|
||||
val userId = "testUserId"
|
||||
val request = UsernameGeneratorRequest.Subaddress(
|
||||
type = AppendType.Random,
|
||||
email = "user@example.com",
|
||||
)
|
||||
val generatedEmail = "user+generated@example.com"
|
||||
|
||||
coEvery { authDiskSource.userState?.activeUserId } returns userId
|
||||
coEvery { generatorSdkSource.generatePlusAddressedEmail(request) } returns
|
||||
Result.success(generatedEmail)
|
||||
|
||||
val result = repository.generatePlusAddressedEmail(request)
|
||||
|
||||
assertEquals(
|
||||
generatedEmail,
|
||||
(result as GeneratedPlusAddressedUsernameResult.Success).generatedEmailAddress,
|
||||
)
|
||||
coVerify { generatorSdkSource.generatePlusAddressedEmail(request) }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `generatePlusAddressedEmail should return InvalidRequest on SDK failure`() = runTest {
|
||||
val request = UsernameGeneratorRequest.Subaddress(
|
||||
type = AppendType.Random,
|
||||
email = "user@example.com",
|
||||
)
|
||||
val exception = RuntimeException("An error occurred")
|
||||
coEvery {
|
||||
generatorSdkSource.generatePlusAddressedEmail(request)
|
||||
} returns Result.failure(exception)
|
||||
|
||||
val result = repository.generatePlusAddressedEmail(request)
|
||||
|
||||
assertTrue(result is GeneratedPlusAddressedUsernameResult.InvalidRequest)
|
||||
coVerify { generatorSdkSource.generatePlusAddressedEmail(request) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generateForwardedService should emit Success result and store the generated email`() =
|
||||
runTest {
|
||||
|
|
|
@ -9,6 +9,7 @@ import com.x8bit.bitwarden.data.tools.generator.repository.GeneratorRepository
|
|||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedForwardedServiceUsernameResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPassphraseResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPasswordResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPlusAddressedUsernameResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
@ -30,6 +31,11 @@ class FakeGeneratorRepository : GeneratorRepository {
|
|||
private val mutablePasswordHistoryStateFlow =
|
||||
MutableStateFlow<LocalDataState<List<PasswordHistoryView>>>(LocalDataState.Loading)
|
||||
|
||||
private var generatePlusAddressedEmailResult: GeneratedPlusAddressedUsernameResult =
|
||||
GeneratedPlusAddressedUsernameResult.Success(
|
||||
generatedEmailAddress = "email+abcd1234@address.com",
|
||||
)
|
||||
|
||||
private var generateForwardedServiceResult: GeneratedForwardedServiceUsernameResult =
|
||||
GeneratedForwardedServiceUsernameResult.Success(
|
||||
generatedEmailAddress = "updatedUsername",
|
||||
|
@ -51,6 +57,12 @@ class FakeGeneratorRepository : GeneratorRepository {
|
|||
return generatePassphraseResult
|
||||
}
|
||||
|
||||
override suspend fun generatePlusAddressedEmail(
|
||||
plusAddressedEmailGeneratorRequest: UsernameGeneratorRequest.Subaddress,
|
||||
): GeneratedPlusAddressedUsernameResult {
|
||||
return generatePlusAddressedEmailResult
|
||||
}
|
||||
|
||||
override suspend fun generateForwardedServiceUsername(
|
||||
forwardedServiceGeneratorRequest: UsernameGeneratorRequest.Forwarded,
|
||||
): GeneratedForwardedServiceUsernameResult {
|
||||
|
|
|
@ -51,6 +51,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.PasscodeType
|
||||
.Password(),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -172,6 +173,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
email = "email",
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -390,6 +392,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.PasscodeType
|
||||
.Password(),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -424,6 +427,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.PasscodeType
|
||||
.Password(),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -458,6 +462,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.PasscodeType
|
||||
.Password(minNumbers = initialMinNumbers),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -486,6 +491,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.PasscodeType
|
||||
.Password(minNumbers = initialMinNumbers),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -514,6 +520,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.PasscodeType
|
||||
.Password(),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -548,6 +555,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.PasscodeType
|
||||
.Password(),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -582,6 +590,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.PasscodeType
|
||||
.Password(minSpecial = initialSpecialChars),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -610,6 +619,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.PasscodeType
|
||||
.Password(minSpecial = initialSpecialChars),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -663,6 +673,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.PasscodeType
|
||||
.Passphrase(numWords = initialNumWords),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -698,6 +709,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.PasscodeType
|
||||
.Passphrase(numWords = initialNumWords),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -726,6 +738,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.PasscodeType
|
||||
.Passphrase(numWords = initialNumWords),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -755,6 +768,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.PasscodeType
|
||||
.Passphrase(),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -789,6 +803,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.PasscodeType
|
||||
.Passphrase(),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -821,6 +836,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.PasscodeType
|
||||
.Passphrase(),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -852,6 +868,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.PasscodeType
|
||||
.Passphrase(),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -884,6 +901,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
selectedServiceType = null,
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -943,6 +961,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.AddyIo(),
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -985,6 +1004,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.AddyIo(),
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -1031,6 +1051,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.DuckDuckGo(),
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -1071,6 +1092,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.FastMail(),
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -1111,6 +1133,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.FirefoxRelay(),
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -1157,6 +1180,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.SimpleLogin(),
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -1191,6 +1215,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
email = "",
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -1226,6 +1251,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
domainName = "",
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -1259,6 +1285,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
selectedType = GeneratorState.MainType.Username(
|
||||
GeneratorState.MainType.Username.UsernameType.RandomWord(),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -1284,6 +1311,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
selectedType = GeneratorState.MainType.Username(
|
||||
GeneratorState.MainType.Username.UsernameType.RandomWord(),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@ package com.x8bit.bitwarden.ui.tools.feature.generator
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedForwardedServiceUsernameResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPassphraseResult
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPasswordResult
|
||||
|
@ -10,6 +13,9 @@ import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerat
|
|||
import com.x8bit.bitwarden.data.tools.generator.repository.util.FakeGeneratorRepository
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
|
@ -54,6 +60,11 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
private val initialRandomWordState = createRandomWordState()
|
||||
private val randomWordSavedStateHandle = createSavedStateHandleWithState(initialRandomWordState)
|
||||
|
||||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(DEFAULT_USER_STATE)
|
||||
private val authRepository = mockk<AuthRepository>() {
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
}
|
||||
|
||||
private val fakeGeneratorRepository = FakeGeneratorRepository().apply {
|
||||
setMockGeneratePasswordResult(
|
||||
GeneratedPasswordResult.Success("defaultPassword"),
|
||||
|
@ -61,11 +72,15 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(initialPasscodeState, awaitItem())
|
||||
}
|
||||
fun `initial state should be correct when there is no saved state`() {
|
||||
val viewModel = createViewModel(state = null)
|
||||
assertEquals(initialPasscodeState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct when there is a saved state`() {
|
||||
val viewModel = createViewModel(state = initialPasscodeState)
|
||||
assertEquals(initialPasscodeState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
|
@ -190,18 +205,34 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `RegenerateClick for username state should do nothing`() = runTest {
|
||||
val viewModel = GeneratorViewModel(usernameSavedStateHandle, fakeGeneratorRepository)
|
||||
fun `RegenerateClick for plus addressed email state should update the plus addressed email correctly`() =
|
||||
runTest {
|
||||
val viewModel = GeneratorViewModel(
|
||||
usernameSavedStateHandle,
|
||||
fakeGeneratorRepository,
|
||||
authRepository,
|
||||
)
|
||||
|
||||
fakeGeneratorRepository.setMockGeneratePasswordResult(
|
||||
GeneratedPasswordResult.Success("DifferentUsername"),
|
||||
)
|
||||
fakeGeneratorRepository.setMockGeneratePasswordResult(
|
||||
GeneratedPasswordResult.Success("DifferentUsername"),
|
||||
)
|
||||
|
||||
viewModel.actionChannel.trySend(GeneratorAction.RegenerateClick)
|
||||
viewModel.actionChannel.trySend(GeneratorAction.RegenerateClick)
|
||||
|
||||
assertEquals(initialUsernameState, viewModel.stateFlow.value)
|
||||
}
|
||||
val expectedState =
|
||||
initialPasscodeState.copy(
|
||||
generatedText = "email+abcd1234@address.com",
|
||||
selectedType = GeneratorState.MainType.Username(
|
||||
GeneratorState.MainType.Username.UsernameType.PlusAddressedEmail(
|
||||
email = "currentEmail",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CopyClick should emit CopyTextToClipboard event`() = runTest {
|
||||
|
@ -246,7 +277,13 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
val expectedState =
|
||||
initialPasscodeState.copy(selectedType = GeneratorState.MainType.Username())
|
||||
initialPasscodeState.copy(
|
||||
selectedType = GeneratorState.MainType.Username(
|
||||
GeneratorState.MainType.Username.UsernameType.PlusAddressedEmail(
|
||||
email = "currentEmail",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
@ -320,7 +357,9 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.PlusAddressedEmail(),
|
||||
.PlusAddressedEmail(
|
||||
email = "currentEmail",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -412,7 +451,11 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
fakeGeneratorRepository.setMockGeneratePasswordResult(
|
||||
GeneratedPasswordResult.Success("defaultPassword"),
|
||||
)
|
||||
viewModel = GeneratorViewModel(initialPasscodeSavedStateHandle, fakeGeneratorRepository)
|
||||
viewModel = GeneratorViewModel(
|
||||
initialPasscodeSavedStateHandle,
|
||||
fakeGeneratorRepository,
|
||||
authRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
|
@ -785,7 +828,11 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
fakeGeneratorRepository.setMockGeneratePasswordResult(
|
||||
GeneratedPasswordResult.Success("defaultPassphrase"),
|
||||
)
|
||||
viewModel = GeneratorViewModel(passphraseSavedStateHandle, fakeGeneratorRepository)
|
||||
viewModel = GeneratorViewModel(
|
||||
passphraseSavedStateHandle,
|
||||
fakeGeneratorRepository,
|
||||
authRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -923,7 +970,11 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
@BeforeEach
|
||||
fun setup() {
|
||||
viewModel =
|
||||
GeneratorViewModel(forwardedEmailAliasSavedStateHandle, fakeGeneratorRepository)
|
||||
GeneratorViewModel(
|
||||
forwardedEmailAliasSavedStateHandle,
|
||||
fakeGeneratorRepository,
|
||||
authRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -980,7 +1031,11 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
viewModel = GeneratorViewModel(addyIoSavedStateHandle, fakeGeneratorRepository)
|
||||
viewModel = GeneratorViewModel(
|
||||
addyIoSavedStateHandle,
|
||||
fakeGeneratorRepository,
|
||||
authRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1070,7 +1125,11 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
viewModel = GeneratorViewModel(duckDuckGoSavedStateHandle, fakeGeneratorRepository)
|
||||
viewModel = GeneratorViewModel(
|
||||
duckDuckGoSavedStateHandle,
|
||||
fakeGeneratorRepository,
|
||||
authRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1120,7 +1179,11 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
viewModel = GeneratorViewModel(fastMailSavedStateHandle, fakeGeneratorRepository)
|
||||
viewModel = GeneratorViewModel(
|
||||
fastMailSavedStateHandle,
|
||||
fakeGeneratorRepository,
|
||||
authRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1170,7 +1233,11 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
viewModel = GeneratorViewModel(firefoxRelaySavedStateHandle, fakeGeneratorRepository)
|
||||
viewModel = GeneratorViewModel(
|
||||
firefoxRelaySavedStateHandle,
|
||||
fakeGeneratorRepository,
|
||||
authRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1221,7 +1288,11 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
viewModel = GeneratorViewModel(simpleLoginSavedStateHandle, fakeGeneratorRepository)
|
||||
viewModel = GeneratorViewModel(
|
||||
simpleLoginSavedStateHandle,
|
||||
fakeGeneratorRepository,
|
||||
authRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1272,7 +1343,11 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
viewModel = GeneratorViewModel(usernameSavedStateHandle, fakeGeneratorRepository)
|
||||
viewModel = GeneratorViewModel(
|
||||
usernameSavedStateHandle,
|
||||
fakeGeneratorRepository,
|
||||
authRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
|
@ -1280,6 +1355,7 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
fun `EmailTextChange should update email correctly`() =
|
||||
runTest {
|
||||
val newEmail = "test@example.com"
|
||||
val newGeneratedEmail = "email+abcd1234@address.com"
|
||||
viewModel.actionChannel.trySend(
|
||||
GeneratorAction
|
||||
.MainType
|
||||
|
@ -1314,7 +1390,11 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
viewModel = GeneratorViewModel(catchAllEmailSavedStateHandle, fakeGeneratorRepository)
|
||||
viewModel = GeneratorViewModel(
|
||||
catchAllEmailSavedStateHandle,
|
||||
fakeGeneratorRepository,
|
||||
authRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
|
@ -1356,7 +1436,11 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
viewModel = GeneratorViewModel(randomWordSavedStateHandle, fakeGeneratorRepository)
|
||||
viewModel = GeneratorViewModel(
|
||||
randomWordSavedStateHandle,
|
||||
fakeGeneratorRepository,
|
||||
authRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
|
@ -1437,6 +1521,7 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
avoidAmbiguousChars = avoidAmbiguousChars,
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
)
|
||||
|
||||
private fun createPassphraseState(
|
||||
|
@ -1456,6 +1541,7 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
includeNumber = includeNumber,
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
)
|
||||
|
||||
private fun createForwardedEmailAliasState(
|
||||
|
@ -1470,6 +1556,7 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
obfuscatedText = obfuscatedText,
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
)
|
||||
|
||||
private fun createAddyIoState(
|
||||
|
@ -1488,6 +1575,7 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
.AddyIo(),
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
)
|
||||
|
||||
private fun createDuckDuckGoState(
|
||||
|
@ -1506,6 +1594,7 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
.DuckDuckGo(),
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
)
|
||||
|
||||
private fun createFastMailState(
|
||||
|
@ -1524,6 +1613,7 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
.FastMail(),
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
)
|
||||
|
||||
private fun createFirefoxRelayState(
|
||||
|
@ -1542,6 +1632,7 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
.FirefoxRelay(),
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
)
|
||||
|
||||
private fun createSimpleLoginState(
|
||||
|
@ -1560,11 +1651,12 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
.SimpleLogin(),
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
)
|
||||
|
||||
private fun createPlusAddressedEmailState(
|
||||
generatedText: String = "defaultPlusAddressedEmail",
|
||||
email: String = "defaultEmail",
|
||||
email: String = "currentEmail",
|
||||
): GeneratorState =
|
||||
GeneratorState(
|
||||
generatedText = generatedText,
|
||||
|
@ -1573,6 +1665,7 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
email = email,
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
)
|
||||
|
||||
private fun createCatchAllEmailState(
|
||||
|
@ -1586,6 +1679,7 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
domainName = domain,
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
)
|
||||
|
||||
private fun createRandomWordState(
|
||||
|
@ -1601,6 +1695,7 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
includeNumber = includeNumber,
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
)
|
||||
|
||||
private fun createSavedStateHandleWithState(state: GeneratorState) =
|
||||
|
@ -1613,7 +1708,24 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
): GeneratorViewModel = GeneratorViewModel(
|
||||
savedStateHandle = SavedStateHandle().apply { set("state", state) },
|
||||
generatorRepository = fakeGeneratorRepository,
|
||||
authRepository = authRepository,
|
||||
)
|
||||
|
||||
//endregion Helper Functions
|
||||
}
|
||||
|
||||
private val DEFAULT_USER_STATE = UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "Active User",
|
||||
email = "currentEmail",
|
||||
environment = Environment.Us,
|
||||
avatarColorHex = "#aa00aa",
|
||||
isPremium = true,
|
||||
isVaultUnlocked = true,
|
||||
organizations = emptyList(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue