mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 07:05:35 +03:00
Add AuthSdkSource (#118)
This commit is contained in:
parent
84d10d7634
commit
69a4eef68f
12 changed files with 591 additions and 16 deletions
|
@ -149,7 +149,8 @@ koverReport {
|
|||
"*.*NavigationKt*",
|
||||
// Composable singletons
|
||||
"*.*ComposableSingletons*",
|
||||
|
||||
// Generated classes related to interfaces with default values
|
||||
"*.*DefaultImpls*",
|
||||
// OS-level components
|
||||
"com.x8bit.bitwarden.BitwardenApplication",
|
||||
"com.x8bit.bitwarden.MainActivity*",
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.sdk
|
||||
|
||||
import com.bitwarden.core.Kdf
|
||||
import com.bitwarden.core.MasterPasswordPolicyOptions
|
||||
import com.bitwarden.core.RegisterKeyResponse
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
|
||||
|
||||
/**
|
||||
* Source of authentication information and functionality from the Bitwarden SDK.
|
||||
*/
|
||||
interface AuthSdkSource {
|
||||
/**
|
||||
* Creates a hashed password provided the given [email], [password], and [kdf].
|
||||
* [kdf].
|
||||
*/
|
||||
suspend fun hashPassword(
|
||||
email: String,
|
||||
password: String,
|
||||
kdf: Kdf,
|
||||
): Result<String>
|
||||
|
||||
/**
|
||||
* Creates a set of encryption key information for registraation pers
|
||||
*/
|
||||
suspend fun makeRegisterKeys(
|
||||
email: String,
|
||||
password: String,
|
||||
kdf: Kdf,
|
||||
): Result<RegisterKeyResponse>
|
||||
|
||||
/**
|
||||
* Checks the password strength for the given [email] and [password] combination, along with
|
||||
* some [additionalInputs].
|
||||
*/
|
||||
suspend fun passwordStrength(
|
||||
email: String,
|
||||
password: String,
|
||||
additionalInputs: List<String> = emptyList(),
|
||||
): Result<PasswordStrength>
|
||||
|
||||
/**
|
||||
* Checks that the given [password] with the given [passwordStrength] satisfies the given
|
||||
* [policy]. Returns `true` if so and `false` otherwise.
|
||||
*/
|
||||
suspend fun satisfiesPolicy(
|
||||
password: String,
|
||||
passwordStrength: PasswordStrength,
|
||||
policy: MasterPasswordPolicyOptions,
|
||||
): Result<Boolean>
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.sdk
|
||||
|
||||
import com.bitwarden.core.Kdf
|
||||
import com.bitwarden.core.MasterPasswordPolicyOptions
|
||||
import com.bitwarden.core.RegisterKeyResponse
|
||||
import com.bitwarden.sdk.ClientAuth
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toPasswordStrengthOrNull
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toUByte
|
||||
|
||||
/**
|
||||
* Primary implementation of [AuthSdkSource] that serves as a convenience wrapper around a
|
||||
* [ClientAuth].
|
||||
*/
|
||||
class AuthSdkSourceImpl(
|
||||
private val clientAuth: ClientAuth,
|
||||
) : AuthSdkSource {
|
||||
|
||||
override suspend fun hashPassword(
|
||||
email: String,
|
||||
password: String,
|
||||
kdf: Kdf,
|
||||
): Result<String> = runCatching {
|
||||
clientAuth.hashPassword(
|
||||
email = email,
|
||||
password = password,
|
||||
kdfParams = kdf,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun makeRegisterKeys(
|
||||
email: String,
|
||||
password: String,
|
||||
kdf: Kdf,
|
||||
): Result<RegisterKeyResponse> = runCatching {
|
||||
clientAuth.makeRegisterKeys(
|
||||
email = email,
|
||||
password = password,
|
||||
kdf = kdf,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun passwordStrength(
|
||||
email: String,
|
||||
password: String,
|
||||
additionalInputs: List<String>,
|
||||
): Result<PasswordStrength> = runCatching {
|
||||
@Suppress("UnsafeCallOnNullableType")
|
||||
clientAuth
|
||||
.passwordStrength(
|
||||
password = password,
|
||||
email = email,
|
||||
additionalInputs = additionalInputs,
|
||||
)
|
||||
.toPasswordStrengthOrNull()!!
|
||||
}
|
||||
|
||||
override suspend fun satisfiesPolicy(
|
||||
password: String,
|
||||
passwordStrength: PasswordStrength,
|
||||
policy: MasterPasswordPolicyOptions,
|
||||
): Result<Boolean> = runCatching {
|
||||
clientAuth.satisfiesPolicy(
|
||||
password = password,
|
||||
strength = passwordStrength.toUByte(),
|
||||
policy = policy,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.sdk.di
|
||||
|
||||
import com.bitwarden.sdk.Client
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSourceImpl
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Provides SDK-related dependencies for the auth package.
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object SdkModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAuthSdkSource(
|
||||
client: Client,
|
||||
): AuthSdkSource = AuthSdkSourceImpl(clientAuth = client.auth())
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.sdk.model
|
||||
|
||||
/**
|
||||
* An estimate of password strength.
|
||||
*
|
||||
* Adapted from [zxcvbn](https://github.com/dropbox/zxcvbn#usage).
|
||||
*/
|
||||
enum class PasswordStrength {
|
||||
/**
|
||||
* Too guessable; very risky.
|
||||
*/
|
||||
LEVEL_0,
|
||||
|
||||
/**
|
||||
* Very guessable; limited protection.
|
||||
*/
|
||||
LEVEL_1,
|
||||
|
||||
/**
|
||||
* Somewhat guessable; some protection.
|
||||
*/
|
||||
LEVEL_2,
|
||||
|
||||
/**
|
||||
* Safely unguessable; moderate protection.
|
||||
*/
|
||||
LEVEL_3,
|
||||
|
||||
/**
|
||||
* Very unguessable; strong protection.
|
||||
*/
|
||||
LEVEL_4,
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.sdk.util
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
|
||||
|
||||
/**
|
||||
* Converts the given [Int] to a [PasswordStrength]. A `null` value is returned if this value is
|
||||
* not in the [0, 4] range.
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
fun Int.toPasswordStrengthOrNull(): PasswordStrength? =
|
||||
when (this) {
|
||||
0 -> PasswordStrength.LEVEL_0
|
||||
1 -> PasswordStrength.LEVEL_1
|
||||
2 -> PasswordStrength.LEVEL_2
|
||||
3 -> PasswordStrength.LEVEL_3
|
||||
4 -> PasswordStrength.LEVEL_4
|
||||
else -> null
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given [UByte] to a [PasswordStrength]. A `null` value is returned if this value is
|
||||
* not in the [0, 4] range.
|
||||
*/
|
||||
fun UByte.toPasswordStrengthOrNull(): PasswordStrength? =
|
||||
this.toInt().toPasswordStrengthOrNull()
|
||||
|
||||
/**
|
||||
* Converts the given [UInt] to a [PasswordStrength]. A `null` value is returned if this value is
|
||||
* not in the [0, 4] range.
|
||||
*/
|
||||
fun UInt.toPasswordStrengthOrNull(): PasswordStrength? =
|
||||
this.toInt().toPasswordStrengthOrNull()
|
||||
|
||||
/**
|
||||
* Converts the given [PasswordStrength] to an [Int].
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
fun PasswordStrength.toInt(): Int =
|
||||
when (this) {
|
||||
PasswordStrength.LEVEL_0 -> 0
|
||||
PasswordStrength.LEVEL_1 -> 1
|
||||
PasswordStrength.LEVEL_2 -> 2
|
||||
PasswordStrength.LEVEL_3 -> 3
|
||||
PasswordStrength.LEVEL_4 -> 4
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given [PasswordStrength] to a [UByte].
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
fun PasswordStrength.toUByte(): UByte =
|
||||
this.toInt().toUByte()
|
||||
|
||||
/**
|
||||
* Converts the given [PasswordStrength] to a [UInt].
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
fun PasswordStrength.toUInt(): UInt =
|
||||
this.toInt().toUInt()
|
|
@ -1,6 +1,5 @@
|
|||
package com.x8bit.bitwarden.data.auth.repository
|
||||
|
||||
import com.bitwarden.sdk.Client
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthState
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
|
||||
|
@ -10,6 +9,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.LoginResult
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.util.CaptchaCallbackTokenResult
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
||||
import com.x8bit.bitwarden.data.auth.util.toSdkParams
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor
|
||||
import com.x8bit.bitwarden.data.platform.util.flatMap
|
||||
|
@ -29,7 +29,7 @@ import javax.inject.Singleton
|
|||
class AuthRepositoryImpl @Inject constructor(
|
||||
private val accountsService: AccountsService,
|
||||
private val identityService: IdentityService,
|
||||
private val bitwardenSdkClient: Client,
|
||||
private val authSdkSource: AuthSdkSource,
|
||||
private val authDiskSource: AuthDiskSource,
|
||||
private val authTokenInterceptor: AuthTokenInterceptor,
|
||||
) : AuthRepository {
|
||||
|
@ -55,13 +55,13 @@ class AuthRepositoryImpl @Inject constructor(
|
|||
): LoginResult = accountsService
|
||||
.preLogin(email = email)
|
||||
.flatMap {
|
||||
val passwordHash = bitwardenSdkClient
|
||||
.auth()
|
||||
.hashPassword(
|
||||
email = email,
|
||||
password = password,
|
||||
kdfParams = it.kdfParams.toSdkParams(),
|
||||
)
|
||||
authSdkSource.hashPassword(
|
||||
email = email,
|
||||
password = password,
|
||||
kdf = it.kdfParams.toSdkParams(),
|
||||
)
|
||||
}
|
||||
.flatMap { passwordHash ->
|
||||
identityService.getToken(
|
||||
email = email,
|
||||
passwordHash = passwordHash,
|
||||
|
|
|
@ -8,3 +8,15 @@ inline fun <T, R> Result<T>.flatMap(transform: (T) -> Result<R>): Result<R> =
|
|||
this.exceptionOrNull()
|
||||
?.let { Result.failure(it) }
|
||||
?: transform(this.getOrThrow())
|
||||
|
||||
/**
|
||||
* Returns the given receiver of type [T] as a "success" [Result].
|
||||
*/
|
||||
fun <T> T.asSuccess(): Result<T> =
|
||||
Result.success(this)
|
||||
|
||||
/**
|
||||
* Returns the given [Throwable] as a "failure" [Result].
|
||||
*/
|
||||
fun Throwable.asFailure(): Result<Nothing> =
|
||||
Result.failure(this)
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.sdk
|
||||
|
||||
import com.bitwarden.core.Kdf
|
||||
import com.bitwarden.core.MasterPasswordPolicyOptions
|
||||
import com.bitwarden.core.RegisterKeyResponse
|
||||
import com.bitwarden.sdk.ClientAuth
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
|
||||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class AuthSdkSourceTest {
|
||||
private val clientAuth = mockk<ClientAuth>()
|
||||
|
||||
private val authSkdSource: AuthSdkSource = AuthSdkSourceImpl(
|
||||
clientAuth = clientAuth,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `hashPassword should call SDK and return a Result with the correct data`() = runBlocking {
|
||||
val email = "email"
|
||||
val password = "password"
|
||||
val kdf = mockk<Kdf>()
|
||||
val expectedResult = "hashedPassword"
|
||||
coEvery {
|
||||
clientAuth.hashPassword(
|
||||
email = email,
|
||||
password = password,
|
||||
kdfParams = kdf,
|
||||
)
|
||||
} returns expectedResult
|
||||
|
||||
val result = authSkdSource.hashPassword(
|
||||
email = email,
|
||||
password = password,
|
||||
kdf = kdf,
|
||||
)
|
||||
assertEquals(
|
||||
expectedResult.asSuccess(),
|
||||
result,
|
||||
)
|
||||
coVerify {
|
||||
clientAuth.hashPassword(
|
||||
email = email,
|
||||
password = password,
|
||||
kdfParams = kdf,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `makeRegisterKeys should call SDK and return a Result with the correct data`() =
|
||||
runBlocking {
|
||||
val email = "email"
|
||||
val password = "password"
|
||||
val kdf = mockk<Kdf>()
|
||||
val expectedResult = mockk<RegisterKeyResponse>()
|
||||
coEvery {
|
||||
clientAuth.makeRegisterKeys(
|
||||
email = email,
|
||||
password = password,
|
||||
kdf = kdf,
|
||||
)
|
||||
} returns expectedResult
|
||||
|
||||
val result = authSkdSource.makeRegisterKeys(
|
||||
email = email,
|
||||
password = password,
|
||||
kdf = kdf,
|
||||
)
|
||||
assertEquals(
|
||||
expectedResult.asSuccess(),
|
||||
result,
|
||||
)
|
||||
coVerify {
|
||||
clientAuth.makeRegisterKeys(
|
||||
email = email,
|
||||
password = password,
|
||||
kdf = kdf,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This test is disabled due to issue here with mocking UByte (BIT-877).
|
||||
// See: https://github.com/mockk/mockk/issues/544
|
||||
@Disabled
|
||||
@Test
|
||||
fun `passwordStrength should call SDK and return a Result with the correct data`() =
|
||||
runBlocking {
|
||||
val email = "email"
|
||||
val password = "password"
|
||||
val additionalInputs = listOf("test1", "test2")
|
||||
val sdkResult = 3.toUByte()
|
||||
val expectedResult = PasswordStrength.LEVEL_3
|
||||
coEvery {
|
||||
clientAuth.passwordStrength(
|
||||
email = email,
|
||||
password = password,
|
||||
additionalInputs = additionalInputs,
|
||||
)
|
||||
} returns sdkResult
|
||||
|
||||
val result = authSkdSource.passwordStrength(
|
||||
email = email,
|
||||
password = password,
|
||||
additionalInputs = additionalInputs,
|
||||
)
|
||||
assertEquals(
|
||||
expectedResult.asSuccess(),
|
||||
result,
|
||||
)
|
||||
coVerify {
|
||||
clientAuth.passwordStrength(
|
||||
email = email,
|
||||
password = password,
|
||||
additionalInputs = additionalInputs,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `satisfiesPolicy should call SDK and return a Result with the correct data`() =
|
||||
runBlocking {
|
||||
val password = "password"
|
||||
val passwordStrength = PasswordStrength.LEVEL_3
|
||||
val rawStrength = 3.toUByte()
|
||||
val policy = mockk<MasterPasswordPolicyOptions>()
|
||||
val expectedResult = true
|
||||
coEvery {
|
||||
clientAuth.satisfiesPolicy(
|
||||
password = password,
|
||||
strength = rawStrength,
|
||||
policy = policy,
|
||||
)
|
||||
} returns expectedResult
|
||||
|
||||
val result = authSkdSource.satisfiesPolicy(
|
||||
password = password,
|
||||
passwordStrength = passwordStrength,
|
||||
policy = policy,
|
||||
)
|
||||
assertEquals(
|
||||
expectedResult.asSuccess(),
|
||||
result,
|
||||
)
|
||||
coVerify {
|
||||
clientAuth.satisfiesPolicy(
|
||||
password = password,
|
||||
strength = rawStrength,
|
||||
policy = policy,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.auth.repository
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.sdk.Client
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthState
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
|
||||
|
@ -10,6 +9,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJs
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.util.CaptchaCallbackTokenResult
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
||||
import com.x8bit.bitwarden.data.auth.util.toSdkParams
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor
|
||||
import io.mockk.clearMocks
|
||||
|
@ -30,20 +30,20 @@ class AuthRepositoryTest {
|
|||
private val identityService: IdentityService = mockk()
|
||||
private val authInterceptor = mockk<AuthTokenInterceptor>()
|
||||
private val fakeAuthDiskSource = FakeAuthDiskSource()
|
||||
private val mockBitwardenSdk = mockk<Client> {
|
||||
private val authSdkSource = mockk<AuthSdkSource> {
|
||||
coEvery {
|
||||
auth().hashPassword(
|
||||
hashPassword(
|
||||
email = EMAIL,
|
||||
password = PASSWORD,
|
||||
kdfParams = PRE_LOGIN_SUCCESS.kdfParams.toSdkParams(),
|
||||
kdf = PRE_LOGIN_SUCCESS.kdfParams.toSdkParams(),
|
||||
)
|
||||
} returns PASSWORD_HASH
|
||||
} returns Result.success(PASSWORD_HASH)
|
||||
}
|
||||
|
||||
private val repository = AuthRepositoryImpl(
|
||||
accountsService = accountsService,
|
||||
identityService = identityService,
|
||||
bitwardenSdkClient = mockBitwardenSdk,
|
||||
authSdkSource = authSdkSource,
|
||||
authDiskSource = fakeAuthDiskSource,
|
||||
authTokenInterceptor = authInterceptor,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.sdk.util
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toInt
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toPasswordStrengthOrNull
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toUByte
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toUInt
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class PasswordStrengthExtensionsTest {
|
||||
@Nested
|
||||
inner class IntegerType {
|
||||
@Test
|
||||
fun `toPasswordStrengthOrNull returns the correct values in 0 to 4 range`() {
|
||||
mapOf(
|
||||
0 to PasswordStrength.LEVEL_0,
|
||||
1 to PasswordStrength.LEVEL_1,
|
||||
2 to PasswordStrength.LEVEL_2,
|
||||
3 to PasswordStrength.LEVEL_3,
|
||||
4 to PasswordStrength.LEVEL_4,
|
||||
)
|
||||
.forEach { (intValue, level) ->
|
||||
assertEquals(
|
||||
level,
|
||||
intValue.toPasswordStrengthOrNull(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toPasswordStrengthOrNull returns null outside the 0 to 4 range`() {
|
||||
listOf(-2, -1, 5, 6).forEach { intValue ->
|
||||
assertNull(
|
||||
intValue.toPasswordStrengthOrNull(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toInt returns the correct Int for each level`() {
|
||||
mapOf(
|
||||
PasswordStrength.LEVEL_0 to 0,
|
||||
PasswordStrength.LEVEL_1 to 1,
|
||||
PasswordStrength.LEVEL_2 to 2,
|
||||
PasswordStrength.LEVEL_3 to 3,
|
||||
PasswordStrength.LEVEL_4 to 4,
|
||||
)
|
||||
.forEach { (level, intValue) ->
|
||||
assertEquals(
|
||||
intValue,
|
||||
level.toInt(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class UByteType {
|
||||
@Test
|
||||
fun `toPasswordStrengthOrNull returns the correct values in 0 to 4 range`() {
|
||||
mapOf(
|
||||
0.toUByte() to PasswordStrength.LEVEL_0,
|
||||
1.toUByte() to PasswordStrength.LEVEL_1,
|
||||
2.toUByte() to PasswordStrength.LEVEL_2,
|
||||
3.toUByte() to PasswordStrength.LEVEL_3,
|
||||
4.toUByte() to PasswordStrength.LEVEL_4,
|
||||
)
|
||||
.forEach { (uByteValue, level) ->
|
||||
assertEquals(
|
||||
level,
|
||||
uByteValue.toPasswordStrengthOrNull(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toPasswordStrengthOrNull returns null outside the 0 to 4 range`() {
|
||||
listOf(5.toUByte(), 6.toUByte()).forEach { uByteValue ->
|
||||
assertNull(
|
||||
uByteValue.toPasswordStrengthOrNull(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toUByte returns the correct UByte for each level`() {
|
||||
mapOf(
|
||||
PasswordStrength.LEVEL_0 to 0.toUByte(),
|
||||
PasswordStrength.LEVEL_1 to 1.toUByte(),
|
||||
PasswordStrength.LEVEL_2 to 2.toUByte(),
|
||||
PasswordStrength.LEVEL_3 to 3.toUByte(),
|
||||
PasswordStrength.LEVEL_4 to 4.toUByte(),
|
||||
)
|
||||
.forEach { (level, uByteValue) ->
|
||||
assertEquals(
|
||||
uByteValue,
|
||||
level.toUByte(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class UIntType {
|
||||
@Test
|
||||
fun `toPasswordStrengthOrNull returns the correct values in 0 to 4 range`() {
|
||||
mapOf(
|
||||
0u to PasswordStrength.LEVEL_0,
|
||||
1u to PasswordStrength.LEVEL_1,
|
||||
2u to PasswordStrength.LEVEL_2,
|
||||
3u to PasswordStrength.LEVEL_3,
|
||||
4u to PasswordStrength.LEVEL_4,
|
||||
)
|
||||
.forEach { (uIntValue, level) ->
|
||||
assertEquals(
|
||||
level,
|
||||
uIntValue.toPasswordStrengthOrNull(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toPasswordStrengthOrNull returns null outside the 0 to 4 range`() {
|
||||
listOf(5u, 6u).forEach { uIntValue ->
|
||||
assertNull(
|
||||
uIntValue.toPasswordStrengthOrNull(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toUInt returns the correct UInt for each level`() {
|
||||
mapOf(
|
||||
PasswordStrength.LEVEL_0 to 0u,
|
||||
PasswordStrength.LEVEL_1 to 1u,
|
||||
PasswordStrength.LEVEL_2 to 2u,
|
||||
PasswordStrength.LEVEL_3 to 3u,
|
||||
PasswordStrength.LEVEL_4 to 4u,
|
||||
)
|
||||
.forEach { (level, uIntValue) ->
|
||||
assertEquals(
|
||||
uIntValue,
|
||||
level.toUInt(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -49,4 +49,21 @@ class ResultTest {
|
|||
assertTrue(stringResult.isFailure)
|
||||
assertEquals(expectedException, stringResult.exceptionOrNull())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `asSuccess returns a success Result with the correct content`() {
|
||||
assertEquals(
|
||||
Result.success("Test"),
|
||||
"Test".asSuccess(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `asFailure returns a failure Result with the correct content`() {
|
||||
val throwable = IllegalStateException("Test")
|
||||
assertEquals(
|
||||
Result.failure<Nothing>(throwable),
|
||||
throwable.asFailure(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue