Merge pull request #116 from kirmanak/base-url-interceptor
Simplify base server URL handling
This commit is contained in:
@@ -19,18 +19,16 @@ class AuthDataSourceImpl @Inject constructor(
|
|||||||
|
|
||||||
private suspend fun getVersion(): ServerVersion = serverInfoRepo.getVersion()
|
private suspend fun getVersion(): ServerVersion = serverInfoRepo.getVersion()
|
||||||
|
|
||||||
private suspend fun getUrl(): String = serverInfoRepo.requireUrl()
|
|
||||||
|
|
||||||
override suspend fun authenticate(
|
override suspend fun authenticate(
|
||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
): String = when (getVersion()) {
|
): String = when (getVersion()) {
|
||||||
ServerVersion.V0 -> v0Source.authenticate(getUrl(), username, password)
|
ServerVersion.V0 -> v0Source.authenticate(username, password)
|
||||||
ServerVersion.V1 -> v1Source.authenticate(getUrl(), username, password)
|
ServerVersion.V1 -> v1Source.authenticate(username, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun createApiToken(name: String): String = when (getVersion()) {
|
override suspend fun createApiToken(name: String): String = when (getVersion()) {
|
||||||
ServerVersion.V0 -> v0Source.createApiToken(getUrl(), CreateApiTokenRequestV0(name))
|
ServerVersion.V0 -> v0Source.createApiToken(CreateApiTokenRequestV0(name))
|
||||||
ServerVersion.V1 -> v1Source.createApiToken(getUrl(), CreateApiTokenRequestV1(name)).token
|
ServerVersion.V1 -> v1Source.createApiToken(CreateApiTokenRequestV1(name)).token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,10 +4,9 @@ interface ServerInfoRepo {
|
|||||||
|
|
||||||
suspend fun getUrl(): String?
|
suspend fun getUrl(): String?
|
||||||
|
|
||||||
suspend fun requireUrl(): String
|
|
||||||
|
|
||||||
suspend fun getVersion(): ServerVersion
|
suspend fun getVersion(): ServerVersion
|
||||||
|
|
||||||
suspend fun storeBaseURL(baseURL: String, version: String)
|
suspend fun tryBaseURL(baseURL: String): Result<Unit>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package gq.kirmanak.mealient.data.baseurl
|
package gq.kirmanak.mealient.data.baseurl
|
||||||
|
|
||||||
import gq.kirmanak.mealient.datasource.NetworkError
|
import gq.kirmanak.mealient.datasource.NetworkError
|
||||||
|
import gq.kirmanak.mealient.datasource.ServerUrlProvider
|
||||||
|
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -10,7 +12,7 @@ class ServerInfoRepoImpl @Inject constructor(
|
|||||||
private val serverInfoStorage: ServerInfoStorage,
|
private val serverInfoStorage: ServerInfoStorage,
|
||||||
private val versionDataSource: VersionDataSource,
|
private val versionDataSource: VersionDataSource,
|
||||||
private val logger: Logger,
|
private val logger: Logger,
|
||||||
) : ServerInfoRepo {
|
) : ServerInfoRepo, ServerUrlProvider {
|
||||||
|
|
||||||
override suspend fun getUrl(): String? {
|
override suspend fun getUrl(): String? {
|
||||||
val result = serverInfoStorage.getBaseURL()
|
val result = serverInfoStorage.getBaseURL()
|
||||||
@@ -18,17 +20,11 @@ class ServerInfoRepoImpl @Inject constructor(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun requireUrl(): String {
|
|
||||||
val result = checkNotNull(getUrl()) { "Server URL was null when it was required" }
|
|
||||||
logger.v { "requireUrl() returned: $result" }
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getVersion(): ServerVersion {
|
override suspend fun getVersion(): ServerVersion {
|
||||||
var version = serverInfoStorage.getServerVersion()
|
var version = serverInfoStorage.getServerVersion()
|
||||||
val serverVersion = if (version == null) {
|
val serverVersion = if (version == null) {
|
||||||
logger.d { "getVersion: version is null, requesting" }
|
logger.d { "getVersion: version is null, requesting" }
|
||||||
version = versionDataSource.getVersionInfo(requireUrl()).version
|
version = versionDataSource.getVersionInfo().version
|
||||||
val result = determineServerVersion(version)
|
val result = determineServerVersion(version)
|
||||||
serverInfoStorage.storeServerVersion(version)
|
serverInfoStorage.storeServerVersion(version)
|
||||||
result
|
result
|
||||||
@@ -45,8 +41,16 @@ class ServerInfoRepoImpl @Inject constructor(
|
|||||||
else -> throw NetworkError.NotMealie(IllegalStateException("Server version is unknown: $version"))
|
else -> throw NetworkError.NotMealie(IllegalStateException("Server version is unknown: $version"))
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun storeBaseURL(baseURL: String, version: String) {
|
override suspend fun tryBaseURL(baseURL: String): Result<Unit> {
|
||||||
logger.v { "storeBaseURL() called with: baseURL = $baseURL, version = $version" }
|
val oldVersion = serverInfoStorage.getServerVersion()
|
||||||
serverInfoStorage.storeBaseURL(baseURL, version)
|
val oldBaseUrl = serverInfoStorage.getBaseURL()
|
||||||
|
|
||||||
|
return runCatchingExceptCancel {
|
||||||
|
serverInfoStorage.storeBaseURL(baseURL)
|
||||||
|
val version = versionDataSource.getVersionInfo().version
|
||||||
|
serverInfoStorage.storeServerVersion(version)
|
||||||
|
}.onFailure {
|
||||||
|
serverInfoStorage.storeBaseURL(oldBaseUrl, oldVersion)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,9 +4,12 @@ interface ServerInfoStorage {
|
|||||||
|
|
||||||
suspend fun getBaseURL(): String?
|
suspend fun getBaseURL(): String?
|
||||||
|
|
||||||
suspend fun storeBaseURL(baseURL: String, version: String)
|
suspend fun storeBaseURL(baseURL: String)
|
||||||
|
|
||||||
|
suspend fun storeBaseURL(baseURL: String?, version: String?)
|
||||||
|
|
||||||
suspend fun storeServerVersion(version: String)
|
suspend fun storeServerVersion(version: String)
|
||||||
|
|
||||||
suspend fun getServerVersion(): String?
|
suspend fun getServerVersion(): String?
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@ package gq.kirmanak.mealient.data.baseurl
|
|||||||
|
|
||||||
interface VersionDataSource {
|
interface VersionDataSource {
|
||||||
|
|
||||||
suspend fun getVersionInfo(baseUrl: String): VersionInfo
|
suspend fun getVersionInfo(): VersionInfo
|
||||||
}
|
}
|
||||||
@@ -16,13 +16,13 @@ class VersionDataSourceImpl @Inject constructor(
|
|||||||
private val v1Source: MealieDataSourceV1,
|
private val v1Source: MealieDataSourceV1,
|
||||||
) : VersionDataSource {
|
) : VersionDataSource {
|
||||||
|
|
||||||
override suspend fun getVersionInfo(baseUrl: String): VersionInfo {
|
override suspend fun getVersionInfo(): VersionInfo {
|
||||||
val responses = coroutineScope {
|
val responses = coroutineScope {
|
||||||
val v0Deferred = async {
|
val v0Deferred = async {
|
||||||
runCatchingExceptCancel { v0Source.getVersionInfo(baseUrl).toVersionInfo() }
|
runCatchingExceptCancel { v0Source.getVersionInfo().toVersionInfo() }
|
||||||
}
|
}
|
||||||
val v1Deferred = async {
|
val v1Deferred = async {
|
||||||
runCatchingExceptCancel { v1Source.getVersionInfo(baseUrl).toVersionInfo() }
|
runCatchingExceptCancel { v1Source.getVersionInfo().toVersionInfo() }
|
||||||
}
|
}
|
||||||
listOf(v0Deferred, v1Deferred).awaitAll()
|
listOf(v0Deferred, v1Deferred).awaitAll()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,30 @@ class ServerInfoStorageImpl @Inject constructor(
|
|||||||
|
|
||||||
override suspend fun getBaseURL(): String? = getValue(baseUrlKey)
|
override suspend fun getBaseURL(): String? = getValue(baseUrlKey)
|
||||||
|
|
||||||
override suspend fun storeBaseURL(baseURL: String, version: String) {
|
override suspend fun storeBaseURL(baseURL: String) {
|
||||||
|
preferencesStorage.storeValues(Pair(baseUrlKey, baseURL))
|
||||||
|
preferencesStorage.removeValues(serverVersionKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun storeBaseURL(baseURL: String?, version: String?) {
|
||||||
|
when {
|
||||||
|
baseURL == null -> {
|
||||||
|
preferencesStorage.removeValues(baseUrlKey, serverVersionKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
version != null -> {
|
||||||
preferencesStorage.storeValues(
|
preferencesStorage.storeValues(
|
||||||
Pair(baseUrlKey, baseURL),
|
Pair(baseUrlKey, baseURL), Pair(serverVersionKey, version)
|
||||||
Pair(serverVersionKey, version),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
preferencesStorage.removeValues(serverVersionKey)
|
||||||
|
preferencesStorage.storeValues(Pair(baseUrlKey, baseURL))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getServerVersion(): String? = getValue(serverVersionKey)
|
override suspend fun getServerVersion(): String? = getValue(serverVersionKey)
|
||||||
|
|
||||||
override suspend fun storeServerVersion(version: String) {
|
override suspend fun storeServerVersion(version: String) {
|
||||||
@@ -33,4 +50,5 @@ class ServerInfoStorageImpl @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun <T> getValue(key: Preferences.Key<T>): T? = preferencesStorage.getValue(key)
|
private suspend fun <T> getValue(key: Preferences.Key<T>): T? = preferencesStorage.getValue(key)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -29,13 +29,11 @@ class MealieDataSourceWrapper @Inject constructor(
|
|||||||
|
|
||||||
private suspend fun getVersion(): ServerVersion = serverInfoRepo.getVersion()
|
private suspend fun getVersion(): ServerVersion = serverInfoRepo.getVersion()
|
||||||
|
|
||||||
private suspend fun getUrl(): String = serverInfoRepo.requireUrl()
|
|
||||||
|
|
||||||
override suspend fun addRecipe(recipe: AddRecipeInfo): String = when (getVersion()) {
|
override suspend fun addRecipe(recipe: AddRecipeInfo): String = when (getVersion()) {
|
||||||
ServerVersion.V0 -> v0Source.addRecipe(getUrl(), recipe.toV0Request())
|
ServerVersion.V0 -> v0Source.addRecipe(recipe.toV0Request())
|
||||||
ServerVersion.V1 -> {
|
ServerVersion.V1 -> {
|
||||||
val slug = v1Source.createRecipe(getUrl(), recipe.toV1CreateRequest())
|
val slug = v1Source.createRecipe(recipe.toV1CreateRequest())
|
||||||
v1Source.updateRecipe(getUrl(), slug, recipe.toV1UpdateRequest())
|
v1Source.updateRecipe(slug, recipe.toV1UpdateRequest())
|
||||||
slug
|
slug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,25 +43,25 @@ class MealieDataSourceWrapper @Inject constructor(
|
|||||||
limit: Int,
|
limit: Int,
|
||||||
): List<RecipeSummaryInfo> = when (getVersion()) {
|
): List<RecipeSummaryInfo> = when (getVersion()) {
|
||||||
ServerVersion.V0 -> {
|
ServerVersion.V0 -> {
|
||||||
v0Source.requestRecipes(getUrl(), start, limit).map { it.toRecipeSummaryInfo() }
|
v0Source.requestRecipes(start, limit).map { it.toRecipeSummaryInfo() }
|
||||||
}
|
}
|
||||||
ServerVersion.V1 -> {
|
ServerVersion.V1 -> {
|
||||||
// Imagine start is 30 and limit is 15. It means that we already have page 1 and 2, now we need page 3
|
// Imagine start is 30 and limit is 15. It means that we already have page 1 and 2, now we need page 3
|
||||||
val page = start / limit + 1
|
val page = start / limit + 1
|
||||||
v1Source.requestRecipes(getUrl(), page, limit).map { it.toRecipeSummaryInfo() }
|
v1Source.requestRecipes(page, limit).map { it.toRecipeSummaryInfo() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun requestRecipeInfo(slug: String): FullRecipeInfo = when (getVersion()) {
|
override suspend fun requestRecipeInfo(slug: String): FullRecipeInfo = when (getVersion()) {
|
||||||
ServerVersion.V0 -> v0Source.requestRecipeInfo(getUrl(), slug).toFullRecipeInfo()
|
ServerVersion.V0 -> v0Source.requestRecipeInfo(slug).toFullRecipeInfo()
|
||||||
ServerVersion.V1 -> v1Source.requestRecipeInfo(getUrl(), slug).toFullRecipeInfo()
|
ServerVersion.V1 -> v1Source.requestRecipeInfo(slug).toFullRecipeInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun parseRecipeFromURL(
|
override suspend fun parseRecipeFromURL(
|
||||||
parseRecipeURLInfo: ParseRecipeURLInfo,
|
parseRecipeURLInfo: ParseRecipeURLInfo,
|
||||||
): String = when (getVersion()) {
|
): String = when (getVersion()) {
|
||||||
ServerVersion.V0 -> v0Source.parseRecipeFromURL(getUrl(), parseRecipeURLInfo.toV0Request())
|
ServerVersion.V0 -> v0Source.parseRecipeFromURL(parseRecipeURLInfo.toV0Request())
|
||||||
ServerVersion.V1 -> v1Source.parseRecipeFromURL(getUrl(), parseRecipeURLInfo.toV1Request())
|
ServerVersion.V1 -> v1Source.parseRecipeFromURL(parseRecipeURLInfo.toV1Request())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import dagger.hilt.InstallIn
|
|||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import gq.kirmanak.mealient.data.baseurl.*
|
import gq.kirmanak.mealient.data.baseurl.*
|
||||||
import gq.kirmanak.mealient.data.baseurl.impl.ServerInfoStorageImpl
|
import gq.kirmanak.mealient.data.baseurl.impl.ServerInfoStorageImpl
|
||||||
|
import gq.kirmanak.mealient.datasource.ServerUrlProvider
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@@ -23,4 +24,8 @@ interface BaseURLModule {
|
|||||||
@Binds
|
@Binds
|
||||||
@Singleton
|
@Singleton
|
||||||
fun bindServerInfoRepo(serverInfoRepoImpl: ServerInfoRepoImpl): ServerInfoRepo
|
fun bindServerInfoRepo(serverInfoRepoImpl: ServerInfoRepoImpl): ServerInfoRepo
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
fun bindServerUrlProvider(serverInfoRepoImpl: ServerInfoRepoImpl): ServerUrlProvider
|
||||||
}
|
}
|
||||||
@@ -7,9 +7,7 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||||
import gq.kirmanak.mealient.data.baseurl.VersionDataSource
|
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||||
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import gq.kirmanak.mealient.ui.OperationUiState
|
import gq.kirmanak.mealient.ui.OperationUiState
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -20,7 +18,6 @@ class BaseURLViewModel @Inject constructor(
|
|||||||
private val serverInfoRepo: ServerInfoRepo,
|
private val serverInfoRepo: ServerInfoRepo,
|
||||||
private val authRepo: AuthRepo,
|
private val authRepo: AuthRepo,
|
||||||
private val recipeRepo: RecipeRepo,
|
private val recipeRepo: RecipeRepo,
|
||||||
private val versionDataSource: VersionDataSource,
|
|
||||||
private val logger: Logger,
|
private val logger: Logger,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
@@ -42,10 +39,8 @@ class BaseURLViewModel @Inject constructor(
|
|||||||
_uiState.value = OperationUiState.fromResult(Result.success(Unit))
|
_uiState.value = OperationUiState.fromResult(Result.success(Unit))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val result = runCatchingExceptCancel {
|
val result = serverInfoRepo.tryBaseURL(baseURL)
|
||||||
// If it returns proper version info then it must be a Mealie
|
if (result.isSuccess) {
|
||||||
val version = versionDataSource.getVersionInfo(baseURL).version
|
|
||||||
serverInfoRepo.storeBaseURL(baseURL, version)
|
|
||||||
authRepo.logout()
|
authRepo.logout()
|
||||||
recipeRepo.clearLocalData()
|
recipeRepo.clearLocalData()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
|||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_API_AUTH_HEADER
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_API_AUTH_HEADER
|
||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_API_TOKEN
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_API_TOKEN
|
||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER
|
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_PASSWORD
|
||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION_V0
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION_V0
|
||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN
|
||||||
@@ -54,7 +53,6 @@ class AuthRepoImplTest : BaseUnitTest() {
|
|||||||
fun `when authenticate successfully then saves to storage`() = runTest {
|
fun `when authenticate successfully then saves to storage`() = runTest {
|
||||||
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0
|
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0
|
||||||
coEvery { dataSource.authenticate(any(), any()) } returns TEST_TOKEN
|
coEvery { dataSource.authenticate(any(), any()) } returns TEST_TOKEN
|
||||||
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
|
|
||||||
coEvery { dataSource.createApiToken(any()) } returns TEST_API_TOKEN
|
coEvery { dataSource.createApiToken(any()) } returns TEST_API_TOKEN
|
||||||
subject.authenticate(TEST_USERNAME, TEST_PASSWORD)
|
subject.authenticate(TEST_USERNAME, TEST_PASSWORD)
|
||||||
coVerify {
|
coVerify {
|
||||||
@@ -69,7 +67,6 @@ class AuthRepoImplTest : BaseUnitTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `when authenticate fails then does not change storage`() = runTest {
|
fun `when authenticate fails then does not change storage`() = runTest {
|
||||||
coEvery { dataSource.authenticate(any(), any()) } throws RuntimeException()
|
coEvery { dataSource.authenticate(any(), any()) } throws RuntimeException()
|
||||||
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
|
|
||||||
runCatchingExceptCancel { subject.authenticate("invalid", "") }
|
runCatchingExceptCancel { subject.authenticate("invalid", "") }
|
||||||
confirmVerified(storage)
|
confirmVerified(storage)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class ServerInfoRepoTest : BaseUnitTest() {
|
class ServerInfoRepoTest : BaseUnitTest() {
|
||||||
@@ -44,12 +45,6 @@ class ServerInfoRepoTest : BaseUnitTest() {
|
|||||||
assertThat(subject.getUrl()).isEqualTo(expected)
|
assertThat(subject.getUrl()).isEqualTo(expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalStateException::class)
|
|
||||||
fun `when storage returns null url expect requireUrl to throw`() = runTest {
|
|
||||||
coEvery { storage.getBaseURL() } returns null
|
|
||||||
subject.requireUrl()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when getUrl expect storage is accessed`() = runTest {
|
fun `when getUrl expect storage is accessed`() = runTest {
|
||||||
coEvery { storage.getBaseURL() } returns null
|
coEvery { storage.getBaseURL() } returns null
|
||||||
@@ -58,32 +53,45 @@ class ServerInfoRepoTest : BaseUnitTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when requireUrl expect storage is accessed`() = runTest {
|
fun `when tryBaseURL succeeds expect call to storage`() = runTest {
|
||||||
coEvery { storage.getBaseURL() } returns TEST_BASE_URL
|
coEvery { storage.getServerVersion() } returns null
|
||||||
subject.requireUrl()
|
coEvery { storage.getBaseURL() } returns null
|
||||||
coVerify { storage.getBaseURL() }
|
coEvery { dataSource.getVersionInfo() } returns VersionInfo(TEST_VERSION)
|
||||||
|
subject.tryBaseURL(TEST_BASE_URL)
|
||||||
|
coVerify {
|
||||||
|
storage.storeBaseURL(eq(TEST_BASE_URL))
|
||||||
|
dataSource.getVersionInfo()
|
||||||
|
storage.storeServerVersion(TEST_VERSION)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when storeBaseUrl expect call to storage`() = runTest {
|
fun `when tryBaseURL fails expect call to storage`() = runTest {
|
||||||
subject.storeBaseURL(TEST_BASE_URL, TEST_VERSION)
|
coEvery { storage.getServerVersion() } returns "serverVersion"
|
||||||
coVerify { storage.storeBaseURL(TEST_BASE_URL, TEST_VERSION) }
|
coEvery { storage.getBaseURL() } returns "baseUrl"
|
||||||
|
coEvery { dataSource.getVersionInfo() } throws IOException()
|
||||||
|
subject.tryBaseURL(TEST_BASE_URL)
|
||||||
|
coVerify {
|
||||||
|
storage.storeBaseURL(eq(TEST_BASE_URL))
|
||||||
|
dataSource.getVersionInfo()
|
||||||
|
storage.storeBaseURL(eq("baseUrl"), eq("serverVersion"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when storage is empty expect getVersion to call data source`() = runTest {
|
fun `when storage is empty expect getVersion to call data source`() = runTest {
|
||||||
coEvery { storage.getServerVersion() } returns null
|
coEvery { storage.getServerVersion() } returns null
|
||||||
coEvery { storage.getBaseURL() } returns TEST_BASE_URL
|
coEvery { storage.getBaseURL() } returns TEST_BASE_URL
|
||||||
coEvery { dataSource.getVersionInfo(eq(TEST_BASE_URL)) } returns VERSION_INFO_V0
|
coEvery { dataSource.getVersionInfo() } returns VERSION_INFO_V0
|
||||||
subject.getVersion()
|
subject.getVersion()
|
||||||
coVerify { dataSource.getVersionInfo(eq(TEST_BASE_URL)) }
|
coVerify { dataSource.getVersionInfo() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when storage is empty and data source has value expect getVersion to save it`() = runTest {
|
fun `when storage is empty and data source has value expect getVersion to save it`() = runTest {
|
||||||
coEvery { storage.getServerVersion() } returns null
|
coEvery { storage.getServerVersion() } returns null
|
||||||
coEvery { storage.getBaseURL() } returns TEST_BASE_URL
|
coEvery { storage.getBaseURL() } returns TEST_BASE_URL
|
||||||
coEvery { dataSource.getVersionInfo(eq(TEST_BASE_URL)) } returns VersionInfo(TEST_VERSION)
|
coEvery { dataSource.getVersionInfo() } returns VersionInfo(TEST_VERSION)
|
||||||
subject.getVersion()
|
subject.getVersion()
|
||||||
coVerify { storage.storeServerVersion(TEST_VERSION) }
|
coVerify { storage.storeServerVersion(TEST_VERSION) }
|
||||||
}
|
}
|
||||||
@@ -92,7 +100,7 @@ class ServerInfoRepoTest : BaseUnitTest() {
|
|||||||
fun `when data source has invalid value expect getVersion to throw`() = runTest {
|
fun `when data source has invalid value expect getVersion to throw`() = runTest {
|
||||||
coEvery { storage.getServerVersion() } returns null
|
coEvery { storage.getServerVersion() } returns null
|
||||||
coEvery { storage.getBaseURL() } returns TEST_BASE_URL
|
coEvery { storage.getBaseURL() } returns TEST_BASE_URL
|
||||||
coEvery { dataSource.getVersionInfo(eq(TEST_BASE_URL)) } returns VersionInfo("v2.0.0")
|
coEvery { dataSource.getVersionInfo() } returns VersionInfo("v2.0.0")
|
||||||
subject.getVersion()
|
subject.getVersion()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +108,7 @@ class ServerInfoRepoTest : BaseUnitTest() {
|
|||||||
fun `when data source has invalid value expect getVersion not to save`() = runTest {
|
fun `when data source has invalid value expect getVersion not to save`() = runTest {
|
||||||
coEvery { storage.getServerVersion() } returns null
|
coEvery { storage.getServerVersion() } returns null
|
||||||
coEvery { storage.getBaseURL() } returns TEST_BASE_URL
|
coEvery { storage.getBaseURL() } returns TEST_BASE_URL
|
||||||
coEvery { dataSource.getVersionInfo(eq(TEST_BASE_URL)) } returns VersionInfo("v2.0.0")
|
coEvery { dataSource.getVersionInfo() } returns VersionInfo("v2.0.0")
|
||||||
subject.runCatching { getVersion() }
|
subject.runCatching { getVersion() }
|
||||||
coVerify(inverse = true) { storage.storeServerVersion(any()) }
|
coVerify(inverse = true) { storage.storeServerVersion(any()) }
|
||||||
}
|
}
|
||||||
@@ -116,7 +124,7 @@ class ServerInfoRepoTest : BaseUnitTest() {
|
|||||||
fun `when storage has value expect getVersion to not call data source`() = runTest {
|
fun `when storage has value expect getVersion to not call data source`() = runTest {
|
||||||
coEvery { storage.getServerVersion() } returns TEST_VERSION
|
coEvery { storage.getServerVersion() } returns TEST_VERSION
|
||||||
subject.getVersion()
|
subject.getVersion()
|
||||||
coVerify(inverse = true) { dataSource.getVersionInfo(any()) }
|
coVerify(inverse = true) { dataSource.getVersionInfo() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -135,7 +143,7 @@ class ServerInfoRepoTest : BaseUnitTest() {
|
|||||||
fun `when data source has valid v0 value expect getVersion to return it`() = runTest {
|
fun `when data source has valid v0 value expect getVersion to return it`() = runTest {
|
||||||
coEvery { storage.getServerVersion() } returns null
|
coEvery { storage.getServerVersion() } returns null
|
||||||
coEvery { storage.getBaseURL() } returns TEST_BASE_URL
|
coEvery { storage.getBaseURL() } returns TEST_BASE_URL
|
||||||
coEvery { dataSource.getVersionInfo(eq(TEST_BASE_URL)) } returns VersionInfo("v0.5.6")
|
coEvery { dataSource.getVersionInfo() } returns VersionInfo("v0.5.6")
|
||||||
assertThat(subject.getVersion()).isEqualTo(ServerVersion.V0)
|
assertThat(subject.getVersion()).isEqualTo(ServerVersion.V0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +151,7 @@ class ServerInfoRepoTest : BaseUnitTest() {
|
|||||||
fun `when data source has valid v1 value expect getVersion to return it`() = runTest {
|
fun `when data source has valid v1 value expect getVersion to return it`() = runTest {
|
||||||
coEvery { storage.getServerVersion() } returns null
|
coEvery { storage.getServerVersion() } returns null
|
||||||
coEvery { storage.getBaseURL() } returns TEST_BASE_URL
|
coEvery { storage.getBaseURL() } returns TEST_BASE_URL
|
||||||
coEvery { dataSource.getVersionInfo(eq(TEST_BASE_URL)) } returns VersionInfo("v1.0.0-beta05")
|
coEvery { dataSource.getVersionInfo() } returns VersionInfo("v1.0.0-beta05")
|
||||||
assertThat(subject.getVersion()).isEqualTo(ServerVersion.V1)
|
assertThat(subject.getVersion()).isEqualTo(ServerVersion.V1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,6 @@ import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
|||||||
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0
|
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0
|
||||||
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1
|
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1
|
||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER
|
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_SERVER_VERSION_V0
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION_V0
|
||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION_V1
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION_V1
|
||||||
import gq.kirmanak.mealient.test.BaseUnitTest
|
import gq.kirmanak.mealient.test.BaseUnitTest
|
||||||
@@ -55,15 +54,14 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
|
|||||||
fun `when server version v1 expect requestRecipeInfo to call v1`() = runTest {
|
fun `when server version v1 expect requestRecipeInfo to call v1`() = runTest {
|
||||||
val slug = "porridge"
|
val slug = "porridge"
|
||||||
coEvery {
|
coEvery {
|
||||||
v1Source.requestRecipeInfo(eq(TEST_BASE_URL), eq(slug))
|
v1Source.requestRecipeInfo(eq(slug))
|
||||||
} returns PORRIDGE_RECIPE_RESPONSE_V1
|
} returns PORRIDGE_RECIPE_RESPONSE_V1
|
||||||
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1
|
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1
|
||||||
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
|
|
||||||
coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER
|
coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER
|
||||||
|
|
||||||
val actual = subject.requestRecipeInfo(slug)
|
val actual = subject.requestRecipeInfo(slug)
|
||||||
|
|
||||||
coVerify { v1Source.requestRecipeInfo(eq(TEST_BASE_URL), eq(slug)) }
|
coVerify { v1Source.requestRecipeInfo(eq(slug)) }
|
||||||
|
|
||||||
assertThat(actual).isEqualTo(PORRIDGE_FULL_RECIPE_INFO)
|
assertThat(actual).isEqualTo(PORRIDGE_FULL_RECIPE_INFO)
|
||||||
}
|
}
|
||||||
@@ -71,10 +69,9 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `when server version v1 expect requestRecipes to call v1`() = runTest {
|
fun `when server version v1 expect requestRecipes to call v1`() = runTest {
|
||||||
coEvery {
|
coEvery {
|
||||||
v1Source.requestRecipes(any(), any(), any())
|
v1Source.requestRecipes(any(), any())
|
||||||
} returns listOf(PORRIDGE_RECIPE_SUMMARY_RESPONSE_V1)
|
} returns listOf(PORRIDGE_RECIPE_SUMMARY_RESPONSE_V1)
|
||||||
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1
|
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1
|
||||||
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
|
|
||||||
coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER
|
coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER
|
||||||
|
|
||||||
val actual = subject.requestRecipes(40, 10)
|
val actual = subject.requestRecipes(40, 10)
|
||||||
@@ -82,7 +79,7 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
|
|||||||
val page = 5 // 0-9 (1), 10-19 (2), 20-29 (3), 30-39 (4), 40-49 (5)
|
val page = 5 // 0-9 (1), 10-19 (2), 20-29 (3), 30-39 (4), 40-49 (5)
|
||||||
val perPage = 10
|
val perPage = 10
|
||||||
coVerify {
|
coVerify {
|
||||||
v1Source.requestRecipes(eq(TEST_BASE_URL), eq(page), eq(perPage))
|
v1Source.requestRecipes(eq(page), eq(perPage))
|
||||||
}
|
}
|
||||||
|
|
||||||
assertThat(actual).isEqualTo(listOf(RECIPE_SUMMARY_PORRIDGE_V1))
|
assertThat(actual).isEqualTo(listOf(RECIPE_SUMMARY_PORRIDGE_V1))
|
||||||
@@ -91,10 +88,9 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `when server version v0 expect requestRecipes to call v0`() = runTest {
|
fun `when server version v0 expect requestRecipes to call v0`() = runTest {
|
||||||
coEvery {
|
coEvery {
|
||||||
v0Source.requestRecipes(any(), any(), any())
|
v0Source.requestRecipes(any(), any())
|
||||||
} returns listOf(PORRIDGE_RECIPE_SUMMARY_RESPONSE_V0)
|
} returns listOf(PORRIDGE_RECIPE_SUMMARY_RESPONSE_V0)
|
||||||
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0
|
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0
|
||||||
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
|
|
||||||
coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER
|
coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER
|
||||||
|
|
||||||
val start = 40
|
val start = 40
|
||||||
@@ -102,7 +98,7 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
|
|||||||
val actual = subject.requestRecipes(start, limit)
|
val actual = subject.requestRecipes(start, limit)
|
||||||
|
|
||||||
coVerify {
|
coVerify {
|
||||||
v0Source.requestRecipes(eq(TEST_BASE_URL), eq(start), eq(limit))
|
v0Source.requestRecipes(eq(start), eq(limit))
|
||||||
}
|
}
|
||||||
|
|
||||||
assertThat(actual).isEqualTo(listOf(RECIPE_SUMMARY_PORRIDGE_V0))
|
assertThat(actual).isEqualTo(listOf(RECIPE_SUMMARY_PORRIDGE_V0))
|
||||||
@@ -110,9 +106,8 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
|
|||||||
|
|
||||||
@Test(expected = IOException::class)
|
@Test(expected = IOException::class)
|
||||||
fun `when request fails expect addRecipe to rethrow`() = runTest {
|
fun `when request fails expect addRecipe to rethrow`() = runTest {
|
||||||
coEvery { v0Source.addRecipe(any(), any()) } throws IOException()
|
coEvery { v0Source.addRecipe(any()) } throws IOException()
|
||||||
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0
|
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0
|
||||||
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
|
|
||||||
coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER
|
coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER
|
||||||
subject.addRecipe(PORRIDGE_ADD_RECIPE_INFO)
|
subject.addRecipe(PORRIDGE_ADD_RECIPE_INFO)
|
||||||
}
|
}
|
||||||
@@ -121,16 +116,14 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
|
|||||||
fun `when server version v0 expect addRecipe to call v0`() = runTest {
|
fun `when server version v0 expect addRecipe to call v0`() = runTest {
|
||||||
val slug = "porridge"
|
val slug = "porridge"
|
||||||
|
|
||||||
coEvery { v0Source.addRecipe(any(), any()) } returns slug
|
coEvery { v0Source.addRecipe(any()) } returns slug
|
||||||
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0
|
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0
|
||||||
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
|
|
||||||
coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER
|
coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER
|
||||||
|
|
||||||
val actual = subject.addRecipe(PORRIDGE_ADD_RECIPE_INFO)
|
val actual = subject.addRecipe(PORRIDGE_ADD_RECIPE_INFO)
|
||||||
|
|
||||||
coVerify {
|
coVerify {
|
||||||
v0Source.addRecipe(
|
v0Source.addRecipe(
|
||||||
eq(TEST_BASE_URL),
|
|
||||||
eq(PORRIDGE_ADD_RECIPE_REQUEST_V0),
|
eq(PORRIDGE_ADD_RECIPE_REQUEST_V0),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -142,24 +135,21 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
|
|||||||
fun `when server version v1 expect addRecipe to call v1`() = runTest {
|
fun `when server version v1 expect addRecipe to call v1`() = runTest {
|
||||||
val slug = "porridge"
|
val slug = "porridge"
|
||||||
|
|
||||||
coEvery { v1Source.createRecipe(any(), any()) } returns slug
|
coEvery { v1Source.createRecipe(any()) } returns slug
|
||||||
coEvery {
|
coEvery {
|
||||||
v1Source.updateRecipe(any(), any(), any())
|
v1Source.updateRecipe(any(), any())
|
||||||
} returns PORRIDGE_RECIPE_RESPONSE_V1
|
} returns PORRIDGE_RECIPE_RESPONSE_V1
|
||||||
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1
|
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1
|
||||||
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
|
|
||||||
coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER
|
coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER
|
||||||
|
|
||||||
val actual = subject.addRecipe(PORRIDGE_ADD_RECIPE_INFO)
|
val actual = subject.addRecipe(PORRIDGE_ADD_RECIPE_INFO)
|
||||||
|
|
||||||
coVerifySequence {
|
coVerifySequence {
|
||||||
v1Source.createRecipe(
|
v1Source.createRecipe(
|
||||||
eq(TEST_BASE_URL),
|
|
||||||
eq(PORRIDGE_CREATE_RECIPE_REQUEST_V1),
|
eq(PORRIDGE_CREATE_RECIPE_REQUEST_V1),
|
||||||
)
|
)
|
||||||
|
|
||||||
v1Source.updateRecipe(
|
v1Source.updateRecipe(
|
||||||
eq(TEST_BASE_URL),
|
|
||||||
eq(slug),
|
eq(slug),
|
||||||
eq(PORRIDGE_UPDATE_RECIPE_REQUEST_V1),
|
eq(PORRIDGE_UPDATE_RECIPE_REQUEST_V1),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,11 +3,8 @@ package gq.kirmanak.mealient.ui.baseurl
|
|||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||||
import gq.kirmanak.mealient.data.baseurl.VersionDataSource
|
|
||||||
import gq.kirmanak.mealient.data.baseurl.VersionInfo
|
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL
|
||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_VERSION
|
|
||||||
import gq.kirmanak.mealient.test.BaseUnitTest
|
import gq.kirmanak.mealient.test.BaseUnitTest
|
||||||
import gq.kirmanak.mealient.ui.OperationUiState
|
import gq.kirmanak.mealient.ui.OperationUiState
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
@@ -31,9 +28,6 @@ class BaseURLViewModelTest : BaseUnitTest() {
|
|||||||
@MockK(relaxUnitFun = true)
|
@MockK(relaxUnitFun = true)
|
||||||
lateinit var recipeRepo: RecipeRepo
|
lateinit var recipeRepo: RecipeRepo
|
||||||
|
|
||||||
@MockK
|
|
||||||
lateinit var versionDataSource: VersionDataSource
|
|
||||||
|
|
||||||
lateinit var subject: BaseURLViewModel
|
lateinit var subject: BaseURLViewModel
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -43,7 +37,6 @@ class BaseURLViewModelTest : BaseUnitTest() {
|
|||||||
serverInfoRepo = serverInfoRepo,
|
serverInfoRepo = serverInfoRepo,
|
||||||
authRepo = authRepo,
|
authRepo = authRepo,
|
||||||
recipeRepo = recipeRepo,
|
recipeRepo = recipeRepo,
|
||||||
versionDataSource = versionDataSource,
|
|
||||||
logger = logger,
|
logger = logger,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -51,13 +44,13 @@ class BaseURLViewModelTest : BaseUnitTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `when saveBaseURL expect no version checks given that current URL matches new`() = runTest {
|
fun `when saveBaseURL expect no version checks given that current URL matches new`() = runTest {
|
||||||
setupSaveBaseUrlWithOldUrl()
|
setupSaveBaseUrlWithOldUrl()
|
||||||
coVerify(inverse = true) { versionDataSource.getVersionInfo(any()) }
|
coVerify(inverse = true) { serverInfoRepo.tryBaseURL(any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when saveBaseURL expect URL isn't saved given that current URL matches new`() = runTest {
|
fun `when saveBaseURL expect URL isn't saved given that current URL matches new`() = runTest {
|
||||||
setupSaveBaseUrlWithOldUrl()
|
setupSaveBaseUrlWithOldUrl()
|
||||||
coVerify(inverse = true) { serverInfoRepo.storeBaseURL(any(), any()) }
|
coVerify(inverse = true) { serverInfoRepo.tryBaseURL(any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -74,7 +67,7 @@ class BaseURLViewModelTest : BaseUnitTest() {
|
|||||||
|
|
||||||
private fun TestScope.setupSaveBaseUrlWithOldUrl() {
|
private fun TestScope.setupSaveBaseUrlWithOldUrl() {
|
||||||
coEvery { serverInfoRepo.getUrl() } returns TEST_BASE_URL
|
coEvery { serverInfoRepo.getUrl() } returns TEST_BASE_URL
|
||||||
versionDataSourceReturnsSuccess()
|
coEvery { serverInfoRepo.tryBaseURL(any()) } returns Result.success(Unit)
|
||||||
subject.saveBaseUrl(TEST_BASE_URL)
|
subject.saveBaseUrl(TEST_BASE_URL)
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
}
|
}
|
||||||
@@ -82,7 +75,7 @@ class BaseURLViewModelTest : BaseUnitTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `when saveBaseUrl expect URL is saved given that new URL doesn't match old`() = runTest {
|
fun `when saveBaseUrl expect URL is saved given that new URL doesn't match old`() = runTest {
|
||||||
setupSaveBaseUrlWithNewUrl()
|
setupSaveBaseUrlWithNewUrl()
|
||||||
coVerify { serverInfoRepo.storeBaseURL(eq(TEST_BASE_URL), eq(TEST_VERSION)) }
|
coVerify { serverInfoRepo.tryBaseURL(eq(TEST_BASE_URL)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -99,21 +92,15 @@ class BaseURLViewModelTest : BaseUnitTest() {
|
|||||||
|
|
||||||
private fun TestScope.setupSaveBaseUrlWithNewUrl() {
|
private fun TestScope.setupSaveBaseUrlWithNewUrl() {
|
||||||
coEvery { serverInfoRepo.getUrl() } returns null
|
coEvery { serverInfoRepo.getUrl() } returns null
|
||||||
versionDataSourceReturnsSuccess()
|
coEvery { serverInfoRepo.tryBaseURL(any()) } returns Result.success(Unit)
|
||||||
subject.saveBaseUrl(TEST_BASE_URL)
|
subject.saveBaseUrl(TEST_BASE_URL)
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun versionDataSourceReturnsSuccess() {
|
|
||||||
coEvery {
|
|
||||||
versionDataSource.getVersionInfo(eq(TEST_BASE_URL))
|
|
||||||
} returns VersionInfo(TEST_VERSION)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when saveBaseURL expect error given that version can't be fetched`() = runTest {
|
fun `when saveBaseURL expect error given that version can't be fetched`() = runTest {
|
||||||
coEvery { serverInfoRepo.getUrl() } returns null
|
coEvery { serverInfoRepo.getUrl() } returns null
|
||||||
coEvery { versionDataSource.getVersionInfo(eq(TEST_BASE_URL)) } throws IOException()
|
coEvery { serverInfoRepo.tryBaseURL(any()) } returns Result.failure(IOException())
|
||||||
subject.saveBaseUrl(TEST_BASE_URL)
|
subject.saveBaseUrl(TEST_BASE_URL)
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
assertThat(subject.uiState.value).isInstanceOf(OperationUiState.Failure::class.java)
|
assertThat(subject.uiState.value).isInstanceOf(OperationUiState.Failure::class.java)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import dagger.hilt.InstallIn
|
|||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import dagger.multibindings.IntoSet
|
import dagger.multibindings.IntoSet
|
||||||
import gq.kirmanak.mealient.datasource.impl.AuthInterceptor
|
import gq.kirmanak.mealient.datasource.impl.AuthInterceptor
|
||||||
|
import gq.kirmanak.mealient.datasource.impl.BaseUrlInterceptor
|
||||||
import gq.kirmanak.mealient.datasource.impl.CacheBuilderImpl
|
import gq.kirmanak.mealient.datasource.impl.CacheBuilderImpl
|
||||||
import gq.kirmanak.mealient.datasource.impl.NetworkRequestWrapperImpl
|
import gq.kirmanak.mealient.datasource.impl.NetworkRequestWrapperImpl
|
||||||
import gq.kirmanak.mealient.datasource.impl.OkHttpBuilderImpl
|
import gq.kirmanak.mealient.datasource.impl.OkHttpBuilderImpl
|
||||||
@@ -20,7 +21,6 @@ import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1Impl
|
|||||||
import gq.kirmanak.mealient.datasource.v1.MealieServiceV1
|
import gq.kirmanak.mealient.datasource.v1.MealieServiceV1
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Interceptor
|
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import retrofit2.Converter
|
import retrofit2.Converter
|
||||||
@@ -55,8 +55,11 @@ interface DataSourceModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideRetrofit(retrofitBuilder: RetrofitBuilder): Retrofit =
|
fun provideRetrofit(retrofitBuilder: RetrofitBuilder): Retrofit {
|
||||||
retrofitBuilder.buildRetrofit("https://beta.mealie.io/")
|
// Fake base URL which will be replaced later by BaseUrlInterceptor
|
||||||
|
// Solution was suggested here https://github.com/square/retrofit/issues/2161#issuecomment-274204152
|
||||||
|
return retrofitBuilder.buildRetrofit("http://localhost/")
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
@@ -92,5 +95,10 @@ interface DataSourceModule {
|
|||||||
@Binds
|
@Binds
|
||||||
@Singleton
|
@Singleton
|
||||||
@IntoSet
|
@IntoSet
|
||||||
fun bindAuthInterceptor(authInterceptor: AuthInterceptor): Interceptor
|
fun bindAuthInterceptor(authInterceptor: AuthInterceptor): LocalInterceptor
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
@IntoSet
|
||||||
|
fun bindBaseUrlInterceptor(baseUrlInterceptor: BaseUrlInterceptor): LocalInterceptor
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource
|
||||||
|
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker interface which is different from [Interceptor] only in how it is handled.
|
||||||
|
* [Interceptor]s are added as network interceptors to OkHttpClient whereas [LocalInterceptor]s
|
||||||
|
* are added via [OkHttpClient.Builder.addInterceptor] function. They will observe the
|
||||||
|
* full call lifecycle, whereas network interceptors will see only the network part.
|
||||||
|
*/
|
||||||
|
interface LocalInterceptor : Interceptor
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource
|
||||||
|
|
||||||
|
interface ServerUrlProvider {
|
||||||
|
|
||||||
|
suspend fun getUrl(): String?
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package gq.kirmanak.mealient.datasource.impl
|
|||||||
|
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import gq.kirmanak.mealient.datasource.AuthenticationProvider
|
import gq.kirmanak.mealient.datasource.AuthenticationProvider
|
||||||
|
import gq.kirmanak.mealient.datasource.LocalInterceptor
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
@@ -14,13 +15,13 @@ import javax.inject.Singleton
|
|||||||
class AuthInterceptor @Inject constructor(
|
class AuthInterceptor @Inject constructor(
|
||||||
private val logger: Logger,
|
private val logger: Logger,
|
||||||
private val authenticationProviderProvider: Provider<AuthenticationProvider>,
|
private val authenticationProviderProvider: Provider<AuthenticationProvider>,
|
||||||
) : Interceptor {
|
) : LocalInterceptor {
|
||||||
|
|
||||||
private val authenticationProvider: AuthenticationProvider
|
private val authenticationProvider: AuthenticationProvider
|
||||||
get() = authenticationProviderProvider.get()
|
get() = authenticationProviderProvider.get()
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
logger.v { "intercept() was called" }
|
logger.v { "intercept() was called with: request = ${chain.request()}" }
|
||||||
val header = getAuthHeader()
|
val header = getAuthHeader()
|
||||||
val request = chain.request().let {
|
val request = chain.request().let {
|
||||||
if (header == null) it else it.newBuilder().header(HEADER_NAME, header).build()
|
if (header == null) it else it.newBuilder().header(HEADER_NAME, header).build()
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource.impl
|
||||||
|
|
||||||
|
import gq.kirmanak.mealient.datasource.LocalInterceptor
|
||||||
|
import gq.kirmanak.mealient.datasource.ServerUrlProvider
|
||||||
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
import java.io.IOException
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Provider
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class BaseUrlInterceptor @Inject constructor(
|
||||||
|
private val serverUrlProviderProvider: Provider<ServerUrlProvider>,
|
||||||
|
private val logger: Logger,
|
||||||
|
) : LocalInterceptor {
|
||||||
|
|
||||||
|
private val serverUrlProvider: ServerUrlProvider
|
||||||
|
get() = serverUrlProviderProvider.get()
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
logger.v { "intercept() was called with: request = ${chain.request()}" }
|
||||||
|
val oldRequest = chain.request()
|
||||||
|
val baseUrl = getBaseUrl()
|
||||||
|
val correctUrl = oldRequest.url
|
||||||
|
.newBuilder()
|
||||||
|
.host(baseUrl.host)
|
||||||
|
.scheme(baseUrl.scheme)
|
||||||
|
.build()
|
||||||
|
val newRequest = oldRequest.newBuilder().url(correctUrl).build()
|
||||||
|
logger.d { "Replaced ${oldRequest.url} with ${newRequest.url}" }
|
||||||
|
return chain.proceed(newRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getBaseUrl() = runBlocking {
|
||||||
|
serverUrlProvider.getUrl()?.toHttpUrlOrNull() ?: throw IOException("Base URL is unknown")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package gq.kirmanak.mealient.datasource.impl
|
package gq.kirmanak.mealient.datasource.impl
|
||||||
|
|
||||||
import gq.kirmanak.mealient.datasource.CacheBuilder
|
import gq.kirmanak.mealient.datasource.CacheBuilder
|
||||||
|
import gq.kirmanak.mealient.datasource.LocalInterceptor
|
||||||
import gq.kirmanak.mealient.datasource.OkHttpBuilder
|
import gq.kirmanak.mealient.datasource.OkHttpBuilder
|
||||||
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -12,10 +14,16 @@ class OkHttpBuilderImpl @Inject constructor(
|
|||||||
private val cacheBuilder: CacheBuilder,
|
private val cacheBuilder: CacheBuilder,
|
||||||
// Use @JvmSuppressWildcards because otherwise dagger can't inject it (https://stackoverflow.com/a/43149382)
|
// Use @JvmSuppressWildcards because otherwise dagger can't inject it (https://stackoverflow.com/a/43149382)
|
||||||
private val interceptors: Set<@JvmSuppressWildcards Interceptor>,
|
private val interceptors: Set<@JvmSuppressWildcards Interceptor>,
|
||||||
|
private val localInterceptors: Set<@JvmSuppressWildcards LocalInterceptor>,
|
||||||
|
private val logger: Logger,
|
||||||
) : OkHttpBuilder {
|
) : OkHttpBuilder {
|
||||||
|
|
||||||
override fun buildOkHttp(): OkHttpClient = OkHttpClient.Builder()
|
override fun buildOkHttp(): OkHttpClient {
|
||||||
.apply { interceptors.forEach(::addNetworkInterceptor) }
|
logger.v { "buildOkHttp() was called with cacheBuilder = $cacheBuilder, interceptors = $interceptors, localInterceptors = $localInterceptors" }
|
||||||
.cache(cacheBuilder.buildCache())
|
return OkHttpClient.Builder().apply {
|
||||||
.build()
|
localInterceptors.forEach(::addInterceptor)
|
||||||
|
interceptors.forEach(::addNetworkInterceptor)
|
||||||
|
cache(cacheBuilder.buildCache())
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,6 @@ import gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0
|
|||||||
interface MealieDataSourceV0 {
|
interface MealieDataSourceV0 {
|
||||||
|
|
||||||
suspend fun addRecipe(
|
suspend fun addRecipe(
|
||||||
baseUrl: String,
|
|
||||||
recipe: AddRecipeRequestV0,
|
recipe: AddRecipeRequestV0,
|
||||||
): String
|
): String
|
||||||
|
|
||||||
@@ -18,33 +17,27 @@ interface MealieDataSourceV0 {
|
|||||||
* Tries to acquire authentication token using the provided credentials
|
* Tries to acquire authentication token using the provided credentials
|
||||||
*/
|
*/
|
||||||
suspend fun authenticate(
|
suspend fun authenticate(
|
||||||
baseUrl: String,
|
|
||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
): String
|
): String
|
||||||
|
|
||||||
suspend fun getVersionInfo(
|
suspend fun getVersionInfo(
|
||||||
baseUrl: String,
|
|
||||||
): VersionResponseV0
|
): VersionResponseV0
|
||||||
|
|
||||||
suspend fun requestRecipes(
|
suspend fun requestRecipes(
|
||||||
baseUrl: String,
|
|
||||||
start: Int,
|
start: Int,
|
||||||
limit: Int,
|
limit: Int,
|
||||||
): List<GetRecipeSummaryResponseV0>
|
): List<GetRecipeSummaryResponseV0>
|
||||||
|
|
||||||
suspend fun requestRecipeInfo(
|
suspend fun requestRecipeInfo(
|
||||||
baseUrl: String,
|
|
||||||
slug: String,
|
slug: String,
|
||||||
): GetRecipeResponseV0
|
): GetRecipeResponseV0
|
||||||
|
|
||||||
suspend fun parseRecipeFromURL(
|
suspend fun parseRecipeFromURL(
|
||||||
baseUrl: String,
|
|
||||||
request: ParseRecipeURLRequestV0,
|
request: ParseRecipeURLRequestV0,
|
||||||
): String
|
): String
|
||||||
|
|
||||||
suspend fun createApiToken(
|
suspend fun createApiToken(
|
||||||
baseUrl: String,
|
|
||||||
request: CreateApiTokenRequestV0,
|
request: CreateApiTokenRequestV0,
|
||||||
): String
|
): String
|
||||||
}
|
}
|
||||||
@@ -26,34 +26,30 @@ class MealieDataSourceV0Impl @Inject constructor(
|
|||||||
) : MealieDataSourceV0 {
|
) : MealieDataSourceV0 {
|
||||||
|
|
||||||
override suspend fun addRecipe(
|
override suspend fun addRecipe(
|
||||||
baseUrl: String,
|
|
||||||
recipe: AddRecipeRequestV0,
|
recipe: AddRecipeRequestV0,
|
||||||
): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
block = { service.addRecipe("$baseUrl/api/recipes/create", recipe) },
|
block = { service.addRecipe(recipe) },
|
||||||
logMethod = { "addRecipe" },
|
logMethod = { "addRecipe" },
|
||||||
logParameters = { "baseUrl = $baseUrl, recipe = $recipe" }
|
logParameters = { "recipe = $recipe" }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun authenticate(
|
override suspend fun authenticate(
|
||||||
baseUrl: String,
|
|
||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
): String = networkRequestWrapper.makeCall(
|
): String = networkRequestWrapper.makeCall(
|
||||||
block = { service.getToken("$baseUrl/api/auth/token", username, password) },
|
block = { service.getToken(username, password) },
|
||||||
logMethod = { "authenticate" },
|
logMethod = { "authenticate" },
|
||||||
logParameters = { "baseUrl = $baseUrl, username = $username, password = $password" }
|
logParameters = { "username = $username, password = $password" }
|
||||||
).map { it.accessToken }.getOrElse {
|
).map { it.accessToken }.getOrElse {
|
||||||
val errorBody = (it as? HttpException)?.response()?.errorBody() ?: throw it
|
val errorBody = (it as? HttpException)?.response()?.errorBody() ?: throw it
|
||||||
val errorDetailV0 = errorBody.decode<ErrorDetailV0>(json)
|
val errorDetailV0 = errorBody.decode<ErrorDetailV0>(json)
|
||||||
throw if (errorDetailV0.detail == "Unauthorized") NetworkError.Unauthorized(it) else it
|
throw if (errorDetailV0.detail == "Unauthorized") NetworkError.Unauthorized(it) else it
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getVersionInfo(
|
override suspend fun getVersionInfo(): VersionResponseV0 = networkRequestWrapper.makeCall(
|
||||||
baseUrl: String
|
block = { service.getVersion() },
|
||||||
): VersionResponseV0 = networkRequestWrapper.makeCall(
|
|
||||||
block = { service.getVersion("$baseUrl/api/debug/version") },
|
|
||||||
logMethod = { "getVersionInfo" },
|
logMethod = { "getVersionInfo" },
|
||||||
logParameters = { "baseUrl = $baseUrl" },
|
logParameters = { "" },
|
||||||
).getOrElse {
|
).getOrElse {
|
||||||
throw when (it) {
|
throw when (it) {
|
||||||
is HttpException, is SerializationException -> NetworkError.NotMealie(it)
|
is HttpException, is SerializationException -> NetworkError.NotMealie(it)
|
||||||
@@ -63,39 +59,35 @@ class MealieDataSourceV0Impl @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun requestRecipes(
|
override suspend fun requestRecipes(
|
||||||
baseUrl: String,
|
|
||||||
start: Int,
|
start: Int,
|
||||||
limit: Int,
|
limit: Int,
|
||||||
): List<GetRecipeSummaryResponseV0> = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
): List<GetRecipeSummaryResponseV0> = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
block = { service.getRecipeSummary("$baseUrl/api/recipes/summary", start, limit) },
|
block = { service.getRecipeSummary(start, limit) },
|
||||||
logMethod = { "requestRecipes" },
|
logMethod = { "requestRecipes" },
|
||||||
logParameters = { "baseUrl = $baseUrl, start = $start, limit = $limit" }
|
logParameters = { "start = $start, limit = $limit" }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun requestRecipeInfo(
|
override suspend fun requestRecipeInfo(
|
||||||
baseUrl: String,
|
|
||||||
slug: String,
|
slug: String,
|
||||||
): GetRecipeResponseV0 = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
): GetRecipeResponseV0 = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
block = { service.getRecipe("$baseUrl/api/recipes/$slug") },
|
block = { service.getRecipe(slug) },
|
||||||
logMethod = { "requestRecipeInfo" },
|
logMethod = { "requestRecipeInfo" },
|
||||||
logParameters = { "baseUrl = $baseUrl, slug = $slug" }
|
logParameters = { "slug = $slug" }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun parseRecipeFromURL(
|
override suspend fun parseRecipeFromURL(
|
||||||
baseUrl: String,
|
|
||||||
request: ParseRecipeURLRequestV0
|
request: ParseRecipeURLRequestV0
|
||||||
): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
block = { service.createRecipeFromURL("$baseUrl/api/recipes/create-url", request) },
|
block = { service.createRecipeFromURL(request) },
|
||||||
logMethod = { "parseRecipeFromURL" },
|
logMethod = { "parseRecipeFromURL" },
|
||||||
logParameters = { "baseUrl = $baseUrl, request = $request" },
|
logParameters = { "request = $request" },
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun createApiToken(
|
override suspend fun createApiToken(
|
||||||
baseUrl: String,
|
|
||||||
request: CreateApiTokenRequestV0,
|
request: CreateApiTokenRequestV0,
|
||||||
): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
block = { service.createApiToken("$baseUrl/api/users/api-tokens", request) },
|
block = { service.createApiToken(request) },
|
||||||
logMethod = { "createApiToken" },
|
logMethod = { "createApiToken" },
|
||||||
logParameters = { "baseUrl = $baseUrl, request = $request" }
|
logParameters = { "request = $request" }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,45 +6,38 @@ import retrofit2.http.*
|
|||||||
interface MealieServiceV0 {
|
interface MealieServiceV0 {
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST
|
@POST("/api/auth/token")
|
||||||
suspend fun getToken(
|
suspend fun getToken(
|
||||||
@Url url: String,
|
|
||||||
@Field("username") username: String,
|
@Field("username") username: String,
|
||||||
@Field("password") password: String,
|
@Field("password") password: String,
|
||||||
): GetTokenResponseV0
|
): GetTokenResponseV0
|
||||||
|
|
||||||
@POST
|
@POST("/api/recipes/create")
|
||||||
suspend fun addRecipe(
|
suspend fun addRecipe(
|
||||||
@Url url: String,
|
|
||||||
@Body addRecipeRequestV0: AddRecipeRequestV0,
|
@Body addRecipeRequestV0: AddRecipeRequestV0,
|
||||||
): String
|
): String
|
||||||
|
|
||||||
@GET
|
@GET("/api/debug/version")
|
||||||
suspend fun getVersion(
|
suspend fun getVersion(): VersionResponseV0
|
||||||
@Url url: String,
|
|
||||||
): VersionResponseV0
|
|
||||||
|
|
||||||
@GET
|
@GET("/api/recipes/summary")
|
||||||
suspend fun getRecipeSummary(
|
suspend fun getRecipeSummary(
|
||||||
@Url url: String,
|
|
||||||
@Query("start") start: Int,
|
@Query("start") start: Int,
|
||||||
@Query("limit") limit: Int,
|
@Query("limit") limit: Int,
|
||||||
): List<GetRecipeSummaryResponseV0>
|
): List<GetRecipeSummaryResponseV0>
|
||||||
|
|
||||||
@GET
|
@GET("/api/recipes/{slug}")
|
||||||
suspend fun getRecipe(
|
suspend fun getRecipe(
|
||||||
@Url url: String,
|
@Path("slug") slug: String,
|
||||||
): GetRecipeResponseV0
|
): GetRecipeResponseV0
|
||||||
|
|
||||||
@POST
|
@POST("/api/recipes/create-url")
|
||||||
suspend fun createRecipeFromURL(
|
suspend fun createRecipeFromURL(
|
||||||
@Url url: String,
|
|
||||||
@Body request: ParseRecipeURLRequestV0,
|
@Body request: ParseRecipeURLRequestV0,
|
||||||
): String
|
): String
|
||||||
|
|
||||||
@POST
|
@POST("/api/users/api-tokens")
|
||||||
suspend fun createApiToken(
|
suspend fun createApiToken(
|
||||||
@Url url: String,
|
|
||||||
@Body request: CreateApiTokenRequestV0,
|
@Body request: CreateApiTokenRequestV0,
|
||||||
): String
|
): String
|
||||||
}
|
}
|
||||||
@@ -12,12 +12,10 @@ import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1
|
|||||||
interface MealieDataSourceV1 {
|
interface MealieDataSourceV1 {
|
||||||
|
|
||||||
suspend fun createRecipe(
|
suspend fun createRecipe(
|
||||||
baseUrl: String,
|
|
||||||
recipe: CreateRecipeRequestV1,
|
recipe: CreateRecipeRequestV1,
|
||||||
): String
|
): String
|
||||||
|
|
||||||
suspend fun updateRecipe(
|
suspend fun updateRecipe(
|
||||||
baseUrl: String,
|
|
||||||
slug: String,
|
slug: String,
|
||||||
recipe: UpdateRecipeRequestV1,
|
recipe: UpdateRecipeRequestV1,
|
||||||
): GetRecipeResponseV1
|
): GetRecipeResponseV1
|
||||||
@@ -26,33 +24,27 @@ interface MealieDataSourceV1 {
|
|||||||
* Tries to acquire authentication token using the provided credentials
|
* Tries to acquire authentication token using the provided credentials
|
||||||
*/
|
*/
|
||||||
suspend fun authenticate(
|
suspend fun authenticate(
|
||||||
baseUrl: String,
|
|
||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
): String
|
): String
|
||||||
|
|
||||||
suspend fun getVersionInfo(
|
suspend fun getVersionInfo(
|
||||||
baseUrl: String,
|
|
||||||
): VersionResponseV1
|
): VersionResponseV1
|
||||||
|
|
||||||
suspend fun requestRecipes(
|
suspend fun requestRecipes(
|
||||||
baseUrl: String,
|
|
||||||
page: Int,
|
page: Int,
|
||||||
perPage: Int,
|
perPage: Int,
|
||||||
): List<GetRecipeSummaryResponseV1>
|
): List<GetRecipeSummaryResponseV1>
|
||||||
|
|
||||||
suspend fun requestRecipeInfo(
|
suspend fun requestRecipeInfo(
|
||||||
baseUrl: String,
|
|
||||||
slug: String,
|
slug: String,
|
||||||
): GetRecipeResponseV1
|
): GetRecipeResponseV1
|
||||||
|
|
||||||
suspend fun parseRecipeFromURL(
|
suspend fun parseRecipeFromURL(
|
||||||
baseUrl: String,
|
|
||||||
request: ParseRecipeURLRequestV1,
|
request: ParseRecipeURLRequestV1,
|
||||||
): String
|
): String
|
||||||
|
|
||||||
suspend fun createApiToken(
|
suspend fun createApiToken(
|
||||||
baseUrl: String,
|
|
||||||
request: CreateApiTokenRequestV1,
|
request: CreateApiTokenRequestV1,
|
||||||
): CreateApiTokenResponseV1
|
): CreateApiTokenResponseV1
|
||||||
}
|
}
|
||||||
@@ -28,44 +28,39 @@ class MealieDataSourceV1Impl @Inject constructor(
|
|||||||
) : MealieDataSourceV1 {
|
) : MealieDataSourceV1 {
|
||||||
|
|
||||||
override suspend fun createRecipe(
|
override suspend fun createRecipe(
|
||||||
baseUrl: String,
|
|
||||||
recipe: CreateRecipeRequestV1
|
recipe: CreateRecipeRequestV1
|
||||||
): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
block = { service.createRecipe("$baseUrl/api/recipes", recipe) },
|
block = { service.createRecipe(recipe) },
|
||||||
logMethod = { "createRecipe" },
|
logMethod = { "createRecipe" },
|
||||||
logParameters = { "baseUrl = $baseUrl, recipe = $recipe" }
|
logParameters = { "recipe = $recipe" }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun updateRecipe(
|
override suspend fun updateRecipe(
|
||||||
baseUrl: String,
|
|
||||||
slug: String,
|
slug: String,
|
||||||
recipe: UpdateRecipeRequestV1
|
recipe: UpdateRecipeRequestV1
|
||||||
): GetRecipeResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
): GetRecipeResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
block = { service.updateRecipe("$baseUrl/api/recipes/$slug", recipe) },
|
block = { service.updateRecipe(recipe, slug) },
|
||||||
logMethod = { "updateRecipe" },
|
logMethod = { "updateRecipe" },
|
||||||
logParameters = { "baseUrl = $baseUrl, slug = $slug, recipe = $recipe" }
|
logParameters = { "slug = $slug, recipe = $recipe" }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun authenticate(
|
override suspend fun authenticate(
|
||||||
baseUrl: String,
|
|
||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
): String = networkRequestWrapper.makeCall(
|
): String = networkRequestWrapper.makeCall(
|
||||||
block = { service.getToken("$baseUrl/api/auth/token", username, password) },
|
block = { service.getToken(username, password) },
|
||||||
logMethod = { "authenticate" },
|
logMethod = { "authenticate" },
|
||||||
logParameters = { "baseUrl = $baseUrl, username = $username, password = $password" }
|
logParameters = { "username = $username, password = $password" }
|
||||||
).map { it.accessToken }.getOrElse {
|
).map { it.accessToken }.getOrElse {
|
||||||
val errorBody = (it as? HttpException)?.response()?.errorBody() ?: throw it
|
val errorBody = (it as? HttpException)?.response()?.errorBody() ?: throw it
|
||||||
val errorDetailV0 = errorBody.decode<ErrorDetailV1>(json)
|
val errorDetailV0 = errorBody.decode<ErrorDetailV1>(json)
|
||||||
throw if (errorDetailV0.detail == "Unauthorized") NetworkError.Unauthorized(it) else it
|
throw if (errorDetailV0.detail == "Unauthorized") NetworkError.Unauthorized(it) else it
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getVersionInfo(
|
override suspend fun getVersionInfo(): VersionResponseV1 = networkRequestWrapper.makeCall(
|
||||||
baseUrl: String,
|
block = { service.getVersion() },
|
||||||
): VersionResponseV1 = networkRequestWrapper.makeCall(
|
|
||||||
block = { service.getVersion("$baseUrl/api/app/about") },
|
|
||||||
logMethod = { "getVersionInfo" },
|
logMethod = { "getVersionInfo" },
|
||||||
logParameters = { "baseUrl = $baseUrl" },
|
logParameters = { "" },
|
||||||
).getOrElse {
|
).getOrElse {
|
||||||
throw when (it) {
|
throw when (it) {
|
||||||
is HttpException, is SerializationException -> NetworkError.NotMealie(it)
|
is HttpException, is SerializationException -> NetworkError.NotMealie(it)
|
||||||
@@ -75,40 +70,36 @@ class MealieDataSourceV1Impl @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun requestRecipes(
|
override suspend fun requestRecipes(
|
||||||
baseUrl: String,
|
|
||||||
page: Int,
|
page: Int,
|
||||||
perPage: Int
|
perPage: Int
|
||||||
): List<GetRecipeSummaryResponseV1> = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
): List<GetRecipeSummaryResponseV1> = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
block = { service.getRecipeSummary("$baseUrl/api/recipes", page, perPage) },
|
block = { service.getRecipeSummary(page, perPage) },
|
||||||
logMethod = { "requestRecipes" },
|
logMethod = { "requestRecipes" },
|
||||||
logParameters = { "baseUrl = $baseUrl, page = $page, perPage = $perPage" }
|
logParameters = { "page = $page, perPage = $perPage" }
|
||||||
).items
|
).items
|
||||||
|
|
||||||
override suspend fun requestRecipeInfo(
|
override suspend fun requestRecipeInfo(
|
||||||
baseUrl: String,
|
|
||||||
slug: String
|
slug: String
|
||||||
): GetRecipeResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
): GetRecipeResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
block = { service.getRecipe("$baseUrl/api/recipes/$slug") },
|
block = { service.getRecipe(slug) },
|
||||||
logMethod = { "requestRecipeInfo" },
|
logMethod = { "requestRecipeInfo" },
|
||||||
logParameters = { "baseUrl = $baseUrl, slug = $slug" }
|
logParameters = { "slug = $slug" }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun parseRecipeFromURL(
|
override suspend fun parseRecipeFromURL(
|
||||||
baseUrl: String,
|
|
||||||
request: ParseRecipeURLRequestV1
|
request: ParseRecipeURLRequestV1
|
||||||
): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
block = { service.createRecipeFromURL("$baseUrl/api/recipes/create-url", request) },
|
block = { service.createRecipeFromURL(request) },
|
||||||
logMethod = { "parseRecipeFromURL" },
|
logMethod = { "parseRecipeFromURL" },
|
||||||
logParameters = { "baseUrl = $baseUrl, request = $request" }
|
logParameters = { "request = $request" }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun createApiToken(
|
override suspend fun createApiToken(
|
||||||
baseUrl: String,
|
|
||||||
request: CreateApiTokenRequestV1
|
request: CreateApiTokenRequestV1
|
||||||
): CreateApiTokenResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
): CreateApiTokenResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
block = { service.createApiToken("$baseUrl/api/users/api-tokens", request) },
|
block = { service.createApiToken(request) },
|
||||||
logMethod = { "createApiToken" },
|
logMethod = { "createApiToken" },
|
||||||
logParameters = { "baseUrl = $baseUrl, request = $request" }
|
logParameters = { "request = $request" }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,51 +6,44 @@ import retrofit2.http.*
|
|||||||
interface MealieServiceV1 {
|
interface MealieServiceV1 {
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST
|
@POST("/api/auth/token")
|
||||||
suspend fun getToken(
|
suspend fun getToken(
|
||||||
@Url url: String,
|
|
||||||
@Field("username") username: String,
|
@Field("username") username: String,
|
||||||
@Field("password") password: String,
|
@Field("password") password: String,
|
||||||
): GetTokenResponseV1
|
): GetTokenResponseV1
|
||||||
|
|
||||||
@POST
|
@POST("/api/recipes")
|
||||||
suspend fun createRecipe(
|
suspend fun createRecipe(
|
||||||
@Url url: String,
|
|
||||||
@Body addRecipeRequest: CreateRecipeRequestV1,
|
@Body addRecipeRequest: CreateRecipeRequestV1,
|
||||||
): String
|
): String
|
||||||
|
|
||||||
@PATCH
|
@PATCH("/api/recipes/{slug}")
|
||||||
suspend fun updateRecipe(
|
suspend fun updateRecipe(
|
||||||
@Url url: String,
|
|
||||||
@Body addRecipeRequest: UpdateRecipeRequestV1,
|
@Body addRecipeRequest: UpdateRecipeRequestV1,
|
||||||
|
@Path("slug") slug: String,
|
||||||
): GetRecipeResponseV1
|
): GetRecipeResponseV1
|
||||||
|
|
||||||
@GET
|
@GET("/api/app/about")
|
||||||
suspend fun getVersion(
|
suspend fun getVersion(): VersionResponseV1
|
||||||
@Url url: String,
|
|
||||||
): VersionResponseV1
|
|
||||||
|
|
||||||
@GET
|
@GET("/api/recipes")
|
||||||
suspend fun getRecipeSummary(
|
suspend fun getRecipeSummary(
|
||||||
@Url url: String,
|
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("perPage") perPage: Int,
|
@Query("perPage") perPage: Int,
|
||||||
): GetRecipesResponseV1
|
): GetRecipesResponseV1
|
||||||
|
|
||||||
@GET
|
@GET("/api/recipes/{slug}")
|
||||||
suspend fun getRecipe(
|
suspend fun getRecipe(
|
||||||
@Url url: String,
|
@Path("slug") slug: String,
|
||||||
): GetRecipeResponseV1
|
): GetRecipeResponseV1
|
||||||
|
|
||||||
@POST
|
@POST("/api/recipes/create-url")
|
||||||
suspend fun createRecipeFromURL(
|
suspend fun createRecipeFromURL(
|
||||||
@Url url: String,
|
|
||||||
@Body request: ParseRecipeURLRequestV1,
|
@Body request: ParseRecipeURLRequestV1,
|
||||||
): String
|
): String
|
||||||
|
|
||||||
@POST
|
@POST("/api/users/api-tokens")
|
||||||
suspend fun createApiToken(
|
suspend fun createApiToken(
|
||||||
@Url url: String,
|
|
||||||
@Body request: CreateApiTokenRequestV1,
|
@Body request: CreateApiTokenRequestV1,
|
||||||
): CreateApiTokenResponseV1
|
): CreateApiTokenResponseV1
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package gq.kirmanak.mealient
|
||||||
|
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
object ReleaseModule {
|
||||||
|
|
||||||
|
// Release version of the application doesn't have any interceptors but this Set
|
||||||
|
// is required by Dagger, so an empty Set is provided here
|
||||||
|
// Use @JvmSuppressWildcards because otherwise dagger can't inject it (https://stackoverflow.com/a/43149382)
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideInterceptors(): Set<@JvmSuppressWildcards Interceptor> = emptySet()
|
||||||
|
}
|
||||||
@@ -38,34 +38,34 @@ class MealieDataSourceV0ImplTest : BaseUnitTest() {
|
|||||||
@Test(expected = NetworkError.NotMealie::class)
|
@Test(expected = NetworkError.NotMealie::class)
|
||||||
fun `when getVersionInfo and getVersion throws HttpException then NotMealie`() = runTest {
|
fun `when getVersionInfo and getVersion throws HttpException then NotMealie`() = runTest {
|
||||||
val error = HttpException(Response.error<VersionResponseV0>(404, "".toJsonResponseBody()))
|
val error = HttpException(Response.error<VersionResponseV0>(404, "".toJsonResponseBody()))
|
||||||
coEvery { service.getVersion(any()) } throws error
|
coEvery { service.getVersion() } throws error
|
||||||
subject.getVersionInfo(TEST_BASE_URL)
|
subject.getVersionInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = NetworkError.NotMealie::class)
|
@Test(expected = NetworkError.NotMealie::class)
|
||||||
fun `when getVersionInfo and getVersion throws SerializationException then NotMealie`() =
|
fun `when getVersionInfo and getVersion throws SerializationException then NotMealie`() =
|
||||||
runTest {
|
runTest {
|
||||||
coEvery { service.getVersion(any()) } throws SerializationException()
|
coEvery { service.getVersion() } throws SerializationException()
|
||||||
subject.getVersionInfo(TEST_BASE_URL)
|
subject.getVersionInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = NetworkError.NoServerConnection::class)
|
@Test(expected = NetworkError.NoServerConnection::class)
|
||||||
fun `when getVersionInfo and getVersion throws IOException then NoServerConnection`() =
|
fun `when getVersionInfo and getVersion throws IOException then NoServerConnection`() =
|
||||||
runTest {
|
runTest {
|
||||||
coEvery { service.getVersion(any()) } throws ConnectException()
|
coEvery { service.getVersion() } throws ConnectException()
|
||||||
subject.getVersionInfo(TEST_BASE_URL)
|
subject.getVersionInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when getVersionInfo and getVersion returns result then result`() = runTest {
|
fun `when getVersionInfo and getVersion returns result then result`() = runTest {
|
||||||
val versionResponse = VersionResponseV0("v0.5.6")
|
val versionResponse = VersionResponseV0("v0.5.6")
|
||||||
coEvery { service.getVersion(any()) } returns versionResponse
|
coEvery { service.getVersion() } returns versionResponse
|
||||||
assertThat(subject.getVersionInfo(TEST_BASE_URL)).isSameInstanceAs(versionResponse)
|
assertThat(subject.getVersionInfo()).isSameInstanceAs(versionResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when authentication is successful then token is correct`() = runTest {
|
fun `when authentication is successful then token is correct`() = runTest {
|
||||||
coEvery { service.getToken(any(), any(), any()) } returns GetTokenResponseV0(TEST_TOKEN)
|
coEvery { service.getToken(any(), any()) } returns GetTokenResponseV0(TEST_TOKEN)
|
||||||
assertThat(callAuthenticate()).isEqualTo(TEST_TOKEN)
|
assertThat(callAuthenticate()).isEqualTo(TEST_TOKEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ class MealieDataSourceV0ImplTest : BaseUnitTest() {
|
|||||||
fun `when authenticate receives 401 and Unauthorized then throws Unauthorized`() = runTest {
|
fun `when authenticate receives 401 and Unauthorized then throws Unauthorized`() = runTest {
|
||||||
val body = "{\"detail\":\"Unauthorized\"}".toJsonResponseBody()
|
val body = "{\"detail\":\"Unauthorized\"}".toJsonResponseBody()
|
||||||
coEvery {
|
coEvery {
|
||||||
service.getToken(any(), any(), any())
|
service.getToken(any(), any())
|
||||||
} throws HttpException(Response.error<GetTokenResponseV0>(401, body))
|
} throws HttpException(Response.error<GetTokenResponseV0>(401, body))
|
||||||
callAuthenticate()
|
callAuthenticate()
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@ class MealieDataSourceV0ImplTest : BaseUnitTest() {
|
|||||||
fun `when authenticate receives 401 but not Unauthorized then throws NotMealie`() = runTest {
|
fun `when authenticate receives 401 but not Unauthorized then throws NotMealie`() = runTest {
|
||||||
val body = "{\"detail\":\"Something\"}".toJsonResponseBody()
|
val body = "{\"detail\":\"Something\"}".toJsonResponseBody()
|
||||||
coEvery {
|
coEvery {
|
||||||
service.getToken(any(), any(), any())
|
service.getToken(any(), any())
|
||||||
} throws HttpException(Response.error<GetTokenResponseV0>(401, body))
|
} throws HttpException(Response.error<GetTokenResponseV0>(401, body))
|
||||||
callAuthenticate()
|
callAuthenticate()
|
||||||
}
|
}
|
||||||
@@ -91,22 +91,21 @@ class MealieDataSourceV0ImplTest : BaseUnitTest() {
|
|||||||
fun `when authenticate receives 404 and empty body then throws NotMealie`() = runTest {
|
fun `when authenticate receives 404 and empty body then throws NotMealie`() = runTest {
|
||||||
val body = "".toJsonResponseBody()
|
val body = "".toJsonResponseBody()
|
||||||
coEvery {
|
coEvery {
|
||||||
service.getToken(any(), any(), any())
|
service.getToken(any(), any())
|
||||||
} throws HttpException(Response.error<GetTokenResponseV0>(401, body))
|
} throws HttpException(Response.error<GetTokenResponseV0>(401, body))
|
||||||
callAuthenticate()
|
callAuthenticate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IOException::class)
|
@Test(expected = IOException::class)
|
||||||
fun `when authenticate and getToken throws then throws NoServerConnection`() = runTest {
|
fun `when authenticate and getToken throws then throws NoServerConnection`() = runTest {
|
||||||
coEvery { service.getToken(any(), any(), any()) } throws IOException("Server not found")
|
coEvery { service.getToken(any(), any()) } throws IOException("Server not found")
|
||||||
callAuthenticate()
|
callAuthenticate()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun callAuthenticate(): String =
|
private suspend fun callAuthenticate(): String =
|
||||||
subject.authenticate(TEST_USERNAME, TEST_PASSWORD, TEST_BASE_URL)
|
subject.authenticate(TEST_PASSWORD, TEST_BASE_URL)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TEST_USERNAME = "TEST_USERNAME"
|
|
||||||
const val TEST_PASSWORD = "TEST_PASSWORD"
|
const val TEST_PASSWORD = "TEST_PASSWORD"
|
||||||
const val TEST_BASE_URL = "https://example.com/"
|
const val TEST_BASE_URL = "https://example.com/"
|
||||||
const val TEST_TOKEN = "TEST_TOKEN"
|
const val TEST_TOKEN = "TEST_TOKEN"
|
||||||
|
|||||||
Reference in New Issue
Block a user