From b493ffe7f1ad98de3ea2eb33a091acbe0502b96e Mon Sep 17 00:00:00 2001 From: joshua-livefront <139182194+joshua-livefront@users.noreply.github.com> Date: Tue, 2 Jan 2024 16:40:39 -0500 Subject: [PATCH] BIT-1330, BIT-1331: Implementation to use sdk for forwarded service email generation (#470) --- .../datasource/sdk/GeneratorSdkSource.kt | 8 +++ .../datasource/sdk/GeneratorSdkSourceImpl.kt | 7 ++ .../repository/GeneratorRepository.kt | 9 +++ .../repository/GeneratorRepositoryImpl.kt | 15 +++++ ...GeneratedForwardedServiceUsernameResult.kt | 18 ++++++ .../feature/generator/GeneratorViewModel.kt | 58 ++++++++++++++++- .../generator/util/ServiceTypeExtensions.kt | 47 ++++++++++++++ .../datasource/sdk/GeneratorSdkSourceTest.kt | 25 ++++++++ .../repository/GeneratorRepositoryTest.kt | 45 +++++++++++++ .../util/FakeGeneratorRepository.kt | 20 ++++++ .../generator/GeneratorViewModelTest.kt | 44 ++++++++++++- .../util/ServiceTypeExtensionsTest.kt | 64 +++++++++++++++++++ 12 files changed, 356 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/model/GeneratedForwardedServiceUsernameResult.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ServiceTypeExtensions.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ServiceTypeExtensionsTest.kt diff --git a/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/datasource/sdk/GeneratorSdkSource.kt b/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/datasource/sdk/GeneratorSdkSource.kt index 7e66ebabfc..bfdf988c2f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/datasource/sdk/GeneratorSdkSource.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/datasource/sdk/GeneratorSdkSource.kt @@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.tools.generator.datasource.sdk import com.bitwarden.core.PassphraseGeneratorRequest import com.bitwarden.core.PasswordGeneratorRequest +import com.bitwarden.core.UsernameGeneratorRequest /** * Source of password generation functionality from the Bitwarden SDK. @@ -17,4 +18,11 @@ interface GeneratorSdkSource { * Generates a passphrase returning a [String] wrapped in a [Result]. */ suspend fun generatePassphrase(request: PassphraseGeneratorRequest): Result + + /** + * Generates a forwarded service email returning a [String] wrapped in a [Result]. + */ + suspend fun generateForwardedServiceEmail( + request: UsernameGeneratorRequest.Forwarded, + ): Result } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/datasource/sdk/GeneratorSdkSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/datasource/sdk/GeneratorSdkSourceImpl.kt index 13b00ba646..d9017a099f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/datasource/sdk/GeneratorSdkSourceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/datasource/sdk/GeneratorSdkSourceImpl.kt @@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.tools.generator.datasource.sdk import com.bitwarden.core.PassphraseGeneratorRequest import com.bitwarden.core.PasswordGeneratorRequest +import com.bitwarden.core.UsernameGeneratorRequest import com.bitwarden.sdk.ClientGenerators /** @@ -24,4 +25,10 @@ class GeneratorSdkSourceImpl( ): Result = runCatching { clientGenerator.passphrase(request) } + + override suspend fun generateForwardedServiceEmail( + request: UsernameGeneratorRequest.Forwarded, + ): Result = runCatching { + clientGenerator.username(request) + } } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepository.kt b/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepository.kt index 8378f6556c..bbf6fa8613 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepository.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepository.kt @@ -3,9 +3,11 @@ package com.x8bit.bitwarden.data.tools.generator.repository import com.bitwarden.core.PassphraseGeneratorRequest import com.bitwarden.core.PasswordGeneratorRequest import com.bitwarden.core.PasswordHistoryView +import com.bitwarden.core.UsernameGeneratorRequest import com.x8bit.bitwarden.data.platform.repository.model.LocalDataState 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.GeneratedForwardedServiceUsernameResult import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions import kotlinx.coroutines.flow.StateFlow @@ -36,6 +38,13 @@ interface GeneratorRepository { passphraseGeneratorRequest: PassphraseGeneratorRequest, ): GeneratedPassphraseResult + /** + * Attempt to generate a forwarded service username. + */ + suspend fun generateForwardedServiceUsername( + forwardedServiceGeneratorRequest: UsernameGeneratorRequest.Forwarded, + ): GeneratedForwardedServiceUsernameResult + /** * Get the [PasscodeGenerationOptions] for the current user. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryImpl.kt index a14226b4dc..7a4b77688e 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryImpl.kt @@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.tools.generator.repository import com.bitwarden.core.PassphraseGeneratorRequest import com.bitwarden.core.PasswordGeneratorRequest import com.bitwarden.core.PasswordHistoryView +import com.bitwarden.core.UsernameGeneratorRequest import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager import com.x8bit.bitwarden.data.platform.repository.model.LocalDataState @@ -14,6 +15,7 @@ import com.x8bit.bitwarden.data.tools.generator.datasource.disk.entity.toPasswor import com.x8bit.bitwarden.data.tools.generator.datasource.sdk.GeneratorSdkSource 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.GeneratedForwardedServiceUsernameResult import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource import kotlinx.coroutines.CoroutineScope @@ -120,6 +122,19 @@ class GeneratorRepositoryImpl( onFailure = { GeneratedPassphraseResult.InvalidRequest }, ) + override suspend fun generateForwardedServiceUsername( + forwardedServiceGeneratorRequest: UsernameGeneratorRequest.Forwarded, + ): GeneratedForwardedServiceUsernameResult = + generatorSdkSource.generateForwardedServiceEmail(forwardedServiceGeneratorRequest) + .fold( + onSuccess = { generatedEmail -> + GeneratedForwardedServiceUsernameResult.Success(generatedEmail) + }, + onFailure = { + GeneratedForwardedServiceUsernameResult.InvalidRequest + }, + ) + override fun getPasscodeGenerationOptions(): PasscodeGenerationOptions? { val userId = authDiskSource.userState?.activeUserId return userId?.let { generatorDiskSource.getPasscodeGenerationOptions(it) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/model/GeneratedForwardedServiceUsernameResult.kt b/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/model/GeneratedForwardedServiceUsernameResult.kt new file mode 100644 index 0000000000..d0f3e6d397 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/model/GeneratedForwardedServiceUsernameResult.kt @@ -0,0 +1,18 @@ +package com.x8bit.bitwarden.data.tools.generator.repository.model + +/** + * Represents the outcome of a generator operation. + */ +sealed class GeneratedForwardedServiceUsernameResult { + /** + * Operation succeeded with a value. + */ + data class Success( + val generatedEmailAddress: String, + ) : GeneratedForwardedServiceUsernameResult() + + /** + * There was an error during the operation. + */ + data object InvalidRequest : GeneratedForwardedServiceUsernameResult() +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt index 14ea17741a..9a0cbc011a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt @@ -11,6 +11,7 @@ import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.tools.generator.repository.GeneratorRepository 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.GeneratedForwardedServiceUsernameResult 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 @@ -29,6 +30,7 @@ import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Us import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias.ServiceType.SimpleLogin import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.PlusAddressedEmail import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.RandomWord +import com.x8bit.bitwarden.ui.tools.feature.generator.util.toUsernameGeneratorRequest import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.flow.launchIn @@ -109,6 +111,10 @@ class GeneratorViewModel @Inject constructor( handleUpdateGeneratedPassphraseResult(action) } + is GeneratorAction.Internal.UpdateGeneratedUsernameResult -> { + handleUpdateGeneratedUsernameResult(action) + } + is GeneratorAction.MainType.Username.UsernameTypeOptionSelect -> { handleUsernameTypeOptionSelect(action) } @@ -291,7 +297,7 @@ class GeneratorViewModel @Inject constructor( private fun handleRegenerationClick() { // Go through the update process with the current state to trigger a // regeneration of the generated text for the same state. - updateGeneratorMainType { mutableStateFlow.value.selectedType } + updateGeneratorMainType(isManualRegeneration = true) { mutableStateFlow.value.selectedType } } private fun handleCopyClick() { @@ -330,6 +336,22 @@ class GeneratorViewModel @Inject constructor( } } + private fun handleUpdateGeneratedUsernameResult( + action: GeneratorAction.Internal.UpdateGeneratedUsernameResult, + ) { + when (val result = action.result) { + is GeneratedForwardedServiceUsernameResult.Success -> { + mutableStateFlow.update { + it.copy(generatedText = result.generatedEmailAddress) + } + } + + GeneratedForwardedServiceUsernameResult.InvalidRequest -> { + sendEvent(GeneratorEvent.ShowSnackbar(R.string.an_error_has_occurred.asText())) + } + } + } + //endregion Generated Field Handlers //region Main Type Option Handlers @@ -829,6 +851,7 @@ class GeneratorViewModel @Inject constructor( //region Utility Functions private inline fun updateGeneratorMainType( + isManualRegeneration: Boolean = false, crossinline block: (GeneratorState.MainType) -> GeneratorState.MainType?, ) { val currentSelectedType = mutableStateFlow.value.selectedType @@ -850,13 +873,34 @@ class GeneratorViewModel @Inject constructor( } } - is Username -> { - // TODO: Generate different username types. Plus addressed email: BIT-655 + is Username -> when (val selectedType = updatedMainType.selectedType) { + is ForwardedEmailAlias -> { + if (isManualRegeneration) { + generateForwardedEmailAlias(selectedType) + } + } + is CatchAllEmail -> { + // TODO: Implement catch all email generation (BIT-1334) + } + + is PlusAddressedEmail -> { + // TODO: Implement plus addressed email generation (BIT-1335) + } + + is RandomWord -> { + // TODO: Implement random word generation (BIT-1336) + } } } } } + private suspend fun generateForwardedEmailAlias(alias: ForwardedEmailAlias) { + val request = alias.selectedServiceType?.toUsernameGeneratorRequest() ?: return + val result = generatorRepository.generateForwardedServiceUsername(request) + sendAction(GeneratorAction.Internal.UpdateGeneratedUsernameResult(result)) + } + private inline fun updateGeneratorMainTypePasscode( crossinline block: (Passcode) -> Passcode, ) { @@ -1391,6 +1435,7 @@ data class GeneratorState( data class AddyIo( val apiAccessToken: String = "", val domainName: String = "", + val baseUrl: String = "", ) : ServiceType(), Parcelable { override val displayStringResId: Int get() = ServiceTypeOption.ADDY_IO.labelRes @@ -1823,6 +1868,13 @@ sealed class GeneratorAction { data class UpdateGeneratedPassphraseResult( val result: GeneratedPassphraseResult, ) : Internal() + + /** + * Indicates a generated text update is received. + */ + data class UpdateGeneratedUsernameResult( + val result: GeneratedForwardedServiceUsernameResult, + ) : Internal() } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ServiceTypeExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ServiceTypeExtensions.kt new file mode 100644 index 0000000000..d0ef3055f0 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ServiceTypeExtensions.kt @@ -0,0 +1,47 @@ +package com.x8bit.bitwarden.ui.tools.feature.generator.util + +import com.bitwarden.core.ForwarderServiceType +import com.bitwarden.core.UsernameGeneratorRequest +import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias.ServiceType + +/** + * Converts a [ServiceType] to a [UsernameGeneratorRequest.Forwarded]. + */ +fun ServiceType.toUsernameGeneratorRequest(): UsernameGeneratorRequest.Forwarded { + return when (this) { + is ServiceType.AddyIo -> { + UsernameGeneratorRequest.Forwarded( + service = ForwarderServiceType.AddyIo( + apiToken = this.apiAccessToken, + domain = this.domainName, + baseUrl = this.baseUrl, + ), + website = null, + ) + } + is ServiceType.DuckDuckGo -> { + UsernameGeneratorRequest.Forwarded( + service = ForwarderServiceType.DuckDuckGo(token = this.apiKey), + website = null, + ) + } + is ServiceType.FirefoxRelay -> { + UsernameGeneratorRequest.Forwarded( + service = ForwarderServiceType.Firefox(apiToken = this.apiAccessToken), + website = null, + ) + } + is ServiceType.FastMail -> { + UsernameGeneratorRequest.Forwarded( + service = ForwarderServiceType.Fastmail(apiToken = this.apiKey), + website = null, + ) + } + is ServiceType.SimpleLogin -> { + UsernameGeneratorRequest.Forwarded( + service = ForwarderServiceType.SimpleLogin(apiKey = this.apiKey), + website = null, + ) + } + } +} diff --git a/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/datasource/sdk/GeneratorSdkSourceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/datasource/sdk/GeneratorSdkSourceTest.kt index e33e2198b9..64fd06ad55 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/datasource/sdk/GeneratorSdkSourceTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/datasource/sdk/GeneratorSdkSourceTest.kt @@ -1,7 +1,9 @@ package com.x8bit.bitwarden.data.tools.generator.datasource.sdk +import com.bitwarden.core.ForwarderServiceType import com.bitwarden.core.PassphraseGeneratorRequest import com.bitwarden.core.PasswordGeneratorRequest +import com.bitwarden.core.UsernameGeneratorRequest import com.bitwarden.sdk.ClientGenerators import io.mockk.coEvery import io.mockk.coVerify @@ -67,4 +69,27 @@ class GeneratorSdkSourceTest { clientGenerators.passphrase(request) } } + + @Suppress("MaxLineLength") + @Test + fun `generateForwardedServiceEmail should call SDK and return a Result with the generated email`() = + runBlocking { + val request = UsernameGeneratorRequest.Forwarded( + service = ForwarderServiceType.DuckDuckGo(token = "testToken"), + website = null, + ) + val expectedResult = "generated@email.com" + + coEvery { + clientGenerators.username(request) + } returns expectedResult + + val result = generatorSdkSource.generateForwardedServiceEmail(request) + + assertEquals(Result.success(expectedResult), result) + + coVerify { + clientGenerators.username(request) + } + } } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryTest.kt index feac64ce2a..ef0fc70d63 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryTest.kt @@ -1,10 +1,12 @@ package com.x8bit.bitwarden.data.tools.generator.repository import app.cash.turbine.test +import com.bitwarden.core.ForwarderServiceType import com.bitwarden.core.PassphraseGeneratorRequest import com.bitwarden.core.PasswordGeneratorRequest import com.bitwarden.core.PasswordHistory import com.bitwarden.core.PasswordHistoryView +import com.bitwarden.core.UsernameGeneratorRequest import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson @@ -21,6 +23,7 @@ import com.x8bit.bitwarden.data.tools.generator.datasource.disk.PasswordHistoryD import com.x8bit.bitwarden.data.tools.generator.datasource.disk.entity.PasswordHistoryEntity import com.x8bit.bitwarden.data.tools.generator.datasource.disk.entity.toPasswordHistoryEntity import com.x8bit.bitwarden.data.tools.generator.datasource.sdk.GeneratorSdkSource +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.PasscodeGenerationOptions @@ -250,6 +253,48 @@ class GeneratorRepositoryTest { coVerify { generatorSdkSource.generatePassphrase(request) } } + @Suppress("MaxLineLength") + @Test + fun `generateForwardedService should emit Success result and store the generated email`() = runTest { + val userId = "testUserId" + val request = UsernameGeneratorRequest.Forwarded( + service = ForwarderServiceType.DuckDuckGo( + token = "testToken", + ), + website = null, + ) + + val generatedEmail = "generated@email.com" + + coEvery { authDiskSource.userState?.activeUserId } returns userId + coEvery { generatorSdkSource.generateForwardedServiceEmail(request) } returns + Result.success(generatedEmail) + + val result = repository.generateForwardedServiceUsername(request) + + assertEquals( + generatedEmail, + (result as GeneratedForwardedServiceUsernameResult.Success).generatedEmailAddress, + ) + coVerify { generatorSdkSource.generateForwardedServiceEmail(request) } + } + + @Suppress("MaxLineLength") + @Test + fun `generateForwardedService should emit InvalidRequest result when SDK throws exception`() = runTest { + val request = UsernameGeneratorRequest.Forwarded( + service = ForwarderServiceType.DuckDuckGo(token = "testToken"), + website = null, + ) + val exception = RuntimeException("An error occurred") + coEvery { generatorSdkSource.generateForwardedServiceEmail(request) } returns Result.failure(exception) + + val result = repository.generateForwardedServiceUsername(request) + + assertTrue(result is GeneratedForwardedServiceUsernameResult.InvalidRequest) + coVerify { generatorSdkSource.generateForwardedServiceEmail(request) } + } + @Test fun `getPasscodeGenerationOptions should return options when available`() = runTest { val userId = "activeUserId" diff --git a/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/repository/util/FakeGeneratorRepository.kt b/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/repository/util/FakeGeneratorRepository.kt index cd9b10ab28..eb62ffdc5c 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/repository/util/FakeGeneratorRepository.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/tools/generator/repository/util/FakeGeneratorRepository.kt @@ -3,8 +3,10 @@ package com.x8bit.bitwarden.data.tools.generator.repository.util import com.bitwarden.core.PassphraseGeneratorRequest import com.bitwarden.core.PasswordGeneratorRequest import com.bitwarden.core.PasswordHistoryView +import com.bitwarden.core.UsernameGeneratorRequest import com.x8bit.bitwarden.data.platform.repository.model.LocalDataState 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.PasscodeGenerationOptions @@ -28,6 +30,11 @@ class FakeGeneratorRepository : GeneratorRepository { private val mutablePasswordHistoryStateFlow = MutableStateFlow>>(LocalDataState.Loading) + private var generateForwardedServiceResult: GeneratedForwardedServiceUsernameResult = + GeneratedForwardedServiceUsernameResult.Success( + generatedEmailAddress = "updatedUsername", + ) + override val passwordHistoryStateFlow: StateFlow>> get() = mutablePasswordHistoryStateFlow @@ -44,6 +51,12 @@ class FakeGeneratorRepository : GeneratorRepository { return generatePassphraseResult } + override suspend fun generateForwardedServiceUsername( + forwardedServiceGeneratorRequest: UsernameGeneratorRequest.Forwarded, + ): GeneratedForwardedServiceUsernameResult { + return generateForwardedServiceResult + } + override fun getPasscodeGenerationOptions(): PasscodeGenerationOptions? { return passcodeGenerationOptions } @@ -90,4 +103,11 @@ class FakeGeneratorRepository : GeneratorRepository { fun emitPasswordHistoryState(state: LocalDataState>) { mutablePasswordHistoryStateFlow.value = state } + + /** + * Sets the mock result for the generateForwardedService function. + */ + fun setMockGenerateForwardedServiceResult(result: GeneratedForwardedServiceUsernameResult) { + generateForwardedServiceResult = result + } } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModelTest.kt index fa30076a45..b1ec349707 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModelTest.kt @@ -3,6 +3,7 @@ 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.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.PasscodeGenerationOptions @@ -942,6 +943,12 @@ class GeneratorViewModelTest : BaseViewModelTest() { .ADDY_IO, ) + fakeGeneratorRepository.setMockGenerateForwardedServiceResult( + GeneratedForwardedServiceUsernameResult.Success( + generatedEmailAddress = "defaultForwardedEmailAlias", + ), + ) + viewModel.actionChannel.trySend(action) val expectedState = defaultForwardedEmailAliasState.copy( @@ -989,6 +996,12 @@ class GeneratorViewModelTest : BaseViewModelTest() { accessToken = newAccessToken, ) + fakeGeneratorRepository.setMockGenerateForwardedServiceResult( + GeneratedForwardedServiceUsernameResult.Success( + generatedEmailAddress = "defaultAddyIo", + ), + ) + viewModel.actionChannel.trySend(action) val expectedState = defaultAddyIoState.copy( @@ -1006,7 +1019,6 @@ class GeneratorViewModelTest : BaseViewModelTest() { ), ), ) - assertEquals(expectedState, viewModel.stateFlow.value) } @@ -1023,6 +1035,12 @@ class GeneratorViewModelTest : BaseViewModelTest() { domain = newDomainName, ) + fakeGeneratorRepository.setMockGenerateForwardedServiceResult( + GeneratedForwardedServiceUsernameResult.Success( + generatedEmailAddress = "defaultAddyIo", + ), + ) + viewModel.actionChannel.trySend(action) val expectedState = defaultAddyIoState.copy( @@ -1067,6 +1085,12 @@ class GeneratorViewModelTest : BaseViewModelTest() { apiKey = newApiKey, ) + fakeGeneratorRepository.setMockGenerateForwardedServiceResult( + GeneratedForwardedServiceUsernameResult.Success( + generatedEmailAddress = "defaultDuckDuckGo", + ), + ) + viewModel.actionChannel.trySend(action) val expectedState = defaultDuckDuckGoState.copy( @@ -1111,6 +1135,12 @@ class GeneratorViewModelTest : BaseViewModelTest() { apiKey = newApiKey, ) + fakeGeneratorRepository.setMockGenerateForwardedServiceResult( + GeneratedForwardedServiceUsernameResult.Success( + generatedEmailAddress = "defaultFastMail", + ), + ) + viewModel.actionChannel.trySend(action) val expectedState = defaultFastMailState.copy( @@ -1156,6 +1186,12 @@ class GeneratorViewModelTest : BaseViewModelTest() { accessToken = newAccessToken, ) + fakeGeneratorRepository.setMockGenerateForwardedServiceResult( + GeneratedForwardedServiceUsernameResult.Success( + generatedEmailAddress = "defaultFirefoxRelay", + ), + ) + viewModel.actionChannel.trySend(action) val expectedState = defaultFirefoxRelayState.copy( @@ -1201,6 +1237,12 @@ class GeneratorViewModelTest : BaseViewModelTest() { apiKey = newApiKey, ) + fakeGeneratorRepository.setMockGenerateForwardedServiceResult( + GeneratedForwardedServiceUsernameResult.Success( + generatedEmailAddress = "defaultSimpleLogin", + ), + ) + viewModel.actionChannel.trySend(action) val expectedState = defaultSimpleLoginState.copy( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ServiceTypeExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ServiceTypeExtensionsTest.kt new file mode 100644 index 0000000000..ae9aa99aa3 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ServiceTypeExtensionsTest.kt @@ -0,0 +1,64 @@ +package com.x8bit.bitwarden.ui.tools.feature.generator.util + +import com.bitwarden.core.ForwarderServiceType +import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias.ServiceType +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assertions.assertEquals + +internal class ServiceTypeExtensionsTest { + + @Test + fun `toUsernameGeneratorRequest for AddyIo returns correct request`() { + val addyIoServiceType = ServiceType.AddyIo( + apiAccessToken = "testToken", + domainName = "test.com", + baseUrl = "http://test.com", + ) + val request = addyIoServiceType.toUsernameGeneratorRequest() + + assertEquals( + ForwarderServiceType.AddyIo( + apiToken = "testToken", + domain = "test.com", + baseUrl = "http://test.com", + ), request.service, + ) + assertEquals(null, request.website) + } + + @Test + fun `toUsernameGeneratorRequest for DuckDuckGo returns correct request`() { + val duckDuckGoServiceType = ServiceType.DuckDuckGo(apiKey = "testKey") + val request = duckDuckGoServiceType.toUsernameGeneratorRequest() + + assertEquals(ForwarderServiceType.DuckDuckGo("testKey"), request.service) + assertEquals(null, request.website) + } + + @Test + fun `toUsernameGeneratorRequest for FirefoxRelay returns correct request`() { + val firefoxRelayServiceType = ServiceType.FirefoxRelay(apiAccessToken = "testToken") + val request = firefoxRelayServiceType.toUsernameGeneratorRequest() + + assertEquals(ForwarderServiceType.Firefox(apiToken = "testToken"), request.service) + assertEquals(null, request.website) + } + + @Test + fun `toUsernameGeneratorRequest for FastMail returns correct request`() { + val fastMailServiceType = ServiceType.FastMail(apiKey = "testKey") + val request = fastMailServiceType.toUsernameGeneratorRequest() + + assertEquals(ForwarderServiceType.Fastmail(apiToken = "testKey"), request.service) + assertEquals(null, request.website) + } + + @Test + fun `toUsernameGeneratorRequest for SimpleLogin returns correct request`() { + val simpleLoginServiceType = ServiceType.SimpleLogin(apiKey = "testKey") + val request = simpleLoginServiceType.toUsernameGeneratorRequest() + + assertEquals(ForwarderServiceType.SimpleLogin(apiKey = "testKey"), request.service) + assertEquals(null, request.website) + } +}