Extract Base URL from authentication

This commit is contained in:
Kirill Kamakin
2022-04-04 02:40:32 +05:00
parent 617bcc7eae
commit f44f54522d
47 changed files with 760 additions and 316 deletions

View File

@@ -1,17 +1,15 @@
package gq.kirmanak.mealient.data.auth.impl
import com.google.common.truth.Truth.assertThat
import gq.kirmanak.mealient.data.auth.impl.AuthenticationError.*
import gq.kirmanak.mealient.data.network.NetworkError.*
import gq.kirmanak.mealient.data.network.ServiceFactory
import gq.kirmanak.mealient.di.NetworkModule
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_PASSWORD
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_USERNAME
import gq.kirmanak.mealient.test.toJsonResponseBody
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.every
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
@@ -34,6 +32,7 @@ class AuthDataSourceImplTest {
fun setUp() {
MockKAnnotations.init(this)
subject = AuthDataSourceImpl(authServiceFactory, NetworkModule.createJson())
coEvery { authServiceFactory.provideService() } returns authService
}
@Test
@@ -66,21 +65,21 @@ class AuthDataSourceImplTest {
@Test(expected = NoServerConnection::class)
fun `when authenticate and getToken throws then throws NoServerConnection`() = runTest {
setUpAuthServiceFactory()
coEvery { authService.getToken(any(), any()) } throws IOException("Server not found")
callAuthenticate()
}
@Test(expected = MalformedUrl::class)
fun `when authenticate and provideService throws then MalformedUrl`() = runTest {
coEvery { authServiceFactory.provideService() } throws RuntimeException()
callAuthenticate()
}
private suspend fun authenticate(response: Response<GetTokenResponse>): String {
setUpAuthServiceFactory()
coEvery { authService.getToken(eq(TEST_USERNAME), eq(TEST_PASSWORD)) } returns response
return callAuthenticate()
}
private suspend fun callAuthenticate() =
subject.authenticate(TEST_USERNAME, TEST_PASSWORD, TEST_BASE_URL)
private suspend fun callAuthenticate() = subject.authenticate(TEST_USERNAME, TEST_PASSWORD)
private fun setUpAuthServiceFactory() {
every { authServiceFactory.provideService(eq(TEST_BASE_URL)) } returns authService
}
}

View File

@@ -3,10 +3,8 @@ package gq.kirmanak.mealient.data.auth.impl
import com.google.common.truth.Truth.assertThat
import gq.kirmanak.mealient.data.auth.AuthDataSource
import gq.kirmanak.mealient.data.auth.AuthStorage
import gq.kirmanak.mealient.data.auth.impl.AuthenticationError.MalformedUrl
import gq.kirmanak.mealient.data.auth.impl.AuthenticationError.Unauthorized
import gq.kirmanak.mealient.data.network.NetworkError.Unauthorized
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_PASSWORD
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_USERNAME
@@ -54,9 +52,9 @@ class AuthRepoImplTest : RobolectricTest() {
@Test(expected = Unauthorized::class)
fun `when authentication fails then authenticate throws`() = runTest {
coEvery {
dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD), eq(TEST_BASE_URL))
dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD))
} throws Unauthorized(RuntimeException())
subject.authenticate(TEST_USERNAME, TEST_PASSWORD, TEST_BASE_URL)
subject.authenticate(TEST_USERNAME, TEST_PASSWORD)
}
@Test
@@ -65,44 +63,11 @@ class AuthRepoImplTest : RobolectricTest() {
assertThat(subject.getAuthHeader()).isEqualTo(TEST_AUTH_HEADER)
}
@Test
fun `when authenticated then getBaseUrl returns url`() = runTest {
coEvery { storage.getBaseUrl() } returns TEST_BASE_URL
assertThat(subject.getBaseUrl()).isEqualTo(TEST_BASE_URL)
}
@Test(expected = MalformedUrl::class)
fun `when baseUrl has ftp scheme then throws`() {
subject.parseBaseUrl("ftp://test")
}
@Test
fun `when baseUrl scheme has one slash then corrects`() {
assertThat(subject.parseBaseUrl("https:/test")).isEqualTo("https://test/")
}
@Test
fun `when baseUrl is single word then appends scheme and slash`() {
assertThat(subject.parseBaseUrl("test")).isEqualTo("https://test/")
}
@Test
fun `when baseUrl is host appends scheme and slash`() {
assertThat(subject.parseBaseUrl("google.com")).isEqualTo("https://google.com/")
}
@Test
fun `when baseUrl is correct then doesn't change`() {
assertThat(subject.parseBaseUrl("https://google.com/")).isEqualTo("https://google.com/")
}
@Test
fun `when authenticated successfully then stores token and url`() = runTest {
coEvery {
dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD), eq(TEST_BASE_URL))
} returns TEST_TOKEN
subject.authenticate(TEST_USERNAME, TEST_PASSWORD, TEST_BASE_URL)
coVerify { storage.storeAuthData(TEST_AUTH_HEADER, TEST_BASE_URL) }
coEvery { dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD)) } returns TEST_TOKEN
subject.authenticate(TEST_USERNAME, TEST_PASSWORD)
coVerify { storage.storeAuthData(TEST_AUTH_HEADER) }
}
@Test

