From 7b22632c491ee8d387474c61692b825d3da458ef Mon Sep 17 00:00:00 2001 From: joshua-livefront <139182194+joshua-livefront@users.noreply.github.com> Date: Tue, 14 Nov 2023 15:07:48 -0500 Subject: [PATCH] BIT-654: Adding generator disk source implementation (#245) --- .../datasource/disk/GeneratorDiskSource.kt | 19 ++ .../disk/GeneratorDiskSourceImpl.kt | 43 +++++ .../datasource/disk/di/GeneratorDiskModule.kt | 30 ++++ .../repository/GeneratorRepository.kt | 11 ++ .../repository/GeneratorRepositoryImpl.kt | 15 ++ .../di/GeneratorRepositoryModule.kt | 10 +- .../model/PasswordGenerationOptions.kt | 51 ++++++ .../disk/GeneratorDiskSourceTest.kt | 84 +++++++++ .../repository/GeneratorRepositoryTest.kt | 170 ++++++++++++++++++ .../util/FakeGeneratorRepository.kt | 31 ++++ 10 files changed, 463 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/generator/datasource/disk/GeneratorDiskSource.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/generator/datasource/disk/GeneratorDiskSourceImpl.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/generator/datasource/disk/di/GeneratorDiskModule.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/generator/repository/model/PasswordGenerationOptions.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/generator/datasource/disk/GeneratorDiskSourceTest.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/generator/repository/util/FakeGeneratorRepository.kt diff --git a/app/src/main/java/com/x8bit/bitwarden/data/generator/datasource/disk/GeneratorDiskSource.kt b/app/src/main/java/com/x8bit/bitwarden/data/generator/datasource/disk/GeneratorDiskSource.kt new file mode 100644 index 0000000000..8e138e2235 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/generator/datasource/disk/GeneratorDiskSource.kt @@ -0,0 +1,19 @@ +package com.x8bit.bitwarden.data.generator.datasource.disk + +import com.x8bit.bitwarden.data.generator.repository.model.PasswordGenerationOptions + +/** + * Primary access point for disk information related to generation. + */ +interface GeneratorDiskSource { + + /** + * Retrieves a user's password generation options using a [userId]. + */ + fun getPasswordGenerationOptions(userId: String): PasswordGenerationOptions? + + /** + * Stores a user's password generation options using a [userId]. + */ + fun storePasswordGenerationOptions(userId: String, options: PasswordGenerationOptions?) +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/generator/datasource/disk/GeneratorDiskSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/generator/datasource/disk/GeneratorDiskSourceImpl.kt new file mode 100644 index 0000000000..ae7b6eb1f6 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/generator/datasource/disk/GeneratorDiskSourceImpl.kt @@ -0,0 +1,43 @@ +package com.x8bit.bitwarden.data.generator.datasource.disk + +import android.content.SharedPreferences +import com.x8bit.bitwarden.data.generator.repository.model.PasswordGenerationOptions +import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +private const val PASSWORD_GENERATION_OPTIONS_KEY = "passwordGenerationOptions" + +/** + * Primary implementation of [GeneratorDiskSource]. + */ +class GeneratorDiskSourceImpl( + sharedPreferences: SharedPreferences, + private val json: Json, +) : BaseDiskSource(sharedPreferences), + GeneratorDiskSource { + + override fun getPasswordGenerationOptions(userId: String): PasswordGenerationOptions? { + val key = getPasswordGenerationOptionsKey(userId) + return getString(key)?.let { json.decodeFromString(it) } + } + + override fun storePasswordGenerationOptions( + userId: String, + options: PasswordGenerationOptions?, + ) { + val key = getPasswordGenerationOptionsKey(userId) + putString( + key, + options?.let { json.encodeToString(options) }, + ) + } + + override fun onSharedPreferenceChanged( + sharedPreferences: SharedPreferences?, + key: String?, + ) = Unit + + private fun getPasswordGenerationOptionsKey(userId: String): String = + "${BASE_KEY}_${PASSWORD_GENERATION_OPTIONS_KEY}_$userId" +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/generator/datasource/disk/di/GeneratorDiskModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/generator/datasource/disk/di/GeneratorDiskModule.kt new file mode 100644 index 0000000000..65f769f26f --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/generator/datasource/disk/di/GeneratorDiskModule.kt @@ -0,0 +1,30 @@ +package com.x8bit.bitwarden.data.generator.datasource.disk.di + +import android.content.SharedPreferences +import com.x8bit.bitwarden.data.generator.datasource.disk.GeneratorDiskSource +import com.x8bit.bitwarden.data.generator.datasource.disk.GeneratorDiskSourceImpl +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.serialization.json.Json +import javax.inject.Singleton + +/** + * Provides persistence-related dependencies for the generator package. + */ +@Module +@InstallIn(SingletonComponent::class) +object GeneratorDiskModule { + + @Provides + @Singleton + fun provideGeneratorDiskSource( + sharedPreferences: SharedPreferences, + json: Json, + ): GeneratorDiskSource = + GeneratorDiskSourceImpl( + sharedPreferences = sharedPreferences, + json = json, + ) +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/generator/repository/GeneratorRepository.kt b/app/src/main/java/com/x8bit/bitwarden/data/generator/repository/GeneratorRepository.kt index 725c2b01e1..9c4fdcbfd6 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/generator/repository/GeneratorRepository.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/generator/repository/GeneratorRepository.kt @@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.generator.repository import com.bitwarden.core.PasswordGeneratorRequest import com.x8bit.bitwarden.data.generator.repository.model.GeneratedPasswordResult +import com.x8bit.bitwarden.data.generator.repository.model.PasswordGenerationOptions /** * Responsible for managing generator data. @@ -14,4 +15,14 @@ interface GeneratorRepository { suspend fun generatePassword( passwordGeneratorRequest: PasswordGeneratorRequest, ): GeneratedPasswordResult + + /** + * Get the [PasswordGenerationOptions] for the current user. + */ + fun getPasswordGenerationOptions(): PasswordGenerationOptions? + + /** + * Save the [PasswordGenerationOptions] for the current user. + */ + fun savePasswordGenerationOptions(options: PasswordGenerationOptions) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/generator/repository/GeneratorRepositoryImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/generator/repository/GeneratorRepositoryImpl.kt index 7db2de46e2..9cdeac22b1 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/generator/repository/GeneratorRepositoryImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/generator/repository/GeneratorRepositoryImpl.kt @@ -1,8 +1,11 @@ package com.x8bit.bitwarden.data.generator.repository import com.bitwarden.core.PasswordGeneratorRequest +import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource +import com.x8bit.bitwarden.data.generator.datasource.disk.GeneratorDiskSource import com.x8bit.bitwarden.data.generator.datasource.sdk.GeneratorSdkSource import com.x8bit.bitwarden.data.generator.repository.model.GeneratedPasswordResult +import com.x8bit.bitwarden.data.generator.repository.model.PasswordGenerationOptions import javax.inject.Singleton /** @@ -11,6 +14,8 @@ import javax.inject.Singleton @Singleton class GeneratorRepositoryImpl constructor( private val generatorSdkSource: GeneratorSdkSource, + private val generatorDiskSource: GeneratorDiskSource, + private val authDiskSource: AuthDiskSource, ) : GeneratorRepository { override suspend fun generatePassword( @@ -22,4 +27,14 @@ class GeneratorRepositoryImpl constructor( onSuccess = { GeneratedPasswordResult.Success(it) }, onFailure = { GeneratedPasswordResult.InvalidRequest }, ) + + override fun getPasswordGenerationOptions(): PasswordGenerationOptions? { + val userId = authDiskSource.userState?.activeUserId + return userId?.let { generatorDiskSource.getPasswordGenerationOptions(it) } + } + + override fun savePasswordGenerationOptions(options: PasswordGenerationOptions) { + val userId = authDiskSource.userState?.activeUserId + userId?.let { generatorDiskSource.storePasswordGenerationOptions(it, options) } + } } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/generator/repository/di/GeneratorRepositoryModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/generator/repository/di/GeneratorRepositoryModule.kt index 7ab1306c53..569d913dcb 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/generator/repository/di/GeneratorRepositoryModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/generator/repository/di/GeneratorRepositoryModule.kt @@ -1,5 +1,7 @@ package com.x8bit.bitwarden.data.generator.repository.di +import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource +import com.x8bit.bitwarden.data.generator.datasource.disk.GeneratorDiskSource import com.x8bit.bitwarden.data.generator.datasource.sdk.GeneratorSdkSource import com.x8bit.bitwarden.data.generator.repository.GeneratorRepository import com.x8bit.bitwarden.data.generator.repository.GeneratorRepositoryImpl @@ -20,5 +22,11 @@ object GeneratorRepositoryModule { @Singleton fun provideGeneratorRepository( generatorSdkSource: GeneratorSdkSource, - ): GeneratorRepository = GeneratorRepositoryImpl(generatorSdkSource) + generatorDiskSource: GeneratorDiskSource, + authDiskSource: AuthDiskSource, + ): GeneratorRepository = GeneratorRepositoryImpl( + generatorSdkSource = generatorSdkSource, + generatorDiskSource = generatorDiskSource, + authDiskSource = authDiskSource, + ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/generator/repository/model/PasswordGenerationOptions.kt b/app/src/main/java/com/x8bit/bitwarden/data/generator/repository/model/PasswordGenerationOptions.kt new file mode 100644 index 0000000000..262b378dd5 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/generator/repository/model/PasswordGenerationOptions.kt @@ -0,0 +1,51 @@ +package com.x8bit.bitwarden.data.generator.repository.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * A data class representing the configuration options for password generation. + * + * @property length The total length of the generated password. + * @property allowAmbiguousChar Indicates whether ambiguous characters are allowed in the password. + * @property hasNumbers Indicates whether the password should contain numbers. + * @property minNumber The minimum number of numeric characters required in the password. + * @property hasUppercase Indicates whether the password should contain uppercase characters. + * @property minUppercase The minimum number of uppercase characters required in the password. + * @property hasLowercase Indicates whether the password should contain lowercase characters. + * @property minLowercase The minimum number of lowercase characters required in the password. + * @property allowSpecial Indicates whether special characters are allowed in the password. + * @property minSpecial The minimum number of special characters required in the password. + */ +@Serializable +data class PasswordGenerationOptions( + @SerialName("length") + val length: Int, + + @SerialName("allowAmbiguousChar") + val allowAmbiguousChar: Boolean, + + @SerialName("number") + val hasNumbers: Boolean, + + @SerialName("minNumber") + val minNumber: Int, + + @SerialName("uppercase") + val hasUppercase: Boolean, + + @SerialName("minUppercase") + val minUppercase: Int?, + + @SerialName("lowercase") + val hasLowercase: Boolean, + + @SerialName("minLowercase") + val minLowercase: Int?, + + @SerialName("special") + val allowSpecial: Boolean, + + @SerialName("minSpecial") + val minSpecial: Int, +) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/generator/datasource/disk/GeneratorDiskSourceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/generator/datasource/disk/GeneratorDiskSourceTest.kt new file mode 100644 index 0000000000..18861e7675 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/generator/datasource/disk/GeneratorDiskSourceTest.kt @@ -0,0 +1,84 @@ +package com.x8bit.bitwarden.data.generator.datasource.disk + +import com.x8bit.bitwarden.data.generator.repository.model.PasswordGenerationOptions +import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test + +class GeneratorDiskSourceTest { + private val fakeSharedPreferences = FakeSharedPreferences() + + @OptIn(ExperimentalSerializationApi::class) + private val json = Json { + ignoreUnknownKeys = true + explicitNulls = false + } + + private val generatorDiskSource = GeneratorDiskSourceImpl( + sharedPreferences = fakeSharedPreferences, + json = json, + ) + + @Test + fun `getPasswordGenerationOptions should return correct options when available`() { + val userId = "user123" + val options = PasswordGenerationOptions( + length = 14, + allowAmbiguousChar = false, + hasNumbers = true, + minNumber = 0, + hasUppercase = true, + minUppercase = null, + hasLowercase = false, + minLowercase = null, + allowSpecial = false, + minSpecial = 1, + ) + + val key = "bwPreferencesStorage_passwordGenerationOptions_$userId" + fakeSharedPreferences.edit().putString(key, json.encodeToString(options)).apply() + + val result = generatorDiskSource.getPasswordGenerationOptions(userId) + + assertEquals(options, result) + } + + @Test + fun `getPasswordGenerationOptions should return null when options are not available`() { + val userId = "user123" + + val result = generatorDiskSource.getPasswordGenerationOptions(userId) + + assertNull(result) + } + + @Test + fun `storePasswordGenerationOptions should correctly store options`() { + val userId = "user123" + val options = PasswordGenerationOptions( + length = 14, + allowAmbiguousChar = false, + hasNumbers = true, + minNumber = 0, + hasUppercase = true, + minUppercase = null, + hasLowercase = false, + minLowercase = null, + allowSpecial = false, + minSpecial = 1, + ) + + val key = "bwPreferencesStorage_passwordGenerationOptions_$userId" + + generatorDiskSource.storePasswordGenerationOptions(userId, options) + + val storedValue = fakeSharedPreferences.getString(key, null) + assertNotNull(storedValue) + assertEquals(json.encodeToString(options), storedValue) + } +} diff --git a/app/src/test/java/com/x8bit/bitwarden/data/generator/repository/GeneratorRepositoryTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/generator/repository/GeneratorRepositoryTest.kt index e593a5c6eb..d40cbeb509 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/generator/repository/GeneratorRepositoryTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/generator/repository/GeneratorRepositoryTest.kt @@ -1,14 +1,28 @@ package com.x8bit.bitwarden.data.generator.repository import com.bitwarden.core.PasswordGeneratorRequest +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 +import com.x8bit.bitwarden.data.auth.datasource.disk.model.ForcePasswordResetReason +import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson +import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson +import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorUserDecryptionOptionsJson +import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceUserDecryptionOptionsJson +import com.x8bit.bitwarden.data.auth.datasource.network.model.UserDecryptionOptionsJson +import com.x8bit.bitwarden.data.generator.datasource.disk.GeneratorDiskSource import com.x8bit.bitwarden.data.generator.datasource.sdk.GeneratorSdkSource import com.x8bit.bitwarden.data.generator.repository.model.GeneratedPasswordResult +import com.x8bit.bitwarden.data.generator.repository.model.PasswordGenerationOptions +import io.mockk.Runs import io.mockk.clearMocks import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.just import io.mockk.mockk import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -16,9 +30,13 @@ import org.junit.jupiter.api.Test class GeneratorRepositoryTest { private val generatorSdkSource: GeneratorSdkSource = mockk() + private val generatorDiskSource: GeneratorDiskSource = mockk() + private val authDiskSource: AuthDiskSource = mockk() private val repository = GeneratorRepositoryImpl( generatorSdkSource = generatorSdkSource, + generatorDiskSource = generatorDiskSource, + authDiskSource = authDiskSource, ) @BeforeEach @@ -73,4 +91,156 @@ class GeneratorRepositoryTest { assertTrue(result is GeneratedPasswordResult.InvalidRequest) coVerify { generatorSdkSource.generatePassword(request) } } + + @Test + fun `getPasswordGenerationOptions should return options when available`() = runTest { + val userId = "activeUserId" + val expectedOptions = PasswordGenerationOptions( + length = 14, + allowAmbiguousChar = false, + hasNumbers = true, + minNumber = 0, + hasUppercase = true, + minUppercase = null, + hasLowercase = false, + minLowercase = null, + allowSpecial = false, + minSpecial = 1, + ) + + coEvery { authDiskSource.userState } returns USER_STATE + + coEvery { + generatorDiskSource.getPasswordGenerationOptions(userId) + } returns expectedOptions + + val result = repository.getPasswordGenerationOptions() + + assertEquals(expectedOptions, result) + coVerify { generatorDiskSource.getPasswordGenerationOptions(userId) } + } + + @Test + fun `getPasswordGenerationOptions should return null when there is no active user`() = runTest { + coEvery { authDiskSource.userState } returns null + + val result = repository.getPasswordGenerationOptions() + + assertNull(result) + coVerify(exactly = 0) { generatorDiskSource.getPasswordGenerationOptions(any()) } + } + + @Suppress("MaxLineLength") + @Test + fun `getPasswordGenerationOptions should return null when no data is stored for active user`() = runTest { + val userId = "activeUserId" + coEvery { authDiskSource.userState } returns USER_STATE + coEvery { generatorDiskSource.getPasswordGenerationOptions(userId) } returns null + + val result = repository.getPasswordGenerationOptions() + + assertNull(result) + coVerify { generatorDiskSource.getPasswordGenerationOptions(userId) } + } + + @Test + fun `savePasswordGenerationOptions should store options correctly`() = runTest { + val userId = "activeUserId" + val optionsToSave = PasswordGenerationOptions( + length = 14, + allowAmbiguousChar = false, + hasNumbers = true, + minNumber = 0, + hasUppercase = true, + minUppercase = null, + hasLowercase = false, + minLowercase = null, + allowSpecial = false, + minSpecial = 1, + ) + + coEvery { authDiskSource.userState } returns USER_STATE + + coEvery { + generatorDiskSource.storePasswordGenerationOptions(userId, optionsToSave) + } just Runs + + repository.savePasswordGenerationOptions(optionsToSave) + + coVerify { generatorDiskSource.storePasswordGenerationOptions(userId, optionsToSave) } + } + + @Suppress("MaxLineLength") + @Test + fun `savePasswordGenerationOptions should not store options when there is no active user`() = runTest { + val optionsToSave = PasswordGenerationOptions( + length = 14, + allowAmbiguousChar = false, + hasNumbers = true, + minNumber = 0, + hasUppercase = true, + minUppercase = null, + hasLowercase = false, + minLowercase = null, + allowSpecial = false, + minSpecial = 1, + ) + + coEvery { authDiskSource.userState } returns null + + repository.savePasswordGenerationOptions(optionsToSave) + + coVerify(exactly = 0) { generatorDiskSource.storePasswordGenerationOptions(any(), any()) } + } + + private val USER_STATE = UserStateJson( + activeUserId = "activeUserId", + accounts = mapOf( + "activeUserId" to AccountJson( + profile = AccountJson.Profile( + userId = "activeUserId", + email = "email", + isEmailVerified = true, + name = "name", + stamp = "stamp", + organizationId = "organizationId", + avatarColorHex = "avatarColorHex", + hasPremium = true, + forcePasswordResetReason = ForcePasswordResetReason.ADMIN_FORCE_PASSWORD_RESET, + kdfType = KdfTypeJson.ARGON2_ID, + kdfIterations = 600000, + kdfMemory = 16, + kdfParallelism = 4, + userDecryptionOptions = UserDecryptionOptionsJson( + hasMasterPassword = true, + trustedDeviceUserDecryptionOptions = TrustedDeviceUserDecryptionOptionsJson( + encryptedPrivateKey = "encryptedPrivateKey", + encryptedUserKey = "encryptedUserKey", + hasAdminApproval = true, + hasLoginApprovingDevice = true, + hasManageResetPasswordPermission = true, + ), + keyConnectorUserDecryptionOptions = KeyConnectorUserDecryptionOptionsJson( + keyConnectorUrl = "keyConnectorUrl", + ), + ), + ), + tokens = AccountJson.Tokens( + accessToken = "accessToken", + refreshToken = "refreshToken", + ), + settings = AccountJson.Settings( + environmentUrlData = EnvironmentUrlDataJson( + base = "base", + api = "api", + identity = "identity", + icon = "icon", + notifications = "notifications", + webVault = "webVault", + events = "events", + ), + ), + ), + ), + ) } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/generator/repository/util/FakeGeneratorRepository.kt b/app/src/test/java/com/x8bit/bitwarden/data/generator/repository/util/FakeGeneratorRepository.kt new file mode 100644 index 0000000000..86f0209c0a --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/generator/repository/util/FakeGeneratorRepository.kt @@ -0,0 +1,31 @@ +package com.x8bit.bitwarden.data.generator.repository.util + +import com.bitwarden.core.PasswordGeneratorRequest +import com.x8bit.bitwarden.data.generator.repository.GeneratorRepository +import com.x8bit.bitwarden.data.generator.repository.model.GeneratedPasswordResult +import com.x8bit.bitwarden.data.generator.repository.model.PasswordGenerationOptions + +/** + * A fake implementation of [GeneratorRepository] for testing purposes. + * This class provides a simplified way to set up and control responses for repository methods. + */ +class FakeGeneratorRepository : GeneratorRepository { + private var generatePasswordResult: GeneratedPasswordResult = GeneratedPasswordResult.Success( + generatedString = "pa11w0rd", + ) + private var passwordGenerationOptions: PasswordGenerationOptions? = null + + override suspend fun generatePassword( + passwordGeneratorRequest: PasswordGeneratorRequest, + ): GeneratedPasswordResult { + return generatePasswordResult + } + + override fun getPasswordGenerationOptions(): PasswordGenerationOptions? { + return passwordGenerationOptions + } + + override fun savePasswordGenerationOptions(options: PasswordGenerationOptions) { + passwordGenerationOptions = options + } +}