BIT-654: Adding generator disk source implementation (#245)

This commit is contained in:
joshua-livefront
2023-11-14 15:07:48 -05:00
committed by GitHub
parent c7acbebed3
commit 7b22632c49
10 changed files with 463 additions and 1 deletions

View File

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

View File

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

View File

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