View File

@@ -3,7 +3,6 @@ package gq.kirmanak.mealient.data.auth.impl
import com.google.common.truth.Truth.assertThat
import dagger.hilt.android.testing.HiltAndroidTest
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_URL
import gq.kirmanak.mealient.test.HiltRobolectricTest
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
@@ -20,18 +19,12 @@ class AuthStorageImplTest : HiltRobolectricTest() {
@Test
fun `when storing auth data then doesn't throw`() = runTest {
subject.storeAuthData(TEST_AUTH_HEADER, TEST_URL)
}
@Test
fun `when reading url after storing data then returns url`() = runTest {
subject.storeAuthData(TEST_AUTH_HEADER, TEST_URL)
assertThat(subject.getBaseUrl()).isEqualTo(TEST_URL)
subject.storeAuthData(TEST_AUTH_HEADER)
}
@Test
fun `when reading token after storing data then returns token`() = runTest {
subject.storeAuthData(TEST_AUTH_HEADER, TEST_URL)
subject.storeAuthData(TEST_AUTH_HEADER)
assertThat(subject.getAuthHeader()).isEqualTo(TEST_AUTH_HEADER)
}
@@ -40,11 +33,6 @@ class AuthStorageImplTest : HiltRobolectricTest() {
assertThat(subject.getAuthHeader()).isNull()
}
@Test
fun `when reading url without storing data then returns null`() = runTest {
assertThat(subject.getBaseUrl()).isNull()
}
@Test
fun `when didn't store auth data then first token is null`() = runTest {
assertThat(subject.authHeaderObservable().first()).isNull()
@@ -52,28 +40,21 @@ class AuthStorageImplTest : HiltRobolectricTest() {
@Test
fun `when stored auth data then first token is correct`() = runTest {
subject.storeAuthData(TEST_AUTH_HEADER, TEST_URL)
subject.storeAuthData(TEST_AUTH_HEADER)
assertThat(subject.authHeaderObservable().first()).isEqualTo(TEST_AUTH_HEADER)
}
@Test
fun `when clearAuthData then first token is null`() = runTest {
subject.storeAuthData(TEST_AUTH_HEADER, TEST_URL)
subject.storeAuthData(TEST_AUTH_HEADER)
subject.clearAuthData()
assertThat(subject.authHeaderObservable().first()).isNull()
}
@Test
fun `when clearAuthData then getToken returns null`() = runTest {
subject.storeAuthData(TEST_AUTH_HEADER, TEST_URL)
subject.storeAuthData(TEST_AUTH_HEADER)
subject.clearAuthData()
assertThat(subject.getAuthHeader()).isNull()
}
@Test
fun `when clearAuthData then getBaseUrl returns null`() = runTest {
subject.storeAuthData(TEST_AUTH_HEADER, TEST_URL)
subject.clearAuthData()
assertThat(subject.getBaseUrl()).isNull()
}
}

View File

@@ -0,0 +1,65 @@
package gq.kirmanak.mealient.data.baseurl
import androidx.datastore.preferences.core.stringPreferencesKey
import com.google.common.truth.Truth.assertThat
import gq.kirmanak.mealient.data.storage.PreferencesStorage
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
class BaseURLStorageImplTest {
@MockK(relaxUnitFun = true)
lateinit var preferencesStorage: PreferencesStorage
lateinit var subject: BaseURLStorage
private val baseUrlKey = stringPreferencesKey("baseUrlKey")
@Before
fun setUp() {
MockKAnnotations.init(this)
subject = BaseURLStorageImpl(preferencesStorage)
every { preferencesStorage.baseUrlKey } returns baseUrlKey
}
@Test
fun `when getBaseURL and preferences storage empty then null`() = runTest {
coEvery { preferencesStorage.getValue(eq(baseUrlKey)) } returns null
assertThat(subject.getBaseURL()).isNull()
}
@Test(expected = IllegalStateException::class)
fun `when requireBaseURL and preferences storage empty then IllegalStateException`() = runTest {
coEvery { preferencesStorage.getValue(eq(baseUrlKey)) } returns null
subject.requireBaseURL()
}
@Test
fun `when getBaseUrl and preferences storage has value then value`() = runTest {
coEvery { preferencesStorage.getValue(eq(baseUrlKey)) } returns "baseUrl"
assertThat(subject.getBaseURL()).isEqualTo("baseUrl")
}
@Test
fun `when requireBaseURL and preferences storage has value then value`() = runTest {
coEvery { preferencesStorage.getValue(eq(baseUrlKey)) } returns "baseUrl"
assertThat(subject.requireBaseURL()).isEqualTo("baseUrl")
}
@Test
fun `when storeBaseURL then calls preferences storage`() = runTest {
subject.storeBaseURL("baseUrl")
coVerify {
preferencesStorage.baseUrlKey
preferencesStorage.storeValues(eq(Pair(baseUrlKey, "baseUrl")))
}
}
}

View File

@@ -0,0 +1,71 @@
package gq.kirmanak.mealient.data.baseurl
import com.google.common.truth.Truth.assertThat
import gq.kirmanak.mealient.data.network.NetworkError
import gq.kirmanak.mealient.data.network.ServiceFactory
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL
import gq.kirmanak.mealient.test.toJsonResponseBody
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.SerializationException
import okio.IOException
import org.junit.Before
import org.junit.Test
import retrofit2.HttpException
import retrofit2.Response
@OptIn(ExperimentalCoroutinesApi::class)
class VersionDataSourceImplTest {
@MockK
lateinit var versionService: VersionService
@MockK
lateinit var versionServiceFactory: ServiceFactory<VersionService>
lateinit var subject: VersionDataSource
@Before
fun setUp() {
MockKAnnotations.init(this)
subject = VersionDataSourceImpl(versionServiceFactory)
coEvery { versionServiceFactory.provideService(eq(TEST_BASE_URL)) } returns versionService
}
@Test(expected = NetworkError.MalformedUrl::class)
fun `when getVersionInfo and provideService throws then MalformedUrl`() = runTest {
coEvery { versionServiceFactory.provideService(eq(TEST_BASE_URL)) } throws RuntimeException()
subject.getVersionInfo(TEST_BASE_URL)
}
@Test(expected = NetworkError.NotMealie::class)
fun `when getVersionInfo and getVersion throws HttpException then NotMealie`() = runTest {
val error = HttpException(Response.error<VersionResponse>(404, "".toJsonResponseBody()))
coEvery { versionService.getVersion() } throws error
subject.getVersionInfo(TEST_BASE_URL)
}
@Test(expected = NetworkError.NotMealie::class)
fun `when getVersionInfo and getVersion throws SerializationException then NotMealie`() =
runTest {
coEvery { versionService.getVersion() } throws SerializationException()
subject.getVersionInfo(TEST_BASE_URL)
}
@Test(expected = NetworkError.NoServerConnection::class)
fun `when getVersionInfo and getVersion throws IOException then NoServerConnection`() =
runTest {
coEvery { versionService.getVersion() } throws IOException()
subject.getVersionInfo(TEST_BASE_URL)
}
@Test
fun `when getVersionInfo and getVersion returns result then result`() = runTest {
coEvery { versionService.getVersion() } returns VersionResponse(true, "v0.5.6", true)
assertThat(subject.getVersionInfo(TEST_BASE_URL)).isEqualTo(
VersionInfo(true, "v0.5.6", true)
)
}
}

View File

@@ -0,0 +1,68 @@
package gq.kirmanak.mealient.data.network
import com.google.common.truth.Truth.assertThat
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
import gq.kirmanak.mealient.data.baseurl.VersionService
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL
import io.mockk.*
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import retrofit2.Retrofit
@OptIn(ExperimentalCoroutinesApi::class)
class RetrofitServiceFactoryTest {
@MockK
lateinit var retrofitBuilder: RetrofitBuilder
@MockK
lateinit var baseURLStorage: BaseURLStorage
@MockK
lateinit var retrofit: Retrofit
@MockK
lateinit var versionService: VersionService
lateinit var subject: ServiceFactory<VersionService>
@Before
fun setUp() {
MockKAnnotations.init(this)
subject = retrofitBuilder.createServiceFactory(baseURLStorage)
coEvery { retrofitBuilder.buildRetrofit(any()) } returns retrofit
every { retrofit.create(eq(VersionService::class.java)) } returns versionService
coEvery { baseURLStorage.requireBaseURL() } returns TEST_BASE_URL
}
@Test
fun `when provideService and url is null then url storage requested`() = runTest {
subject.provideService()
coVerify { baseURLStorage.requireBaseURL() }
}
@Test
fun `when provideService and url is null then service still provided`() = runTest {
assertThat(subject.provideService()).isEqualTo(versionService)
}
@Test
fun `when provideService called twice then builder called once`() = runTest {
subject.provideService()
subject.provideService()
coVerifyAll { retrofitBuilder.buildRetrofit(eq(TEST_BASE_URL)) }
}
@Test
fun `when provideService called secondly with new url then builder called twice`() = runTest {
subject.provideService()
subject.provideService("new url")
coVerifyAll {
retrofitBuilder.buildRetrofit(eq(TEST_BASE_URL))
retrofitBuilder.buildRetrofit(eq("new url"))
}
}
}

View File

@@ -1,7 +1,7 @@
package gq.kirmanak.mealient.data.recipes.impl
import com.google.common.truth.Truth.assertThat
import gq.kirmanak.mealient.data.auth.AuthRepo
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
import gq.kirmanak.mealient.ui.ImageLoader
import io.mockk.MockKAnnotations
import io.mockk.coEvery
@@ -16,7 +16,7 @@ class RecipeImageLoaderImplTest {
lateinit var subject: RecipeImageLoaderImpl
@MockK
lateinit var authRepo: AuthRepo
lateinit var baseURLStorage: BaseURLStorage
@MockK
lateinit var imageLoader: ImageLoader
@@ -24,8 +24,8 @@ class RecipeImageLoaderImplTest {
@Before
fun setUp() {
MockKAnnotations.init(this)
subject = RecipeImageLoaderImpl(imageLoader, authRepo)
coEvery { authRepo.getBaseUrl() } returns "https://google.com/"
subject = RecipeImageLoaderImpl(imageLoader, baseURLStorage)
prepareBaseURL("https://google.com/")
}
@Test
@@ -42,21 +42,21 @@ class RecipeImageLoaderImplTest {
@Test
fun `when url is null then generated is null`() = runTest {
coEvery { authRepo.getBaseUrl() } returns null
prepareBaseURL(null)
val actual = subject.generateImageUrl("cake")
assertThat(actual).isNull()
}
@Test
fun `when url is blank then generated is null`() = runTest {
coEvery { authRepo.getBaseUrl() } returns " "
prepareBaseURL(" ")
val actual = subject.generateImageUrl("cake")
assertThat(actual).isNull()
}
@Test
fun `when url is empty then generated is null`() = runTest {
coEvery { authRepo.getBaseUrl() } returns ""
prepareBaseURL("")
val actual = subject.generateImageUrl("cake")
assertThat(actual).isNull()
}
@@ -78,4 +78,8 @@ class RecipeImageLoaderImplTest {
val actual = subject.generateImageUrl(null)
assertThat(actual).isNull()
}
private fun prepareBaseURL(baseURL: String?) {
coEvery { baseURLStorage.getBaseURL() } returns baseURL
}
}

View File

@@ -3,7 +3,7 @@ package gq.kirmanak.mealient.data.recipes.impl
import androidx.paging.*
import androidx.paging.LoadType.*
import com.google.common.truth.Truth.assertThat
import gq.kirmanak.mealient.data.auth.impl.AuthenticationError.Unauthorized
import gq.kirmanak.mealient.data.network.NetworkError.Unauthorized
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeSummaryEntity
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource

View File

@@ -0,0 +1,75 @@
package gq.kirmanak.mealient.ui.baseurl
import com.google.common.truth.Truth.assertThat
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
import gq.kirmanak.mealient.data.baseurl.VersionDataSource
import gq.kirmanak.mealient.data.baseurl.VersionInfo
import gq.kirmanak.mealient.data.network.NetworkError
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL
import gq.kirmanak.mealient.test.RobolectricTest
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
class BaseURLViewModelTest : RobolectricTest() {
@MockK(relaxUnitFun = true)
lateinit var baseURLStorage: BaseURLStorage
@MockK
lateinit var versionDataSource: VersionDataSource
lateinit var subject: BaseURLViewModel
@Before
fun setUp() {
MockKAnnotations.init(this)
subject = BaseURLViewModel(baseURLStorage, versionDataSource)
}
@Test
fun `when initialized then error is null`() {
assertThat(subject.currentScreenState.error).isNull()
}
@Test
fun `when initialized then navigateNext is false`() {
assertThat(subject.currentScreenState.navigateNext).isFalse()
}
@Test
fun `when saveBaseUrl and getVersionInfo throws then state is correct`() = runTest {
val error = NetworkError.Unauthorized(RuntimeException())
coEvery { versionDataSource.getVersionInfo(eq(TEST_BASE_URL)) } throws error
subject.saveBaseUrl(TEST_BASE_URL)
advanceUntilIdle()
assertThat(subject.currentScreenState).isEqualTo(BaseURLScreenState(error, false))
}
@Test
fun `when saveBaseUrl and getVersionInfo returns result then state is correct`() = runTest {
coEvery {
versionDataSource.getVersionInfo(eq(TEST_BASE_URL))
} returns VersionInfo(true, "0.5.6", true)
subject.saveBaseUrl(TEST_BASE_URL)
advanceUntilIdle()
assertThat(subject.currentScreenState).isEqualTo(BaseURLScreenState(null, true))
}
@Test
fun `when saveBaseUrl and getVersionInfo returns result then saves to storage`() = runTest {
coEvery {
versionDataSource.getVersionInfo(eq(TEST_BASE_URL))
} returns VersionInfo(true, "0.5.6", true)
subject.saveBaseUrl(TEST_BASE_URL)
advanceUntilIdle()
coVerify { baseURLStorage.storeBaseURL(eq(TEST_BASE_URL)) }
}
}

View File

@@ -1,7 +1,6 @@
package gq.kirmanak.mealient.ui.disclaimer
import com.google.common.truth.Truth.assertThat
import dagger.hilt.android.testing.HiltAndroidTest
import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage
import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.MockK
@@ -14,7 +13,6 @@ import org.junit.Test
import java.util.concurrent.TimeUnit
@OptIn(ExperimentalCoroutinesApi::class)
@HiltAndroidTest
class DisclaimerViewModelTest {
@MockK(relaxUnitFun = true)
lateinit var storage: DisclaimerStorage