diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 273a884..77bd899 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,8 +16,8 @@ plugins { android { defaultConfig { applicationId = "gq.kirmanak.mealient" - versionCode = 25 - versionName = "0.3.10" + versionCode = 26 + versionName = "0.3.11" testInstrumentationRunner = "gq.kirmanak.mealient.MealientTestRunner" testInstrumentationRunnerArguments += mapOf("clearPackageData" to "true") } diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModel.kt index 42afc2b..015e659 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModel.kt @@ -8,6 +8,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import gq.kirmanak.mealient.data.auth.AuthRepo import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo import gq.kirmanak.mealient.data.recipes.RecipeRepo +import gq.kirmanak.mealient.datasource.NetworkError import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.ui.OperationUiState import kotlinx.coroutines.launch @@ -27,30 +28,40 @@ class BaseURLViewModel @Inject constructor( fun saveBaseUrl(baseURL: String) { logger.v { "saveBaseUrl() called with: baseURL = $baseURL" } _uiState.value = OperationUiState.Progress() - val hasPrefix = ALLOWED_PREFIXES.any { baseURL.startsWith(it) } - var url = baseURL.takeIf { hasPrefix } ?: WITH_PREFIX_FORMAT.format(baseURL) - url = url.trimStart().trimEnd { it == '/' || it.isWhitespace() } - viewModelScope.launch { checkBaseURL(url) } + viewModelScope.launch { checkBaseURL(baseURL) } } private suspend fun checkBaseURL(baseURL: String) { logger.v { "checkBaseURL() called with: baseURL = $baseURL" } - if (baseURL == serverInfoRepo.getUrl()) { + + val hasPrefix = listOf("http://", "https://").any { baseURL.startsWith(it) } + val urlWithPrefix = baseURL.takeIf { hasPrefix } ?: "https://%s".format(baseURL) + val url = urlWithPrefix.trimEnd { it == '/' } + + logger.d { "checkBaseURL: Created URL = \"$url\", with prefix = \"$urlWithPrefix\"" } + if (url == serverInfoRepo.getUrl()) { logger.d { "checkBaseURL: new URL matches current" } _uiState.value = OperationUiState.fromResult(Result.success(Unit)) return } - val result = serverInfoRepo.tryBaseURL(baseURL) + + val result: Result = serverInfoRepo.tryBaseURL(url).recoverCatching { + logger.e(it) { "checkBaseURL: trying to recover, had prefix = $hasPrefix" } + if (hasPrefix || it is NetworkError.NotMealie) { + throw it + } else { + val unencryptedUrl = url.replace("https", "http") + serverInfoRepo.tryBaseURL(unencryptedUrl).getOrThrow() + } + } + if (result.isSuccess) { authRepo.logout() recipeRepo.clearLocalData() } + logger.i { "checkBaseURL: result is $result" } _uiState.value = OperationUiState.fromResult(result) } - companion object { - private val ALLOWED_PREFIXES = listOf("http://", "https://") - private const val WITH_PREFIX_FORMAT = "https://%s" - } } \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModelTest.kt b/app/src/test/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModelTest.kt index c0fd1d2..59b10ea 100644 --- a/app/src/test/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModelTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModelTest.kt @@ -4,17 +4,20 @@ import com.google.common.truth.Truth.assertThat import gq.kirmanak.mealient.data.auth.AuthRepo import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo import gq.kirmanak.mealient.data.recipes.RecipeRepo +import gq.kirmanak.mealient.datasource.NetworkError import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL import gq.kirmanak.mealient.test.BaseUnitTest import gq.kirmanak.mealient.ui.OperationUiState import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.coVerifyOrder import io.mockk.impl.annotations.MockK import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.* import org.junit.Before import org.junit.Test import java.io.IOException +import javax.net.ssl.SSLHandshakeException @OptIn(ExperimentalCoroutinesApi::class) class BaseURLViewModelTest : BaseUnitTest() { @@ -105,4 +108,35 @@ class BaseURLViewModelTest : BaseUnitTest() { advanceUntilIdle() assertThat(subject.uiState.value).isInstanceOf(OperationUiState.Failure::class.java) } + + @Test + fun `when saving base url with no prefix and https throws expect http attempt`() = runTest { + coEvery { serverInfoRepo.getUrl() } returns null + val err = NetworkError.MalformedUrl(SSLHandshakeException("test")) + coEvery { serverInfoRepo.tryBaseURL("https://test") } returns Result.failure(err) + coEvery { serverInfoRepo.tryBaseURL("http://test") } returns Result.success(Unit) + subject.saveBaseUrl("test") + coVerifyOrder { + serverInfoRepo.tryBaseURL("https://test") + serverInfoRepo.tryBaseURL("http://test") + } + } + + @Test + fun `when saving base url with no prefix and https throws non ssl expect no http`() = runTest { + coEvery { serverInfoRepo.getUrl() } returns null + val err = NetworkError.NotMealie(IOException()) + coEvery { serverInfoRepo.tryBaseURL("https://test") } returns Result.failure(err) + subject.saveBaseUrl("test") + coVerify(inverse = true) { serverInfoRepo.tryBaseURL("http://test") } + } + + @Test + fun `when saving base url with https prefix and https throws expect no http call`() = runTest { + coEvery { serverInfoRepo.getUrl() } returns null + val err = NetworkError.MalformedUrl(SSLHandshakeException("test")) + coEvery { serverInfoRepo.tryBaseURL("https://test") } returns Result.failure(err) + subject.saveBaseUrl("https://test") + coVerify(inverse = true) { serverInfoRepo.tryBaseURL("http://test") } + } } \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/NetworkError.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/NetworkError.kt index 50953a4..024c234 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/NetworkError.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/NetworkError.kt @@ -1,6 +1,6 @@ package gq.kirmanak.mealient.datasource -sealed class NetworkError(cause: Throwable) : RuntimeException(cause) { +sealed class NetworkError(cause: Throwable) : RuntimeException(cause.message, cause) { class Unauthorized(cause: Throwable) : NetworkError(cause) class NoServerConnection(cause: Throwable) : NetworkError(cause) class NotMealie(cause: Throwable) : NetworkError(cause) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/BaseUrlInterceptor.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/BaseUrlInterceptor.kt index 4b40e26..ad00f26 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/BaseUrlInterceptor.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/BaseUrlInterceptor.kt @@ -4,7 +4,7 @@ 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.HttpUrl.Companion.toHttpUrl import okhttp3.Interceptor import okhttp3.Response import java.io.IOException @@ -37,6 +37,12 @@ class BaseUrlInterceptor @Inject constructor( } private fun getBaseUrl() = runBlocking { - serverUrlProvider.getUrl()?.toHttpUrlOrNull() ?: throw IOException("Base URL is unknown") + val url = serverUrlProvider.getUrl() ?: throw IOException("Base URL is unknown") + url.runCatching { + toHttpUrl() + }.fold( + onSuccess = { it }, + onFailure = { throw IOException(it.message, it) }, + ) } } \ No newline at end of file