BIT-654: Generator SDK interface and repository implementation (#233)

This commit is contained in:
joshua-livefront 2023-11-09 17:08:37 -05:00 committed by Álison Fernandes
parent 238f6e92c8
commit 698d8c745b
9 changed files with 262 additions and 0 deletions

View file

@ -0,0 +1,14 @@
package com.x8bit.bitwarden.data.generator.datasource.sdk
import com.bitwarden.core.PasswordGeneratorRequest
/**
* Source of password generation functionality from the Bitwarden SDK.
*/
interface GeneratorSdkSource {
/**
* Generates a password returning a [String] wrapped in a [Result].
*/
suspend fun generatePassword(request: PasswordGeneratorRequest): Result<String>
}

View file

@ -0,0 +1,20 @@
package com.x8bit.bitwarden.data.generator.datasource.sdk
import com.bitwarden.core.PasswordGeneratorRequest
import com.bitwarden.sdk.ClientGenerators
/**
* Implementation of [GeneratorSdkSource] that delegates password generation.
*
* @property clientGenerator An instance of [ClientGenerators] provided by the Bitwarden SDK.
*/
class GeneratorSdkSourceImpl(
private val clientGenerator: ClientGenerators,
) : GeneratorSdkSource {
override suspend fun generatePassword(
request: PasswordGeneratorRequest,
): Result<String> = runCatching {
clientGenerator.password(request)
}
}

View file

@ -0,0 +1,24 @@
package com.x8bit.bitwarden.data.generator.datasource.sdk.di
import com.bitwarden.sdk.Client
import com.x8bit.bitwarden.data.generator.datasource.sdk.GeneratorSdkSource
import com.x8bit.bitwarden.data.generator.datasource.sdk.GeneratorSdkSourceImpl
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 password generation package.
*/
@Module
@InstallIn(SingletonComponent::class)
object GeneratorSdkModule {
@Provides
@Singleton
fun provideGeneratorSdkSource(
client: Client,
): GeneratorSdkSource = GeneratorSdkSourceImpl(clientGenerator = client.generators())
}

View file

@ -0,0 +1,17 @@
package com.x8bit.bitwarden.data.generator.repository
import com.bitwarden.core.PasswordGeneratorRequest
import com.x8bit.bitwarden.data.generator.repository.model.GeneratedPasswordResult
/**
* Responsible for managing generator data.
*/
interface GeneratorRepository {
/**
* Attempt to generate a password.
*/
suspend fun generatePassword(
passwordGeneratorRequest: PasswordGeneratorRequest,
): GeneratedPasswordResult
}

View file

@ -0,0 +1,25 @@
package com.x8bit.bitwarden.data.generator.repository
import com.bitwarden.core.PasswordGeneratorRequest
import com.x8bit.bitwarden.data.generator.datasource.sdk.GeneratorSdkSource
import com.x8bit.bitwarden.data.generator.repository.model.GeneratedPasswordResult
import javax.inject.Singleton
/**
* Default implementation of [GeneratorRepository].
*/
@Singleton
class GeneratorRepositoryImpl constructor(
private val generatorSdkSource: GeneratorSdkSource,
) : GeneratorRepository {
override suspend fun generatePassword(
passwordGeneratorRequest: PasswordGeneratorRequest,
): GeneratedPasswordResult =
generatorSdkSource
.generatePassword(passwordGeneratorRequest)
.fold(
onSuccess = { GeneratedPasswordResult.Success(it) },
onFailure = { GeneratedPasswordResult.InvalidRequest },
)
}

View file

@ -0,0 +1,24 @@
package com.x8bit.bitwarden.data.generator.repository.di
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
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
/**
* Provides repositories in the generator package.
*/
@Module
@InstallIn(SingletonComponent::class)
object GeneratorRepositoryModule {
@Provides
@Singleton
fun provideGeneratorRepository(
generatorSdkSource: GeneratorSdkSource,
): GeneratorRepository = GeneratorRepositoryImpl(generatorSdkSource)
}

View file

@ -0,0 +1,17 @@
package com.x8bit.bitwarden.data.generator.repository.model
/**
* Represents the outcome of a generator operation.
*/
sealed class GeneratedPasswordResult {
/**
* Operation succeeded with a value.
*/
data class Success(val generatedString: String) : GeneratedPasswordResult()
/**
* There was an error during the operation.
*/
data object InvalidRequest : GeneratedPasswordResult()
}

View file

@ -0,0 +1,45 @@
package com.x8bit.bitwarden.data.generator.datasource.sdk
import com.bitwarden.core.PasswordGeneratorRequest
import com.bitwarden.sdk.ClientGenerators
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Test
class GeneratorSdkSourceTest {
private val clientGenerators = mockk<ClientGenerators>()
private val generatorSdkSource: GeneratorSdkSource = GeneratorSdkSourceImpl(clientGenerators)
@Suppress("MaxLineLength")
@Test
fun `generatePassword should call SDK and return a Result with the generated password`() = runBlocking {
val request = PasswordGeneratorRequest(
lowercase = true,
uppercase = true,
numbers = true,
special = true,
length = 12.toUByte(),
avoidAmbiguous = false,
minLowercase = true,
minUppercase = true,
minNumber = true,
minSpecial = true,
)
val expectedResult = "GeneratedPassword123!"
coEvery {
clientGenerators.password(request)
} returns expectedResult
val result = generatorSdkSource.generatePassword(request)
assertEquals(Result.success(expectedResult), result)
coVerify {
clientGenerators.password(request)
}
}
}

View file

@ -0,0 +1,76 @@
package com.x8bit.bitwarden.data.generator.repository
import com.bitwarden.core.PasswordGeneratorRequest
import com.x8bit.bitwarden.data.generator.datasource.sdk.GeneratorSdkSource
import com.x8bit.bitwarden.data.generator.repository.model.GeneratedPasswordResult
import io.mockk.clearMocks
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class GeneratorRepositoryTest {
private val generatorSdkSource: GeneratorSdkSource = mockk()
private val repository = GeneratorRepositoryImpl(
generatorSdkSource = generatorSdkSource,
)
@BeforeEach
fun setUp() {
clearMocks(generatorSdkSource)
}
@Test
fun `generatePassword should emit Success result with the generated password`() = runTest {
val request = PasswordGeneratorRequest(
lowercase = true,
uppercase = true,
numbers = true,
special = true,
length = 12.toUByte(),
avoidAmbiguous = false,
minLowercase = null,
minUppercase = null,
minNumber = null,
minSpecial = null,
)
val expectedResult = "GeneratedPassword123!"
coEvery {
generatorSdkSource.generatePassword(request)
} returns Result.success(expectedResult)
val result = repository.generatePassword(request)
assertEquals(expectedResult, (result as GeneratedPasswordResult.Success).generatedString)
coVerify { generatorSdkSource.generatePassword(request) }
}
@Test
fun `generatePassword should emit InvalidRequest result when SDK throws exception`() = runTest {
val request = PasswordGeneratorRequest(
lowercase = true,
uppercase = true,
numbers = true,
special = true,
length = 12.toUByte(),
avoidAmbiguous = false,
minLowercase = null,
minUppercase = null,
minNumber = null,
minSpecial = null,
)
val exception = RuntimeException("An error occurred")
coEvery { generatorSdkSource.generatePassword(request) } returns Result.failure(exception)
val result = repository.generatePassword(request)
assertTrue(result is GeneratedPasswordResult.InvalidRequest)
coVerify { generatorSdkSource.generatePassword(request) }
}
}