BIT-394 Setup service layer to accommodate get token error parsing (#61)

This commit is contained in:
Andrew Haisting
2023-09-21 16:03:54 -05:00
committed by GitHub
parent ef8ecbf0e8
commit b6fec01be1
17 changed files with 477 additions and 50 deletions

View File

@@ -0,0 +1,43 @@
package com.x8bit.bitwarden.data.auth.datasource.network.service
import com.x8bit.bitwarden.data.auth.datasource.network.api.AccountsApi
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJson
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
import kotlinx.coroutines.test.runTest
import okhttp3.mockwebserver.MockResponse
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import retrofit2.create
class AccountsServiceTest : BaseServiceTest() {
private val accountsApi: AccountsApi = retrofit.create()
private val service = AccountsServiceImpl(accountsApi)
@Test
fun `preLogin should call API`() = runTest {
val response = MockResponse().setBody(PRE_LOGIN_RESPONSE_JSON)
server.enqueue(response)
assertEquals(Result.success(PRE_LOGIN_RESPONSE), service.preLogin(EMAIL))
}
companion object {
private const val EMAIL = "email"
}
}
private const val PRE_LOGIN_RESPONSE_JSON = """
{
"kdf": 1,
"kdfIterations": 1,
"kdfMemory": 1,
"kdfParallelism": 1
}
"""
private val PRE_LOGIN_RESPONSE = PreLoginResponseJson(
kdf = 1,
kdfIterations = 1u,
kdfMemory = 1,
kdfParallelism = 1,
)

View File

@@ -0,0 +1,63 @@
package com.x8bit.bitwarden.data.auth.datasource.network.service
import com.x8bit.bitwarden.data.auth.datasource.network.api.IdentityApi
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.Json
import okhttp3.mockwebserver.MockResponse
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import retrofit2.create
class IdentityServiceTest : BaseServiceTest() {
private val identityApi: IdentityApi = retrofit.create()
private val identityService = IdentityServiceImpl(
api = identityApi,
json = Json,
baseUrl = server.url("/").toString(),
)
@Test
fun `getToken when request response is Success should return Success`() = runTest {
server.enqueue(MockResponse().setBody(LOGIN_SUCCESS_JSON))
val result = identityService.getToken(email = EMAIL, passwordHash = PASSWORD_HASH)
assertEquals(Result.success(LOGIN_SUCCESS), result)
}
@Test
fun `getToken when request is error should return error`() = runTest {
server.enqueue(MockResponse().setResponseCode(500))
val result = identityService.getToken(email = EMAIL, passwordHash = PASSWORD_HASH)
assertTrue(result.isFailure)
}
@Test
fun `getToken when response is CaptchaRequired should return CaptchaRequired`() = runTest {
server.enqueue(MockResponse().setResponseCode(400).setBody(CAPTCHA_BODY_JSON))
val result = identityService.getToken(email = EMAIL, passwordHash = PASSWORD_HASH)
assertEquals(Result.success(CAPTCHA_BODY), result)
}
companion object {
private const val EMAIL = "email"
private const val PASSWORD_HASH = "passwordHash"
}
}
private const val CAPTCHA_BODY_JSON = """
{
"HCaptcha_SiteKey": "123"
}
"""
private val CAPTCHA_BODY = GetTokenResponseJson.CaptchaRequired("123")
private const val LOGIN_SUCCESS_JSON = """
{
"access_token": "123"
}
"""
private val LOGIN_SUCCESS = GetTokenResponseJson.Success("123")

View File

@@ -0,0 +1,110 @@
package com.x8bit.bitwarden.data.auth.repository
import com.bitwarden.core.Kdf
import com.bitwarden.sdk.Client
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthState
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.LoginResult
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJson
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.platform.datasource.network.interceptor.AuthTokenInterceptor
import io.mockk.clearMocks
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class AuthRepositoryTest {
private val accountsService: AccountsService = mockk()
private val identityService: IdentityService = mockk()
private val authInterceptor = mockk<AuthTokenInterceptor>()
private val mockBitwardenSdk = mockk<Client> {
coEvery {
auth().hashPassword(
email = EMAIL,
password = PASSWORD,
kdfParams = Kdf.Pbkdf2(iterations = PRE_LOGIN_SUCCESS.kdfIterations),
)
} returns PASSWORD_HASH
}
private val repository = AuthRepositoryImpl(
accountsService = accountsService,
identityService = identityService,
bitwardenSdkClient = mockBitwardenSdk,
authTokenInterceptor = authInterceptor,
)
@BeforeEach
fun beforeEach() {
clearMocks(identityService, accountsService, authInterceptor)
}
@Test
fun `login when pre login fails should return Error`() = runTest {
coEvery { accountsService.preLogin(EMAIL) } returns (Result.failure(RuntimeException()))
val result = repository.login(EMAIL, PASSWORD)
assertEquals(LoginResult.Error, result)
assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value)
coVerify { accountsService.preLogin(EMAIL) }
}
@Test
fun `login get token fails should return Error`() = runTest {
coEvery { accountsService.preLogin(EMAIL) } returns Result.success(PRE_LOGIN_SUCCESS)
coEvery { identityService.getToken(EMAIL, PASSWORD_HASH) }
.returns(Result.failure(RuntimeException()))
val result = repository.login(EMAIL, PASSWORD)
assertEquals(LoginResult.Error, result)
assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value)
coVerify { accountsService.preLogin(EMAIL) }
coVerify { identityService.getToken(EMAIL, PASSWORD_HASH) }
}
@Test
fun `login get token succeeds should return Success and update AuthState`() = runTest {
coEvery { accountsService.preLogin(EMAIL) } returns Result.success(PRE_LOGIN_SUCCESS)
coEvery { identityService.getToken(email = EMAIL, passwordHash = PASSWORD_HASH) }
.returns(Result.success(GetTokenResponseJson.Success(accessToken = ACCESS_TOKEN)))
every { authInterceptor.authToken = ACCESS_TOKEN } returns Unit
val result = repository.login(EMAIL, PASSWORD)
assertEquals(LoginResult.Success, result)
assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value)
verify { authInterceptor.authToken = ACCESS_TOKEN }
coVerify { accountsService.preLogin(EMAIL) }
coVerify { identityService.getToken(EMAIL, PASSWORD_HASH) }
}
@Test
fun `login get token returns captcha request should return CaptchaRequired`() = runTest {
coEvery { accountsService.preLogin(EMAIL) } returns Result.success(PRE_LOGIN_SUCCESS)
coEvery { identityService.getToken(email = EMAIL, passwordHash = PASSWORD_HASH) }
.returns(Result.success(GetTokenResponseJson.CaptchaRequired(CAPTCHA_KEY)))
val result = repository.login(EMAIL, PASSWORD)
assertEquals(LoginResult.CaptchaRequired(CAPTCHA_KEY), result)
assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value)
coVerify { accountsService.preLogin(EMAIL) }
coVerify { identityService.getToken(EMAIL, PASSWORD_HASH) }
}
companion object {
private const val EMAIL = "test@test.com"
private const val PASSWORD = "password"
private const val PASSWORD_HASH = "passwordHash"
private const val ACCESS_TOKEN = "accessToken"
private const val CAPTCHA_KEY = "captcha"
private val PRE_LOGIN_SUCCESS = PreLoginResponseJson(
kdf = 1,
kdfIterations = 1u,
kdfMemory = null,
kdfParallelism = null,
)
}
}

View File

@@ -0,0 +1,28 @@
package com.x8bit.bitwarden.data.platform.base
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.x8bit.bitwarden.data.platform.datasource.network.core.ResultCallAdapterFactory
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import retrofit2.Retrofit
/**
* Base class for service tests. Provides common mock web server and retrofit setup.
*/
abstract class BaseServiceTest {
protected val server = MockWebServer().apply { start() }
protected val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(server.url("/").toString())
.addCallAdapterFactory(ResultCallAdapterFactory())
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.build()
@After
fun after() {
server.shutdown()
}
}