BIT-1335: Adding plus addressed email generation (#501)

This commit is contained in:
joshua-livefront 2024-01-05 12:56:19 -05:00 committed by Álison Fernandes
parent db5c19d971
commit 273763b219
11 changed files with 374 additions and 46 deletions

View file

@ -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].
*/

View file

@ -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 {

View file

@ -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.
*/

View file

@ -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 =

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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`() =

View file

@ -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 {

View file

@ -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 {

View file

@ -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",
),
)

View file

@ -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(),
),
),
)